Path: blob/master/modules/mono/build_scripts/build_assemblies.py
20784 views
#!/usr/bin/env python312from __future__ import annotations34import os5import os.path6import shlex7import subprocess8from dataclasses import dataclass91011def find_dotnet_cli():12if os.name == "nt":13for hint_dir in os.environ["PATH"].split(os.pathsep):14hint_dir = hint_dir.strip('"')15hint_path = os.path.join(hint_dir, "dotnet")16if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):17return hint_path18if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):19return hint_path + ".exe"20else:21for hint_dir in os.environ["PATH"].split(os.pathsep):22hint_dir = hint_dir.strip('"')23hint_path = os.path.join(hint_dir, "dotnet")24if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):25return hint_path262728def find_msbuild_standalone_windows():29msbuild_tools_path = find_msbuild_tools_path_reg()3031if msbuild_tools_path:32return os.path.join(msbuild_tools_path, "MSBuild.exe")3334return None353637def find_msbuild_mono_windows(mono_prefix):38assert mono_prefix is not None3940mono_bin_dir = os.path.join(mono_prefix, "bin")41msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat")4243if os.path.isfile(msbuild_mono):44return msbuild_mono4546return None474849def find_msbuild_mono_unix():50import sys5152hint_dirs = []53if sys.platform == "darwin":54hint_dirs[:0] = [55"/Library/Frameworks/Mono.framework/Versions/Current/bin",56"/usr/local/var/homebrew/linked/mono/bin",57]5859for hint_dir in hint_dirs:60hint_path = os.path.join(hint_dir, "msbuild")61if os.path.isfile(hint_path):62return hint_path63elif os.path.isfile(hint_path + ".exe"):64return hint_path + ".exe"6566for hint_dir in os.environ["PATH"].split(os.pathsep):67hint_dir = hint_dir.strip('"')68hint_path = os.path.join(hint_dir, "msbuild")69if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):70return hint_path71if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):72return hint_path + ".exe"7374return None757677def find_msbuild_tools_path_reg():78import subprocess7980program_files = os.getenv("PROGRAMFILES(X86)")81if not program_files:82program_files = os.getenv("PROGRAMFILES")83vswhere = os.path.join(program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe")8485vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"]8687try:88lines = subprocess.check_output([vswhere] + vswhere_args).splitlines()8990for line in lines:91parts = line.decode("utf-8").split(":", 1)9293if len(parts) < 2 or parts[0] != "installationPath":94continue9596val = parts[1].strip()9798if not val:99raise ValueError("Value of `installationPath` entry is empty")100101# Since VS2019, the directory is simply named "Current"102msbuild_dir = os.path.join(val, "MSBuild", "Current", "Bin")103if os.path.isdir(msbuild_dir):104return msbuild_dir105106# Directory name "15.0" is used in VS 2017107return os.path.join(val, "MSBuild", "15.0", "Bin")108109raise ValueError("Cannot find `installationPath` entry")110except ValueError as e:111print("Error reading output from vswhere: " + str(e))112except OSError:113pass # Fine, vswhere not found114except (subprocess.CalledProcessError, OSError):115pass116117118@dataclass119class ToolsLocation:120dotnet_cli: str = ""121msbuild_standalone: str = ""122msbuild_mono: str = ""123mono_bin_dir: str = ""124125126def find_any_msbuild_tool(mono_prefix):127# Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild128129# Find dotnet CLI130dotnet_cli = find_dotnet_cli()131if dotnet_cli:132return ToolsLocation(dotnet_cli=dotnet_cli)133134# Find standalone MSBuild135if os.name == "nt":136msbuild_standalone = find_msbuild_standalone_windows()137if msbuild_standalone:138return ToolsLocation(msbuild_standalone=msbuild_standalone)139140if mono_prefix:141# Find Mono's MSBuild142if os.name == "nt":143msbuild_mono = find_msbuild_mono_windows(mono_prefix)144if msbuild_mono:145return ToolsLocation(msbuild_mono=msbuild_mono)146else:147msbuild_mono = find_msbuild_mono_unix()148if msbuild_mono:149return ToolsLocation(msbuild_mono=msbuild_mono)150151return None152153154def run_msbuild(tools: ToolsLocation, sln: str, chdir_to: str, msbuild_args: list[str] | None = None):155using_msbuild_mono = False156157# Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild158if tools.dotnet_cli:159args = [tools.dotnet_cli, "msbuild"]160elif tools.msbuild_standalone:161args = [tools.msbuild_standalone]162elif tools.msbuild_mono:163args = [tools.msbuild_mono]164using_msbuild_mono = True165else:166raise RuntimeError("Path to MSBuild or dotnet CLI not provided.")167168args += [sln]169170if msbuild_args:171args += msbuild_args172173print("Running MSBuild: ", " ".join(shlex.quote(arg) for arg in args), flush=True)174175msbuild_env = os.environ.copy()176177# Needed when running from Developer Command Prompt for VS178if "PLATFORM" in msbuild_env:179del msbuild_env["PLATFORM"]180181if using_msbuild_mono:182# The (Csc/Vbc/Fsc)ToolExe environment variables are required when183# building with Mono's MSBuild. They must point to the batch files184# in Mono's bin directory to make sure they are executed with Mono.185msbuild_env.update({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})190191# We want to control cwd when running msbuild, because that's where the search for global.json begins.192return subprocess.call(args, env=msbuild_env, cwd=chdir_to)193194195def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated, werror):196target_filenames = [197"GodotSharp.dll",198"GodotSharp.pdb",199"GodotSharp.xml",200"GodotSharpEditor.dll",201"GodotSharpEditor.pdb",202"GodotSharpEditor.xml",203"GodotPlugins.dll",204"GodotPlugins.pdb",205"GodotPlugins.runtimeconfig.json",206]207208for build_config in ["Debug", "Release"]:209editor_api_dir = os.path.join(output_dir, "GodotSharp", "Api", build_config)210211targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames]212213args = ["/restore", "/t:Build", "/p:Configuration=" + build_config, "/p:NoWarn=1591"]214if push_nupkgs_local:215args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]216if precision == "double":217args += ["/p:GodotFloat64=true"]218if no_deprecated:219args += ["/p:GodotNoDeprecated=true"]220if werror:221args += ["/p:TreatWarningsAsErrors=true"]222223sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln")224exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args)225if exit_code != 0:226return exit_code227228# Copy targets229230core_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharp", "bin", build_config))231editor_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharpEditor", "bin", build_config))232plugins_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotPlugins", "bin", build_config, "net8.0"))233234if not os.path.isdir(editor_api_dir):235assert not os.path.isfile(editor_api_dir)236os.makedirs(editor_api_dir)237238def copy_target(target_path):239from shutil import copy240241filename = os.path.basename(target_path)242243src_path = os.path.join(core_src_dir, filename)244if not os.path.isfile(src_path):245src_path = os.path.join(editor_src_dir, filename)246if not os.path.isfile(src_path):247src_path = os.path.join(plugins_src_dir, filename)248249print(f"Copying assembly to {target_path}...")250copy(src_path, target_path)251252for scons_target in targets:253copy_target(scons_target)254255return 0256257258def generate_sdk_package_versions():259# I can't believe importing files in Python is so convoluted when not260# following the golden standard for packages/modules.261import os262import sys263from os.path import dirname264265# We want ../../../methods.py.266script_path = dirname(os.path.abspath(__file__))267root_path = dirname(dirname(dirname(script_path)))268269sys.path.insert(0, root_path)270from methods import get_version_info271272version_info = get_version_info("")273sys.path.remove(root_path)274275version_str = "{major}.{minor}.{patch}".format(**version_info)276version_status = version_info["status"]277if version_status != "stable": # Pre-release278# If version was overridden to be e.g. "beta3", we insert a dot between279# "beta" and "3" to follow SemVer 2.0.280import re281282match = re.search(r"[\d]+$", version_status)283if match:284pos = match.start()285version_status = version_status[:pos] + "." + version_status[pos:]286version_str += "-" + version_status287288import version289290version_defines = (291[292f"GODOT{version.major}",293f"GODOT{version.major}_{version.minor}",294f"GODOT{version.major}_{version.minor}_{version.patch}",295]296+ [f"GODOT{v}_OR_GREATER" for v in range(4, version.major + 1)]297+ [f"GODOT{version.major}_{v}_OR_GREATER" for v in range(0, version.minor + 1)]298+ [f"GODOT{version.major}_{version.minor}_{v}_OR_GREATER" for v in range(0, version.patch + 1)]299)300301props = """<Project>302<PropertyGroup>303<PackageVersion_GodotSharp>{0}</PackageVersion_GodotSharp>304<PackageVersion_Godot_NET_Sdk>{0}</PackageVersion_Godot_NET_Sdk>305<PackageVersion_Godot_SourceGenerators>{0}</PackageVersion_Godot_SourceGenerators>306<GodotVersionConstants>{1}</GodotVersionConstants>307</PropertyGroup>308</Project>309""".format(version_str, ";".join(version_defines))310311# We write in ../SdkPackageVersions.props.312with open(os.path.join(dirname(script_path), "SdkPackageVersions.props"), "w", encoding="utf-8", newline="\n") as f:313f.write(props)314315# Also write the versioned docs URL to a constant for the Source Generators.316317constants = """namespace Godot.SourceGenerators318{{319// TODO: This is currently disabled because of https://github.com/dotnet/roslyn/issues/52904320#pragma warning disable IDE0040 // Add accessibility modifiers.321partial class Common322{{323public const string VersionDocsUrl = "https://docs.godotengine.org/en/{docs_branch}";324}}325}}326""".format(**version_info)327328generators_dir = os.path.join(329dirname(script_path),330"editor",331"Godot.NET.Sdk",332"Godot.SourceGenerators",333"Generated",334)335os.makedirs(generators_dir, exist_ok=True)336337with open(os.path.join(generators_dir, "Common.Constants.cs"), "w", encoding="utf-8", newline="\n") as f:338f.write(constants)339340341def build_all(342msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision, no_deprecated, werror343):344# Generate SdkPackageVersions.props and VersionDocsUrl constant345generate_sdk_package_versions()346347# Godot API348exit_code = build_godot_api(349msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated, werror350)351if exit_code != 0:352return exit_code353354# GodotTools355sln = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln")356args = ["/restore", "/t:Build", "/p:Configuration=" + ("Debug" if dev_debug else "Release")] + (357["/p:GodotPlatform=" + godot_platform] if godot_platform else []358)359if push_nupkgs_local:360args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]361if precision == "double":362args += ["/p:GodotFloat64=true"]363exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args)364if exit_code != 0:365return exit_code366367# Godot.NET.Sdk368args = ["/restore", "/t:Build", "/p:Configuration=Release"]369if push_nupkgs_local:370args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local]371if precision == "double":372args += ["/p:GodotFloat64=true"]373if no_deprecated:374args += ["/p:GodotNoDeprecated=true"]375sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln")376exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args)377if exit_code != 0:378return exit_code379380return 0381382383def main():384import argparse385import sys386387parser = argparse.ArgumentParser(description="Builds all Godot .NET solutions")388parser.add_argument("--godot-output-dir", type=str, required=True)389parser.add_argument(390"--dev-debug",391action="store_true",392default=False,393help="Build GodotTools and Godot.NET.Sdk with 'Configuration=Debug'",394)395parser.add_argument("--godot-platform", type=str, default="")396parser.add_argument("--mono-prefix", type=str, default="")397parser.add_argument("--push-nupkgs-local", type=str, default="")398parser.add_argument(399"--precision", type=str, default="single", choices=["single", "double"], help="Floating-point precision level"400)401parser.add_argument(402"--no-deprecated",403action="store_true",404default=False,405help="Build GodotSharp without using deprecated features. This is required, if the engine was built with 'deprecated=no'.",406)407parser.add_argument("--werror", action="store_true", default=False, help="Treat compiler warnings as errors.")408409args = parser.parse_args()410411this_script_dir = os.path.dirname(os.path.realpath(__file__))412module_dir = os.path.abspath(os.path.join(this_script_dir, os.pardir))413414output_dir = os.path.abspath(args.godot_output_dir)415416push_nupkgs_local = os.path.abspath(args.push_nupkgs_local) if args.push_nupkgs_local else None417418msbuild_tool = find_any_msbuild_tool(args.mono_prefix)419420if msbuild_tool is None:421print("Unable to find MSBuild")422sys.exit(1)423424exit_code = build_all(425msbuild_tool,426module_dir,427output_dir,428args.godot_platform,429args.dev_debug,430push_nupkgs_local,431args.precision,432args.no_deprecated,433args.werror,434)435sys.exit(exit_code)436437438if __name__ == "__main__":439main()440441442