build_assemblies.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. #!/usr/bin/python3
  2. import os
  3. import os.path
  4. import shlex
  5. import subprocess
  6. from dataclasses import dataclass
  7. def find_dotnet_cli():
  8. if os.name == "nt":
  9. for hint_dir in os.environ["PATH"].split(os.pathsep):
  10. hint_dir = hint_dir.strip('"')
  11. hint_path = os.path.join(hint_dir, "dotnet")
  12. if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
  13. return hint_path
  14. if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
  15. return hint_path + ".exe"
  16. else:
  17. for hint_dir in os.environ["PATH"].split(os.pathsep):
  18. hint_dir = hint_dir.strip('"')
  19. hint_path = os.path.join(hint_dir, "dotnet")
  20. if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
  21. return hint_path
  22. def find_msbuild_standalone_windows():
  23. msbuild_tools_path = find_msbuild_tools_path_reg()
  24. if msbuild_tools_path:
  25. return os.path.join(msbuild_tools_path, "MSBuild.exe")
  26. return None
  27. def find_msbuild_mono_windows(mono_prefix):
  28. assert mono_prefix is not None
  29. mono_bin_dir = os.path.join(mono_prefix, "bin")
  30. msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat")
  31. if os.path.isfile(msbuild_mono):
  32. return msbuild_mono
  33. return None
  34. def find_msbuild_mono_unix():
  35. import sys
  36. hint_dirs = []
  37. if sys.platform == "darwin":
  38. hint_dirs[:0] = [
  39. "/Library/Frameworks/Mono.framework/Versions/Current/bin",
  40. "/usr/local/var/homebrew/linked/mono/bin",
  41. ]
  42. for hint_dir in hint_dirs:
  43. hint_path = os.path.join(hint_dir, "msbuild")
  44. if os.path.isfile(hint_path):
  45. return hint_path
  46. elif os.path.isfile(hint_path + ".exe"):
  47. return hint_path + ".exe"
  48. for hint_dir in os.environ["PATH"].split(os.pathsep):
  49. hint_dir = hint_dir.strip('"')
  50. hint_path = os.path.join(hint_dir, "msbuild")
  51. if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
  52. return hint_path
  53. if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
  54. return hint_path + ".exe"
  55. return None
  56. def find_msbuild_tools_path_reg():
  57. import subprocess
  58. program_files = os.getenv("PROGRAMFILES(X86)")
  59. if not program_files:
  60. program_files = os.getenv("PROGRAMFILES")
  61. vswhere = os.path.join(program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe")
  62. vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"]
  63. try:
  64. lines = subprocess.check_output([vswhere] + vswhere_args).splitlines()
  65. for line in lines:
  66. parts = line.decode("utf-8").split(":", 1)
  67. if len(parts) < 2 or parts[0] != "installationPath":
  68. continue
  69. val = parts[1].strip()
  70. if not val:
  71. raise ValueError("Value of `installationPath` entry is empty")
  72. # Since VS2019, the directory is simply named "Current"
  73. msbuild_dir = os.path.join(val, "MSBuild", "Current", "Bin")
  74. if os.path.isdir(msbuild_dir):
  75. return msbuild_dir
  76. # Directory name "15.0" is used in VS 2017
  77. return os.path.join(val, "MSBuild", "15.0", "Bin")
  78. raise ValueError("Cannot find `installationPath` entry")
  79. except ValueError as e:
  80. print("Error reading output from vswhere: " + str(e))
  81. except OSError:
  82. pass # Fine, vswhere not found
  83. except (subprocess.CalledProcessError, OSError):
  84. pass
  85. @dataclass
  86. class ToolsLocation:
  87. dotnet_cli: str = ""
  88. msbuild_standalone: str = ""
  89. msbuild_mono: str = ""
  90. mono_bin_dir: str = ""
  91. def find_any_msbuild_tool(mono_prefix):
  92. # Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild
  93. # Find dotnet CLI
  94. dotnet_cli = find_dotnet_cli()
  95. if dotnet_cli:
  96. return ToolsLocation(dotnet_cli=dotnet_cli)
  97. # Find standalone MSBuild
  98. if os.name == "nt":
  99. msbuild_standalone = find_msbuild_standalone_windows()
  100. if msbuild_standalone:
  101. return ToolsLocation(msbuild_standalone=msbuild_standalone)
  102. if mono_prefix:
  103. # Find Mono's MSBuild
  104. if os.name == "nt":
  105. msbuild_mono = find_msbuild_mono_windows(mono_prefix)
  106. if msbuild_mono:
  107. return ToolsLocation(msbuild_mono=msbuild_mono)
  108. else:
  109. msbuild_mono = find_msbuild_mono_unix()
  110. if msbuild_mono:
  111. return ToolsLocation(msbuild_mono=msbuild_mono)
  112. return None
  113. def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: [str] = None):
  114. if msbuild_args is None:
  115. msbuild_args = []
  116. using_msbuild_mono = False
  117. # Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild
  118. if tools.dotnet_cli:
  119. args = [tools.dotnet_cli, "msbuild"]
  120. elif tools.msbuild_standalone:
  121. args = [tools.msbuild_standalone]
  122. elif tools.msbuild_mono:
  123. args = [tools.msbuild_mono]
  124. using_msbuild_mono = True
  125. else:
  126. raise RuntimeError("Path to MSBuild or dotnet CLI not provided.")
  127. args += [sln]
  128. if len(msbuild_args) > 0:
  129. args += msbuild_args
  130. print("Running MSBuild: ", " ".join(shlex.quote(arg) for arg in args), flush=True)
  131. msbuild_env = os.environ.copy()
  132. # Needed when running from Developer Command Prompt for VS
  133. if "PLATFORM" in msbuild_env:
  134. del msbuild_env["PLATFORM"]
  135. if using_msbuild_mono:
  136. # The (Csc/Vbc/Fsc)ToolExe environment variables are required when
  137. # building with Mono's MSBuild. They must point to the batch files
  138. # in Mono's bin directory to make sure they are executed with Mono.
  139. msbuild_env.update(
  140. {
  141. "CscToolExe": os.path.join(tools.mono_bin_dir, "csc.bat"),
  142. "VbcToolExe": os.path.join(tools.mono_bin_dir, "vbc.bat"),
  143. "FscToolExe": os.path.join(tools.mono_bin_dir, "fsharpc.bat"),
  144. }
  145. )
  146. return subprocess.call(args, env=msbuild_env)
  147. def build_godot_api(msbuild_tool, module_dir, output_dir):
  148. target_filenames = [
  149. "GodotSharp.dll",
  150. "GodotSharp.pdb",
  151. "GodotSharp.xml",
  152. "GodotSharpEditor.dll",
  153. "GodotSharpEditor.pdb",
  154. "GodotSharpEditor.xml",
  155. "GodotPlugins.dll",
  156. "GodotPlugins.pdb",
  157. "GodotPlugins.runtimeconfig.json",
  158. ]
  159. for build_config in ["Debug", "Release"]:
  160. editor_api_dir = os.path.join(output_dir, "GodotSharp", "Api", build_config)
  161. targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames]
  162. sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln")
  163. exit_code = run_msbuild(
  164. msbuild_tool,
  165. sln=sln,
  166. msbuild_args=["/restore", "/t:Build", "/p:Configuration=" + build_config, "/p:NoWarn=1591"],
  167. )
  168. if exit_code != 0:
  169. return exit_code
  170. # Copy targets
  171. core_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharp", "bin", build_config))
  172. editor_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharpEditor", "bin", build_config))
  173. plugins_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotPlugins", "bin", build_config, "net5.0"))
  174. if not os.path.isdir(editor_api_dir):
  175. assert not os.path.isfile(editor_api_dir)
  176. os.makedirs(editor_api_dir)
  177. def copy_target(target_path):
  178. from shutil import copy
  179. filename = os.path.basename(target_path)
  180. src_path = os.path.join(core_src_dir, filename)
  181. if not os.path.isfile(src_path):
  182. src_path = os.path.join(editor_src_dir, filename)
  183. if not os.path.isfile(src_path):
  184. src_path = os.path.join(plugins_src_dir, filename)
  185. print(f"Copying assembly to {target_path}...")
  186. copy(src_path, target_path)
  187. for scons_target in targets:
  188. copy_target(scons_target)
  189. return 0
  190. def build_all(msbuild_tool, module_dir, output_dir, dev_debug, godot_platform):
  191. # Godot API
  192. exit_code = build_godot_api(msbuild_tool, module_dir, output_dir)
  193. if exit_code != 0:
  194. return exit_code
  195. # GodotTools
  196. sln = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln")
  197. args = ["/restore", "/t:Build", "/p:Configuration=" + ("Debug" if dev_debug else "Release")] + (
  198. ["/p:GodotPlatform=" + godot_platform] if godot_platform else []
  199. )
  200. exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args)
  201. if exit_code != 0:
  202. return exit_code
  203. # Godot.NET.Sdk
  204. sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln")
  205. exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=["/restore", "/t:Build", "/p:Configuration=Release"])
  206. if exit_code != 0:
  207. return exit_code
  208. return 0
  209. def main():
  210. import argparse
  211. import sys
  212. parser = argparse.ArgumentParser(description="Builds all Godot .NET solutions")
  213. parser.add_argument("--godot-output-dir", type=str, required=True)
  214. parser.add_argument(
  215. "--dev-debug",
  216. action="store_true",
  217. default=False,
  218. help="Build GodotTools and Godot.NET.Sdk with 'Configuration=Debug'",
  219. )
  220. parser.add_argument("--godot-platform", type=str, default="")
  221. parser.add_argument("--mono-prefix", type=str, default="")
  222. args = parser.parse_args()
  223. this_script_dir = os.path.dirname(os.path.realpath(__file__))
  224. module_dir = os.path.abspath(os.path.join(this_script_dir, os.pardir))
  225. output_dir = os.path.abspath(args.godot_output_dir)
  226. msbuild_tool = find_any_msbuild_tool(args.mono_prefix)
  227. if msbuild_tool is None:
  228. print("Unable to find MSBuild")
  229. sys.exit(1)
  230. exit_code = build_all(msbuild_tool, module_dir, output_dir, args.godot_platform, args.dev_debug)
  231. sys.exit(exit_code)
  232. if __name__ == "__main__":
  233. main()