88e367a406
This base implementation is still very barebones but it defines the path for how exporting will work (at least when embedding the .NET runtime). Many manual steps are still needed, which should be automatized in the future. For example, in addition to the API assemblies, now you also need to copy the GodotPlugins assembly to each game project.
272 lines
8.3 KiB
Python
272 lines
8.3 KiB
Python
import os
|
|
import os.path
|
|
|
|
|
|
def is_desktop(platform):
|
|
return platform in ["windows", "macos", "linuxbsd", "server", "uwp", "haiku"]
|
|
|
|
|
|
def is_unix_like(platform):
|
|
return platform in ["macos", "linuxbsd", "server", "android", "haiku", "ios"]
|
|
|
|
|
|
def module_supports_tools_on(platform):
|
|
return is_desktop(platform)
|
|
|
|
|
|
def configure(env, env_mono):
|
|
# is_android = env["platform"] == "android"
|
|
# is_javascript = env["platform"] == "javascript"
|
|
# is_ios = env["platform"] == "ios"
|
|
# is_ios_sim = is_ios and env["arch"] in ["x86", "x86_64"]
|
|
|
|
tools_enabled = env["tools"]
|
|
|
|
if tools_enabled and not module_supports_tools_on(env["platform"]):
|
|
raise RuntimeError("This module does not currently support building for this platform with tools enabled")
|
|
|
|
if env["tools"]:
|
|
env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"])
|
|
|
|
app_host_dir = find_dotnet_app_host_dir(env)
|
|
|
|
def check_app_host_file_exists(file):
|
|
file_path = os.path.join(app_host_dir, file)
|
|
if not os.path.isfile(file_path):
|
|
raise RuntimeError("File not found: " + file_path)
|
|
|
|
# TODO:
|
|
# All libnethost does for us is provide a function to find hostfxr.
|
|
# If we could handle that logic ourselves we could void linking it.
|
|
|
|
# nethost file names:
|
|
# static: libnethost.a/lib
|
|
# shared: libnethost.a/dylib and nethost.dll
|
|
check_app_host_file_exists("libnethost.lib" if os.name == "nt" else "libnethost.a")
|
|
check_app_host_file_exists("nethost.h")
|
|
check_app_host_file_exists("hostfxr.h")
|
|
check_app_host_file_exists("coreclr_delegates.h")
|
|
|
|
env_mono.Prepend(CPPPATH=app_host_dir)
|
|
|
|
env.Append(LIBPATH=[app_host_dir])
|
|
|
|
# Only the editor build links nethost, which is needed to find hostfxr.
|
|
# Exported games don't need this logic as hostfxr is bundled with them.
|
|
if tools_enabled:
|
|
libnethost_path = os.path.join(app_host_dir, "libnethost.lib" if os.name == "nt" else "libnethost.a")
|
|
|
|
if env["platform"] == "windows":
|
|
env_mono.Append(CPPDEFINES=["NETHOST_USE_AS_STATIC"])
|
|
|
|
if env.msvc:
|
|
env.Append(LINKFLAGS="libnethost.lib")
|
|
else:
|
|
env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
|
|
else:
|
|
is_apple = env["platform"] in ["macos", "ios"]
|
|
# is_macos = is_apple and not is_ios
|
|
|
|
# if is_ios and not is_ios_sim:
|
|
# env_mono.Append(CPPDEFINES=["IOS_DEVICE"])
|
|
|
|
if is_apple:
|
|
env.Append(LINKFLAGS=["-Wl,-force_load," + libnethost_path])
|
|
else:
|
|
env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"])
|
|
|
|
|
|
def find_dotnet_app_host_dir(env):
|
|
dotnet_version = "5.0"
|
|
|
|
dotnet_root = env["dotnet_root"]
|
|
|
|
if not dotnet_root:
|
|
dotnet_cmd = find_executable("dotnet")
|
|
if dotnet_cmd:
|
|
sdk_path = find_dotnet_sdk(dotnet_cmd, dotnet_version)
|
|
if sdk_path:
|
|
dotnet_root = os.path.abspath(os.path.join(sdk_path, os.pardir))
|
|
|
|
if not dotnet_root:
|
|
raise RuntimeError("Cannot find .NET Core Sdk")
|
|
|
|
print("Found .NET Core Sdk root directory: " + dotnet_root)
|
|
|
|
dotnet_cmd = os.path.join(dotnet_root, "dotnet.exe" if os.name == "nt" else "dotnet")
|
|
|
|
runtime_identifier = determine_runtime_identifier(env)
|
|
|
|
# TODO: In the future, if it can't be found this way, we want to obtain it
|
|
# from the runtime.{runtime_identifier}.Microsoft.NETCore.DotNetAppHost NuGet package.
|
|
app_host_version = find_app_host_version(dotnet_cmd, dotnet_version)
|
|
if not app_host_version:
|
|
raise RuntimeError("Cannot find .NET app host for version: " + dotnet_version)
|
|
|
|
def get_runtime_path():
|
|
return os.path.join(
|
|
dotnet_root,
|
|
"packs",
|
|
"Microsoft.NETCore.App.Host." + runtime_identifier,
|
|
app_host_version,
|
|
"runtimes",
|
|
runtime_identifier,
|
|
"native",
|
|
)
|
|
|
|
app_host_dir = get_runtime_path()
|
|
|
|
# Some Linux distros use their distro name as the RID in these paths.
|
|
# If the initial generic path doesn't exist, try to get the RID from `dotnet --info`.
|
|
# The generic RID should still be the first choice. Some platforms like Windows 10
|
|
# define the RID as `win10-x64` but still use the generic `win-x64` for directory names.
|
|
if not app_host_dir or not os.path.isdir(app_host_dir):
|
|
runtime_identifier = find_dotnet_cli_rid(dotnet_cmd)
|
|
app_host_dir = get_runtime_path()
|
|
|
|
return app_host_dir
|
|
|
|
|
|
def determine_runtime_identifier(env):
|
|
names_map = {
|
|
"windows": "win",
|
|
"macos": "osx",
|
|
"linuxbsd": "linux",
|
|
"server": "linux", # FIXME: Is server linux only, or also macos?
|
|
}
|
|
|
|
# .NET RID architectures: x86, x64, arm, or arm64
|
|
|
|
platform = env["platform"]
|
|
|
|
if is_desktop(platform):
|
|
if env["arch"] in ["arm", "arm32"]:
|
|
rid = "arm"
|
|
elif env["arch"] == "arm64":
|
|
rid = "arm64"
|
|
else:
|
|
bits = env["bits"]
|
|
bit_arch_map = {"64": "x64", "32": "x86"}
|
|
rid = bit_arch_map[bits]
|
|
return "%s-%s" % (names_map[platform], rid)
|
|
else:
|
|
raise NotImplementedError()
|
|
|
|
|
|
def find_app_host_version(dotnet_cmd, search_version_str):
|
|
import subprocess
|
|
from distutils.version import LooseVersion
|
|
|
|
search_version = LooseVersion(search_version_str)
|
|
|
|
try:
|
|
env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
|
|
lines = subprocess.check_output([dotnet_cmd, "--list-runtimes"], env=env).splitlines()
|
|
|
|
for line_bytes in lines:
|
|
line = line_bytes.decode("utf-8")
|
|
if not line.startswith("Microsoft.NETCore.App "):
|
|
continue
|
|
|
|
parts = line.split(" ", 2)
|
|
if len(parts) < 3:
|
|
continue
|
|
|
|
version_str = parts[1]
|
|
|
|
version = LooseVersion(version_str)
|
|
|
|
if version >= search_version:
|
|
return version_str
|
|
except (subprocess.CalledProcessError, OSError) as e:
|
|
import sys
|
|
|
|
print(e, file=sys.stderr)
|
|
|
|
return ""
|
|
|
|
|
|
def find_dotnet_sdk(dotnet_cmd, search_version_str):
|
|
import subprocess
|
|
from distutils.version import LooseVersion
|
|
|
|
search_version = LooseVersion(search_version_str)
|
|
|
|
try:
|
|
env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
|
|
lines = subprocess.check_output([dotnet_cmd, "--list-sdks"], env=env).splitlines()
|
|
|
|
for line_bytes in lines:
|
|
line = line_bytes.decode("utf-8")
|
|
|
|
parts = line.split(" ", 1)
|
|
if len(parts) < 2:
|
|
continue
|
|
|
|
version_str = parts[0]
|
|
|
|
version = LooseVersion(version_str)
|
|
|
|
if version < search_version:
|
|
continue
|
|
|
|
path_part = parts[1]
|
|
return path_part[1 : path_part.find("]")]
|
|
except (subprocess.CalledProcessError, OSError) as e:
|
|
import sys
|
|
|
|
print(e, file=sys.stderr)
|
|
|
|
return ""
|
|
|
|
|
|
def find_dotnet_cli_rid(dotnet_cmd):
|
|
import subprocess
|
|
|
|
try:
|
|
env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US")
|
|
lines = subprocess.check_output([dotnet_cmd, "--info"], env=env).splitlines()
|
|
|
|
for line_bytes in lines:
|
|
line = line_bytes.decode("utf-8")
|
|
if not line.startswith(" RID:"):
|
|
continue
|
|
|
|
parts = line.split()
|
|
if len(parts) < 2:
|
|
continue
|
|
|
|
return parts[1]
|
|
except (subprocess.CalledProcessError, OSError) as e:
|
|
import sys
|
|
|
|
print(e, file=sys.stderr)
|
|
|
|
return ""
|
|
|
|
|
|
ENV_PATH_SEP = ";" if os.name == "nt" else ":"
|
|
|
|
|
|
def find_executable(name):
|
|
is_windows = os.name == "nt"
|
|
windows_exts = os.environ["PATHEXT"].split(ENV_PATH_SEP) if is_windows else None
|
|
path_dirs = os.environ["PATH"].split(ENV_PATH_SEP)
|
|
|
|
search_dirs = path_dirs + [os.getcwd()] # cwd is last in the list
|
|
|
|
for dir in search_dirs:
|
|
path = os.path.join(dir, name)
|
|
|
|
if is_windows:
|
|
for extension in windows_exts:
|
|
path_with_ext = path + extension
|
|
|
|
if os.path.isfile(path_with_ext) and os.access(path_with_ext, os.X_OK):
|
|
return path_with_ext
|
|
else:
|
|
if os.path.isfile(path) and os.access(path, os.X_OK):
|
|
return path
|
|
|
|
return ""
|