mono_configure.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. import os
  2. import os.path
  3. def is_desktop(platform):
  4. return platform in ["windows", "macos", "linuxbsd", "uwp", "haiku"]
  5. def is_unix_like(platform):
  6. return platform in ["macos", "linuxbsd", "android", "haiku", "ios"]
  7. def module_supports_tools_on(platform):
  8. return is_desktop(platform)
  9. def configure(env, env_mono):
  10. # is_android = env["platform"] == "android"
  11. # is_javascript = env["platform"] == "javascript"
  12. # is_ios = env["platform"] == "ios"
  13. # is_ios_sim = is_ios and env["arch"] in ["x86_32", "x86_64"]
  14. tools_enabled = env["tools"]
  15. if tools_enabled and not module_supports_tools_on(env["platform"]):
  16. raise RuntimeError("This module does not currently support building for this platform with tools enabled")
  17. if env["tools"]:
  18. env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"])
  19. app_host_dir = find_dotnet_app_host_dir(env)
  20. def check_app_host_file_exists(file):
  21. file_path = os.path.join(app_host_dir, file)
  22. if not os.path.isfile(file_path):
  23. raise RuntimeError("File not found: " + file_path)
  24. # TODO:
  25. # All libnethost does for us is provide a function to find hostfxr.
  26. # If we could handle that logic ourselves we could void linking it.
  27. # nethost file names:
  28. # static: libnethost.a/lib
  29. # shared: libnethost.a/dylib and nethost.dll
  30. check_app_host_file_exists("libnethost.lib" if os.name == "nt" else "libnethost.a")
  31. check_app_host_file_exists("nethost.h")
  32. check_app_host_file_exists("hostfxr.h")
  33. check_app_host_file_exists("coreclr_delegates.h")
  34. env_mono.Prepend(CPPPATH=app_host_dir)
  35. env.Append(LIBPATH=[app_host_dir])
  36. # Only the editor build links nethost, which is needed to find hostfxr.
  37. # Exported games don't need this logic as hostfxr is bundled with them.
  38. if tools_enabled:
  39. libnethost_path = os.path.join(app_host_dir, "libnethost.lib" if os.name == "nt" else "libnethost.a")
  40. if env["platform"] == "windows":
  41. env_mono.Append(CPPDEFINES=["NETHOST_USE_AS_STATIC"])
  42. if env.msvc:
  43. env.Append(LINKFLAGS="libnethost.lib")
  44. else:
  45. env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
  46. else:
  47. is_apple = env["platform"] in ["macos", "ios"]
  48. # is_macos = is_apple and not is_ios
  49. # if is_ios and not is_ios_sim:
  50. # env_mono.Append(CPPDEFINES=["IOS_DEVICE"])
  51. if is_apple:
  52. env.Append(LINKFLAGS=["-Wl,-force_load," + libnethost_path])
  53. else:
  54. env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
  55. def find_dotnet_app_host_dir(env):
  56. dotnet_version = "6.0"
  57. dotnet_root = env["dotnet_root"]
  58. if not dotnet_root:
  59. dotnet_cmd = find_dotnet_executable(env["arch"])
  60. if dotnet_cmd:
  61. sdk_path = find_dotnet_sdk(dotnet_cmd, dotnet_version)
  62. if sdk_path:
  63. dotnet_root = os.path.abspath(os.path.join(sdk_path, os.pardir))
  64. if not dotnet_root:
  65. raise RuntimeError("Cannot find .NET Core Sdk")
  66. print("Found .NET Core Sdk root directory: " + dotnet_root)
  67. dotnet_cmd = os.path.join(dotnet_root, "dotnet.exe" if os.name == "nt" else "dotnet")
  68. runtime_identifier = determine_runtime_identifier(env)
  69. # TODO: In the future, if it can't be found this way, we want to obtain it
  70. # from the runtime.{runtime_identifier}.Microsoft.NETCore.DotNetAppHost NuGet package.
  71. app_host_version = find_app_host_version(dotnet_cmd, dotnet_version)
  72. if not app_host_version:
  73. raise RuntimeError("Cannot find .NET app host for version: " + dotnet_version)
  74. def get_runtime_path():
  75. return os.path.join(
  76. dotnet_root,
  77. "packs",
  78. "Microsoft.NETCore.App.Host." + runtime_identifier,
  79. app_host_version,
  80. "runtimes",
  81. runtime_identifier,
  82. "native",
  83. )
  84. app_host_dir = get_runtime_path()
  85. # Some Linux distros use their distro name as the RID in these paths.
  86. # If the initial generic path doesn't exist, try to get the RID from `dotnet --info`.
  87. # The generic RID should still be the first choice. Some platforms like Windows 10
  88. # define the RID as `win10-x64` but still use the generic `win-x64` for directory names.
  89. if not app_host_dir or not os.path.isdir(app_host_dir):
  90. runtime_identifier = find_dotnet_cli_rid(dotnet_cmd)
  91. app_host_dir = get_runtime_path()
  92. return app_host_dir
  93. def determine_runtime_identifier(env):
  94. # The keys are Godot's names, the values are the Microsoft's names.
  95. # List: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
  96. names_map = {
  97. "windows": "win",
  98. "macos": "osx",
  99. "linuxbsd": "linux",
  100. }
  101. arch_map = {
  102. "x86_64": "x64",
  103. "x86_32": "x86",
  104. "arm64": "arm64",
  105. "arm32": "arm",
  106. }
  107. platform = env["platform"]
  108. if is_desktop(platform):
  109. return "%s-%s" % (names_map[platform], arch_map[env["arch"]])
  110. else:
  111. raise NotImplementedError()
  112. def find_app_host_version(dotnet_cmd, search_version_str):
  113. import subprocess
  114. from distutils.version import LooseVersion
  115. search_version = LooseVersion(search_version_str)
  116. found_match = False
  117. try:
  118. env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
  119. lines = subprocess.check_output([dotnet_cmd, "--list-runtimes"], env=env).splitlines()
  120. for line_bytes in lines:
  121. line = line_bytes.decode("utf-8")
  122. if not line.startswith("Microsoft.NETCore.App "):
  123. continue
  124. parts = line.split(" ", 2)
  125. if len(parts) < 3:
  126. continue
  127. version_str = parts[1]
  128. version = LooseVersion(version_str)
  129. if version >= search_version:
  130. search_version = version
  131. found_match = True
  132. if found_match:
  133. return str(search_version)
  134. except (subprocess.CalledProcessError, OSError) as e:
  135. import sys
  136. print(e, file=sys.stderr)
  137. return ""
  138. def find_dotnet_arch(dotnet_cmd):
  139. import subprocess
  140. try:
  141. env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
  142. lines = subprocess.check_output([dotnet_cmd, "--info"], env=env).splitlines()
  143. for line_bytes in lines:
  144. line = line_bytes.decode("utf-8")
  145. parts = line.split(":", 1)
  146. if len(parts) < 2:
  147. continue
  148. arch_str = parts[0].strip()
  149. if arch_str != "Architecture":
  150. continue
  151. arch_value = parts[1].strip()
  152. arch_map = {"x64": "x86_64", "x86": "x86_32", "arm64": "arm64", "arm32": "arm32"}
  153. return arch_map[arch_value]
  154. except (subprocess.CalledProcessError, OSError) as e:
  155. import sys
  156. print(e, file=sys.stderr)
  157. return ""
  158. def find_dotnet_sdk(dotnet_cmd, search_version_str):
  159. import subprocess
  160. from distutils.version import LooseVersion
  161. search_version = LooseVersion(search_version_str)
  162. try:
  163. env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
  164. lines = subprocess.check_output([dotnet_cmd, "--list-sdks"], env=env).splitlines()
  165. for line_bytes in lines:
  166. line = line_bytes.decode("utf-8")
  167. parts = line.split(" ", 1)
  168. if len(parts) < 2:
  169. continue
  170. version_str = parts[0]
  171. version = LooseVersion(version_str)
  172. if version < search_version:
  173. continue
  174. path_part = parts[1]
  175. return path_part[1 : path_part.find("]")]
  176. except (subprocess.CalledProcessError, OSError) as e:
  177. import sys
  178. print(e, file=sys.stderr)
  179. return ""
  180. def find_dotnet_cli_rid(dotnet_cmd):
  181. import subprocess
  182. try:
  183. env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
  184. lines = subprocess.check_output([dotnet_cmd, "--info"], env=env).splitlines()
  185. for line_bytes in lines:
  186. line = line_bytes.decode("utf-8")
  187. if not line.startswith(" RID:"):
  188. continue
  189. parts = line.split()
  190. if len(parts) < 2:
  191. continue
  192. return parts[1]
  193. except (subprocess.CalledProcessError, OSError) as e:
  194. import sys
  195. print(e, file=sys.stderr)
  196. return ""
  197. ENV_PATH_SEP = ";" if os.name == "nt" else ":"
  198. def find_dotnet_executable(arch):
  199. is_windows = os.name == "nt"
  200. windows_exts = os.environ["PATHEXT"].split(ENV_PATH_SEP) if is_windows else None
  201. path_dirs = os.environ["PATH"].split(ENV_PATH_SEP)
  202. search_dirs = path_dirs + [os.getcwd()] # cwd is last in the list
  203. for dir in path_dirs:
  204. search_dirs += [
  205. os.path.join(dir, "x64"),
  206. os.path.join(dir, "x86"),
  207. os.path.join(dir, "arm64"),
  208. os.path.join(dir, "arm32"),
  209. ] # search subfolders for cross compiling
  210. # `dotnet --info` may not specify architecture. In such cases,
  211. # we fallback to the first one we find without architecture.
  212. sdk_path_unknown_arch = ""
  213. for dir in search_dirs:
  214. path = os.path.join(dir, "dotnet")
  215. if is_windows:
  216. for extension in windows_exts:
  217. path_with_ext = path + extension
  218. if os.path.isfile(path_with_ext) and os.access(path_with_ext, os.X_OK):
  219. sdk_arch = find_dotnet_arch(path_with_ext)
  220. if sdk_arch == arch or arch == "":
  221. return path_with_ext
  222. elif sdk_arch == "":
  223. sdk_path_unknown_arch = path_with_ext
  224. else:
  225. if os.path.isfile(path) and os.access(path, os.X_OK):
  226. sdk_arch = find_dotnet_arch(path)
  227. if sdk_arch == arch or arch == "":
  228. return path
  229. elif sdk_arch == "":
  230. sdk_path_unknown_arch = path
  231. return sdk_path_unknown_arch