Path: blob/master/modules/mono/build_scripts/build_assemblies.py
10279 views
#!/usr/bin/python312import os3import os.path4import shlex5import subprocess6from dataclasses import dataclass7from typing import List, Optional8910def find_dotnet_cli():11if os.name == "nt":12for hint_dir in os.environ["PATH"].split(os.pathsep):13hint_dir = hint_dir.strip('"')14hint_path = os.path.join(hint_dir, "dotnet")15if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):16return hint_path17if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):18return hint_path + ".exe"19else:20for hint_dir in os.environ["PATH"].split(os.pathsep):21hint_dir = hint_dir.strip('"')22hint_path = os.path.join(hint_dir, "dotnet")23if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):24return hint_path252627def find_msbuild_standalone_windows():28msbuild_tools_path = find_msbuild_tools_path_reg()2930if msbuild_tools_path:31return os.path.join(msbuild_tools_path, "MSBuild.exe")3233return None343536def find_msbuild_mono_windows(mono_prefix):37assert mono_prefix is not None3839mono_bin_dir = os.path.join(mono_prefix, "bin")40msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat")4142if os.path.isfile(msbuild_mono):43return msbuild_mono4445return None464748def find_msbuild_mono_unix():49import sys5051hint_dirs = []52if sys.platform == "darwin":53hint_dirs[:0] = [54"/Library/Frameworks/Mono.framework/Versions/Current/bin",55"/usr/local/var/homebrew/linked/mono/bin",56]5758for hint_dir in hint_dirs:59hint_path = os.path.join(hint_dir, "msbuild")60if os.path.isfile(hint_path):61return hint_path62elif os.path.isfile(hint_path + ".exe"):63return hint_path + ".exe"6465for hint_dir in os.environ["PATH"].split(os.pathsep):66hint_dir = hint_dir.strip('"')67hint_path = os.path.join(hint_dir, "msbuild")68if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):69return hint_path70if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):71return hint_path + ".exe"7273return None747576def find_msbuild_tools_path_reg():77import subprocess7879program_files = os.getenv("PROGRAMFILES(X86)")80if not program_files:81program_files = os.getenv("PROGRAMFILES")82vswhere = os.path.join(program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe")8384vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"]8586try:87lines = subprocess.check_output([vswhere] + vswhere_args).splitlines()8889for line in lines:90parts = line.decode("utf-8").split(":", 1)9192if len(parts) < 2 or parts[0] != "installationPath":93continue9495val = parts[1].strip()9697if not val:98raise ValueError("Value of `installationPath` entry is empty")99100# Since VS2019, the directory is simply named "Current"101msbuild_dir = os.path.join(val, "MSBuild", "Current", "Bin")102if os.path.isdir(msbuild_dir):103return msbuild_dir104105# Directory name "15.0" is used in VS 2017106return os.path.join(val, "MSBuild", "15.0", "Bin")107108raise ValueError("Cannot find `installationPath` entry")109except ValueError as e:110print("Error reading output from vswhere: " + str(e))111except OSError:112pass # Fine, vswhere not found113except (subprocess.CalledProcessError, OSError):114pass115116117@dataclass118class ToolsLocation:119dotnet_cli: str = ""120msbuild_standalone: str = ""121msbuild_mono: str = ""122mono_bin_dir: str = ""123124125def find_any_msbuild_tool(mono_prefix):126# Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild127128# Find dotnet CLI129dotnet_cli = find_dotnet_cli()130if dotnet_cli:131return ToolsLocation(dotnet_cli=dotnet_cli)132133# Find standalone MSBuild134if os.name == "nt":135msbuild_standalone = find_msbuild_standalone_windows()136if msbuild_standalone:137return ToolsLocation(msbuild_standalone=msbuild_standalone)138139if mono_prefix:140# Find Mono's MSBuild141if os.name == "nt":142msbuild_mono = find_msbuild_mono_windows(mono_prefix)143if msbuild_mono:144return ToolsLocation(msbuild_mono=msbuild_mono)145else:146msbuild_mono = find_msbuild_mono_unix()147if msbuild_mono:148return ToolsLocation(msbuild_mono=msbuild_mono)149150return None151152153def run_msbuild(tools: ToolsLocation, sln: str, chdir_to: str, msbuild_args: Optional[List[str]] = None):154using_msbuild_mono = False155156# Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild157if tools.dotnet_cli:158args = [tools.dotnet_cli, "msbuild"]159elif tools.msbuild_standalone:160args = [tools.msbuild_standalone]161elif tools.msbuild_mono:162args = [tools.msbuild_mono]163using_msbuild_mono = True164else:165raise RuntimeError("Path to MSBuild or dotnet CLI not provided.")166167args += [sln]168169if msbuild_args:170args += msbuild_args171172print("Running MSBuild: ", " ".join(shlex.quote(arg) for arg in args), flush=True)173174msbuild_env = os.environ.copy()175176# Needed when running from Developer Command Prompt for VS177if "PLATFORM" in msbuild_env:178del msbuild_env["PLATFORM"]179180if using_msbuild_mono:181# The (Csc/Vbc/Fsc)ToolExe environment variables are required when182# building with Mono's MSBuild. They must point to the batch files183# in Mono's bin directory to make sure they are executed with Mono.184msbuild_env.update(185{186"CscToolExe": os.path.join(tools.mono_bin_dir, "csc.bat"),187"VbcToolExe": os.path.join(tools.mono_bin_dir, "vbc.bat"),188"FscToolExe": os.path.join(tools.mono_bin_dir, "fsharpc.bat"),189}190)191192# We want to control cwd when running msbuild, because that's where the search for global.json begins.193return subprocess.call(args, env=msbuild_env, cwd=chdir_to)194195196def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated, werror):197target_filenames = [198"GodotSharp.dll",199"GodotSharp.pdb",200"GodotSharp.xml",201"GodotSharpEditor.dll",202"GodotSharpEditor.pdb",203"GodotSharpEditor.xml",204"GodotPlugins.dll",205"GodotPlugins.pdb",206"GodotPlugins.runtimeconfig.json",207]208209for build_config in ["Debug", "Release"]:210editor_api_dir = os.path.join(output_dir, "GodotSharp", "Api", build_config)211212targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames]213214args = ["/restore", "/t:Build", "/p:Configuration=" + build_config, "/p:NoWarn=1591"]215if push_nupkgs_local:216args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]217if precision == "double":218args += ["/p:GodotFloat64=true"]219if no_deprecated:220args += ["/p:GodotNoDeprecated=true"]221if werror:222args += ["/p:TreatWarningsAsErrors=true"]223224sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln")225exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args)226if exit_code != 0:227return exit_code228229# Copy targets230231core_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharp", "bin", build_config))232editor_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharpEditor", "bin", build_config))233plugins_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotPlugins", "bin", build_config, "net8.0"))234235if not os.path.isdir(editor_api_dir):236assert not os.path.isfile(editor_api_dir)237os.makedirs(editor_api_dir)238239def copy_target(target_path):240from shutil import copy241242filename = os.path.basename(target_path)243244src_path = os.path.join(core_src_dir, filename)245if not os.path.isfile(src_path):246src_path = os.path.join(editor_src_dir, filename)247if not os.path.isfile(src_path):248src_path = os.path.join(plugins_src_dir, filename)249250print(f"Copying assembly to {target_path}...")251copy(src_path, target_path)252253for scons_target in targets:254copy_target(scons_target)255256return 0257258259def generate_sdk_package_versions():260# I can't believe importing files in Python is so convoluted when not261# following the golden standard for packages/modules.262import os263import sys264from os.path import dirname265266# We want ../../../methods.py.267script_path = dirname(os.path.abspath(__file__))268root_path = dirname(dirname(dirname(script_path)))269270sys.path.insert(0, root_path)271from methods import get_version_info272273version_info = get_version_info("")274sys.path.remove(root_path)275276version_str = "{major}.{minor}.{patch}".format(**version_info)277version_status = version_info["status"]278if version_status != "stable": # Pre-release279# If version was overridden to be e.g. "beta3", we insert a dot between280# "beta" and "3" to follow SemVer 2.0.281import re282283match = re.search(r"[\d]+$", version_status)284if match:285pos = match.start()286version_status = version_status[:pos] + "." + version_status[pos:]287version_str += "-" + version_status288289import version290291version_defines = (292[293f"GODOT{version.major}",294f"GODOT{version.major}_{version.minor}",295f"GODOT{version.major}_{version.minor}_{version.patch}",296]297+ [f"GODOT{v}_OR_GREATER" for v in range(4, version.major + 1)]298+ [f"GODOT{version.major}_{v}_OR_GREATER" for v in range(0, version.minor + 1)]299+ [f"GODOT{version.major}_{version.minor}_{v}_OR_GREATER" for v in range(0, version.patch + 1)]300)301302props = """<Project>303<PropertyGroup>304<PackageVersion_GodotSharp>{0}</PackageVersion_GodotSharp>305<PackageVersion_Godot_NET_Sdk>{0}</PackageVersion_Godot_NET_Sdk>306<PackageVersion_Godot_SourceGenerators>{0}</PackageVersion_Godot_SourceGenerators>307<GodotVersionConstants>{1}</GodotVersionConstants>308</PropertyGroup>309</Project>310""".format(version_str, ";".join(version_defines))311312# We write in ../SdkPackageVersions.props.313with open(os.path.join(dirname(script_path), "SdkPackageVersions.props"), "w", encoding="utf-8", newline="\n") as f:314f.write(props)315316# Also write the versioned docs URL to a constant for the Source Generators.317318constants = """namespace Godot.SourceGenerators319{{320// TODO: This is currently disabled because of https://github.com/dotnet/roslyn/issues/52904321#pragma warning disable IDE0040 // Add accessibility modifiers.322partial class Common323{{324public const string VersionDocsUrl = "https://docs.godotengine.org/en/{docs_branch}";325}}326}}327""".format(**version_info)328329generators_dir = os.path.join(330dirname(script_path),331"editor",332"Godot.NET.Sdk",333"Godot.SourceGenerators",334"Generated",335)336os.makedirs(generators_dir, exist_ok=True)337338with open(os.path.join(generators_dir, "Common.Constants.cs"), "w", encoding="utf-8", newline="\n") as f:339f.write(constants)340341342def build_all(343msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision, no_deprecated, werror344):345# Generate SdkPackageVersions.props and VersionDocsUrl constant346generate_sdk_package_versions()347348# Godot API349exit_code = build_godot_api(350msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated, werror351)352if exit_code != 0:353return exit_code354355# GodotTools356sln = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln")357args = ["/restore", "/t:Build", "/p:Configuration=" + ("Debug" if dev_debug else "Release")] + (358["/p:GodotPlatform=" + godot_platform] if godot_platform else []359)360if push_nupkgs_local:361args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]362if precision == "double":363args += ["/p:GodotFloat64=true"]364exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args)365if exit_code != 0:366return exit_code367368# Godot.NET.Sdk369args = ["/restore", "/t:Build", "/p:Configuration=Release"]370if push_nupkgs_local:371args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]372if precision == "double":373args += ["/p:GodotFloat64=true"]374if no_deprecated:375args += ["/p:GodotNoDeprecated=true"]376sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln")377exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args)378if exit_code != 0:379return exit_code380381return 0382383384def main():385import argparse386import sys387388parser = argparse.ArgumentParser(description="Builds all Godot .NET solutions")389parser.add_argument("--godot-output-dir", type=str, required=True)390parser.add_argument(391"--dev-debug",392action="store_true",393default=False,394help="Build GodotTools and Godot.NET.Sdk with 'Configuration=Debug'",395)396parser.add_argument("--godot-platform", type=str, default="")397parser.add_argument("--mono-prefix", type=str, default="")398parser.add_argument("--push-nupkgs-local", type=str, default="")399parser.add_argument(400"--precision", type=str, default="single", choices=["single", "double"], help="Floating-point precision level"401)402parser.add_argument(403"--no-deprecated",404action="store_true",405default=False,406help="Build GodotSharp without using deprecated features. This is required, if the engine was built with 'deprecated=no'.",407)408parser.add_argument("--werror", action="store_true", default=False, help="Treat compiler warnings as errors.")409410args = parser.parse_args()411412this_script_dir = os.path.dirname(os.path.realpath(__file__))413module_dir = os.path.abspath(os.path.join(this_script_dir, os.pardir))414415output_dir = os.path.abspath(args.godot_output_dir)416417push_nupkgs_local = os.path.abspath(args.push_nupkgs_local) if args.push_nupkgs_local else None418419msbuild_tool = find_any_msbuild_tool(args.mono_prefix)420421if msbuild_tool is None:422print("Unable to find MSBuild")423sys.exit(1)424425exit_code = build_all(426msbuild_tool,427module_dir,428output_dir,429args.godot_platform,430args.dev_debug,431push_nupkgs_local,432args.precision,433args.no_deprecated,434args.werror,435)436sys.exit(exit_code)437438439if __name__ == "__main__":440main()441442443