diff --git a/methods.py b/methods.py index 649072d0984..1f49da7acef 100644 --- a/methods.py +++ b/methods.py @@ -819,21 +819,12 @@ def generate_vs_project(env, num_jobs): module_configs = ModuleConfigs() if env.get("module_mono_enabled"): - import modules.mono.build_scripts.mono_configure as mono_configure - - app_host_dir = mono_configure.find_dotnet_app_host_dir(env) - if app_host_dir and os.path.isdir(app_host_dir): - mono_defines = [("NETHOST_USE_AS_STATIC",)] - if env["tools"]: - mono_defines += [("GD_MONO_HOT_RELOAD",)] - module_configs.add_mode( - "mono", - includes=app_host_dir, - cli_args="module_mono_enabled=yes", - defines=mono_defines, - ) - else: - print(".NET App Host directory not found. Generated project will not have build variants for .NET.") + mono_defines = [("GD_MONO_HOT_RELOAD",)] if env["tools"] else [] + module_configs.add_mode( + "mono", + cli_args="module_mono_enabled=yes", + defines=mono_defines, + ) env["MSVSBUILDCOM"] = module_configs.build_commandline("scons") env["MSVSREBUILDCOM"] = module_configs.build_commandline("scons vsproj=yes") diff --git a/misc/scripts/clang_format.sh b/misc/scripts/clang_format.sh index b7c577d5fb8..edecdb6ecb3 100755 --- a/misc/scripts/clang_format.sh +++ b/misc/scripts/clang_format.sh @@ -7,8 +7,8 @@ set -uo pipefail # Loops through all code files tracked by Git. git ls-files -- '*.c' '*.h' '*.cpp' '*.hpp' '*.cc' '*.hh' '*.cxx' '*.m' '*.mm' '*.inc' '*.java' '*.glsl' \ - ':!:.git/*' ':!:thirdparty/*' ':!:platform/android/java/lib/src/com/google/*' ':!:*-so_wrap.*' \ - ':!:tests/python_build/*' | + ':!:.git/*' ':!:thirdparty/*' ':!:*/thirdparty/*' ':!:platform/android/java/lib/src/com/google/*' \ + ':!:*-so_wrap.*' ':!:tests/python_build/*' | while read -r f; do # Run clang-format. clang-format --Wno-error=unknown -i "$f" diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index c2d54528370..5d637730960 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -27,293 +27,3 @@ def configure(env, env_mono): 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 = "6.0" - - dotnet_root = env["dotnet_root"] - - if not dotnet_root: - dotnet_cmd = find_dotnet_executable(env["arch"]) - 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): - # The keys are Godot's names, the values are the Microsoft's names. - # List: https://docs.microsoft.com/en-us/dotnet/core/rid-catalog - names_map = { - "windows": "win", - "macos": "osx", - "linuxbsd": "linux", - } - arch_map = { - "x86_64": "x64", - "x86_32": "x86", - "arm64": "arm64", - "arm32": "arm", - } - platform = env["platform"] - if is_desktop(platform): - return "%s-%s" % (names_map[platform], arch_map[env["arch"]]) - 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) - found_match = False - - 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: - search_version = version - found_match = True - if found_match: - return str(search_version) - except (subprocess.CalledProcessError, OSError) as e: - import sys - - print(e, file=sys.stderr) - - return "" - - -def find_dotnet_arch(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") - - parts = line.split(":", 1) - if len(parts) < 2: - continue - - arch_str = parts[0].strip() - if arch_str != "Architecture": - continue - - arch_value = parts[1].strip() - arch_map = {"x64": "x86_64", "x86": "x86_32", "arm64": "arm64", "arm32": "arm32"} - return arch_map[arch_value] - 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_dotnet_executable(arch): - 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 path_dirs: - search_dirs += [ - os.path.join(dir, "x64"), - os.path.join(dir, "x86"), - os.path.join(dir, "arm64"), - os.path.join(dir, "arm32"), - ] # search subfolders for cross compiling - - # `dotnet --info` may not specify architecture. In such cases, - # we fallback to the first one we find without architecture. - sdk_path_unknown_arch = "" - - for dir in search_dirs: - path = os.path.join(dir, "dotnet") - - 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): - sdk_arch = find_dotnet_arch(path_with_ext) - if sdk_arch == arch or arch == "": - return path_with_ext - elif sdk_arch == "": - sdk_path_unknown_arch = path_with_ext - else: - if os.path.isfile(path) and os.access(path, os.X_OK): - sdk_arch = find_dotnet_arch(path) - if sdk_arch == arch or arch == "": - return path - elif sdk_arch == "": - sdk_path_unknown_arch = path - - return sdk_path_unknown_arch diff --git a/modules/mono/config.py b/modules/mono/config.py index ad78c8c898e..15fe79ef8cb 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -4,23 +4,13 @@ supported_platforms = ["windows", "macos", "linuxbsd"] def can_build(env, platform): - return not env["arch"].startswith("rv") + if env["arch"].startswith("rv"): + return False + if env["tools"]: + env.module_add_dependencies("mono", ["regex"]) -def get_opts(platform): - from SCons.Variables import BoolVariable, PathVariable - - default_mono_static = platform in ["ios", "web"] - default_mono_bundles_zlib = platform in ["web"] - - return [ - PathVariable( - "dotnet_root", - "Path to the .NET Sdk installation directory for the target platform and architecture", - "", - PathVariable.PathAccept, - ), - ] + return True def configure(env): diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp new file mode 100644 index 00000000000..bdc8fac8b58 --- /dev/null +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -0,0 +1,335 @@ +/*************************************************************************/ +/* hostfxr_resolver.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +/* +Adapted to Godot from the nethost library: https://github.com/dotnet/runtime/tree/main/src/native/corehost +*/ + +/* +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "hostfxr_resolver.h" + +#include "core/config/engine.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/os/os.h" + +#ifdef WINDOWS_ENABLED +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include "../utils/path_utils.h" +#include "semver.h" + +// We don't use libnethost as it gives us issues with some compilers. +// This file tries to mimic libnethost's hostfxr_resolver search logic. We try to use the +// same function names for easier comparing in case we need to update this in the future. + +namespace { + +String get_hostfxr_file_name() { +#if defined(WINDOWS_ENABLED) || defined(UWP_ENABLED) + return "hostfxr.dll"; +#elif defined(OSX_ENABLED) || defined(IOS_ENABLED) + return "libhostfxr.dylib"; +#else + return "libhostfxr.so"; +#endif +} + +bool get_latest_fxr(const String &fxr_root, String &r_fxr_path) { + godotsharp::SemVerParser sem_ver_parser; + + bool found_ver = false; + godotsharp::SemVer latest_ver; + String latest_ver_str; + + Ref da = DirAccess::open(fxr_root); + da->list_dir_begin(); + for (String dir = da->get_next(); !dir.is_empty(); dir = da->get_next()) { + if (!da->current_is_dir() || dir == "." || dir == "..") { + continue; + } + + String ver = dir.get_file(); + + godotsharp::SemVer fx_ver; + if (sem_ver_parser.parse(ver, fx_ver)) { + if (!found_ver || fx_ver > latest_ver) { + latest_ver = fx_ver; + latest_ver_str = ver; + found_ver = true; + } + } + } + + if (!found_ver) { + return false; + } + + String fxr_with_ver = path::join(fxr_root, latest_ver_str); + String hostfxr_file_path = path::join(fxr_with_ver, get_hostfxr_file_name()); + + ERR_FAIL_COND_V_MSG(!FileAccess::exists(hostfxr_file_path), false, "Missing hostfxr library in directory: " + fxr_with_ver); + + r_fxr_path = hostfxr_file_path; + + return true; +} + +#ifdef WINDOWS_ENABLED +typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL); + +BOOL is_wow64() { + BOOL wow64 = FALSE; + + LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); + + if (fnIsWow64Process) { + if (!fnIsWow64Process(GetCurrentProcess(), &wow64)) { + wow64 = FALSE; + } + } + + return wow64; +} +#endif + +static const char *arch_name_map[][2] = { + { "arm32", "arm" }, + { "arm64", "arm64" }, + { "rv64", "riscv64" }, + { "x86_64", "x64" }, + { "x86_32", "x86" }, + { nullptr, nullptr } +}; + +String get_dotnet_arch() { + String arch = Engine::get_singleton()->get_architecture_name(); + + int idx = 0; + while (arch_name_map[idx][0] != nullptr) { + if (arch_name_map[idx][0] == arch) { + return arch_name_map[idx][1]; + } + idx++; + } + + return ""; +} + +bool get_default_installation_dir(String &r_dotnet_root) { +#if defined(WINDOWS_ENABLED) + String program_files_env; + if (is_wow64()) { + // Running x86 on x64, looking for x86 install + program_files_env = "ProgramFiles(x86)"; + } else { + program_files_env = "ProgramFiles"; + } + + String program_files_dir = OS::get_singleton()->get_environment(program_files_env); + + if (program_files_dir.is_empty()) { + return false; + } + +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + String dotnet_root_emulated = path::join(program_files_dir, "dotnet", "x64"); + if (FileAccess::exists(path::join(dotnet_root_emulated, "dotnet.exe"))) { + r_dotnet_root = dotnet_root_emulated; + return true; + } +#endif + + r_dotnet_root = path::join(program_files_dir, "dotnet"); + return true; +#elif defined(TARGET_OSX) + r_dotnet_root = "/usr/local/share/dotnet"; + +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) + // When emulating x64 on arm + String dotnet_root_emulated = path::join(r_dotnet_root, "x64"); + if (FileAccess::exists(path::join(dotnet_root_emulated, "dotnet"))) { + r_dotnet_root = dotnet_root_emulated; + return true; + } +#endif + + return true; +#else + r_dotnet_root = "/usr/share/dotnet"; + return true; +#endif +} + +bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) { + Error err = OK; + Ref f = FileAccess::open(p_file_path, FileAccess::READ, &err); + + if (f.is_null() || err != OK) { + return false; + } + + String line = f->get_line(); + + if (line.is_empty()) { + return false; + } + + r_dotnet_root = line; + return true; +} + +bool get_dotnet_self_registered_dir(String &r_dotnet_root) { +#if defined(WINDOWS_ENABLED) + String sub_key = "SOFTWARE\\dotnet\\Setup\\InstalledVersions\\" + get_dotnet_arch(); + Char16String value = String("InstallLocation").utf16(); + + HKEY hkey = NULL; + LSTATUS result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(sub_key.utf16().get_data()), 0, KEY_READ | KEY_WOW64_32KEY, &hkey); + if (result != ERROR_SUCCESS) { + return false; + } + + DWORD size = 0; + result = RegGetValueW(hkey, nullptr, (LPCWSTR)(value.get_data()), RRF_RT_REG_SZ, nullptr, nullptr, &size); + if (result != ERROR_SUCCESS || size == 0) { + RegCloseKey(hkey); + return false; + } + + Vector buffer; + buffer.resize(size / sizeof(WCHAR)); + result = RegGetValueW(hkey, nullptr, (LPCWSTR)(value.get_data()), RRF_RT_REG_SZ, nullptr, (LPBYTE)buffer.ptrw(), &size); + if (result != ERROR_SUCCESS) { + RegCloseKey(hkey); + return false; + } + + r_dotnet_root = String::utf16((const char16_t *)buffer.ptr()); + RegCloseKey(hkey); + return true; +#else + String install_location_file = path::join("/etc/dotnet", "install_location_" + get_dotnet_arch().to_lower()); + if (get_install_location_from_file(install_location_file, r_dotnet_root)) { + return true; + } + + if (FileAccess::exists(install_location_file)) { + // Don't try with the legacy location, this will fall back to the hard-coded default install location + return false; + } + + String legacy_install_location_file = path::join("/etc/dotnet", "install_location"); + return get_install_location_from_file(legacy_install_location_file, r_dotnet_root); +#endif +} + +bool get_file_path_from_env(const String &p_env_key, String &r_dotnet_root) { + String env_value = OS::get_singleton()->get_environment(p_env_key); + + if (!env_value.is_empty()) { + env_value = path::realpath(env_value); + + if (DirAccess::exists(env_value)) { + r_dotnet_root = env_value; + return true; + } + } + + return false; +} + +bool get_dotnet_root_from_env(String &r_dotnet_root) { + String dotnet_root_env = "DOTNET_ROOT"; + String arch_for_env = get_dotnet_arch(); + + if (!arch_for_env.is_empty()) { + // DOTNET_ROOT_ + if (get_file_path_from_env(dotnet_root_env + "_" + arch_for_env.to_upper(), r_dotnet_root)) { + return true; + } + } + +#ifdef WINDOWS_ENABLED + // WoW64-only: DOTNET_ROOT(x86) + if (is_wow64() && get_file_path_from_env("DOTNET_ROOT(x86)", r_dotnet_root)) { + return true; + } +#endif + + // DOTNET_ROOT + return get_file_path_from_env(dotnet_root_env, r_dotnet_root); +} + +} //namespace + +bool godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_fxr_path) { + String fxr_dir = path::join(p_dotnet_root, "host", "fxr"); + ERR_FAIL_COND_V_MSG(!DirAccess::exists(fxr_dir), false, "The host fxr folder does not exist: " + fxr_dir); + return get_latest_fxr(fxr_dir, r_fxr_path); +} + +bool godotsharp::hostfxr_resolver::try_get_path(String &r_dotnet_root, String &r_fxr_path) { + if (!get_dotnet_root_from_env(r_dotnet_root) && + !get_dotnet_self_registered_dir(r_dotnet_root) && + !get_default_installation_dir(r_dotnet_root)) { + return false; + } + + return try_get_path_from_dotnet_root(r_dotnet_root, r_fxr_path); +} diff --git a/modules/mono/editor/hostfxr_resolver.h b/modules/mono/editor/hostfxr_resolver.h new file mode 100644 index 00000000000..0f029ab7aeb --- /dev/null +++ b/modules/mono/editor/hostfxr_resolver.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* hostfxr_resolver.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef HOSTFXR_RESOLVER_H +#define HOSTFXR_RESOLVER_H + +#include "core/string/ustring.h" + +namespace godotsharp { +namespace hostfxr_resolver { + +bool try_get_path_from_dotnet_root(const String &p_dotnet_root, String &r_out_fxr_path); +bool try_get_path(String &r_out_dotnet_root, String &r_out_fxr_path); + +} //namespace hostfxr_resolver +} //namespace godotsharp + +#endif // HOSTFXR_RESOLVER_H diff --git a/modules/mono/editor/semver.cpp b/modules/mono/editor/semver.cpp new file mode 100644 index 00000000000..1656d0932f5 --- /dev/null +++ b/modules/mono/editor/semver.cpp @@ -0,0 +1,149 @@ +/*************************************************************************/ +/* semver.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "semver.h" + +bool godotsharp::SemVer::parse_digit_only_field(const String &p_field, uint64_t &r_result) { + if (p_field.is_empty()) { + return false; + } + + int64_t integer = 0; + + for (int i = 0; i < p_field.length(); i++) { + char32_t c = p_field[i]; + if (is_digit(c)) { + bool overflow = ((uint64_t)integer > UINT64_MAX / 10) || ((uint64_t)integer == UINT64_MAX / 10 && c > '5'); + ERR_FAIL_COND_V_MSG(overflow, false, "Cannot represent '" + p_field + "' as a 64-bit unsigned integer, since the value is too large."); + integer *= 10; + integer += c - '0'; + } else { + return false; + } + } + + r_result = (uint64_t)integer; + return true; +} + +int godotsharp::SemVer::cmp(const godotsharp::SemVer &p_a, const godotsharp::SemVer &p_b) { + if (p_a.major != p_b.major) { + return p_a.major > p_b.major ? 1 : -1; + } + + if (p_a.minor != p_b.minor) { + return p_a.minor > p_b.minor ? 1 : -1; + } + + if (p_a.patch != p_b.patch) { + return p_a.patch > p_b.patch ? 1 : -1; + } + + if (p_a.prerelease.is_empty() && p_b.prerelease.is_empty()) { + return 0; + } + + if (p_a.prerelease.is_empty() || p_b.prerelease.is_empty()) { + return p_a.prerelease.is_empty() ? 1 : -1; + } + + if (p_a.prerelease != p_b.prerelease) { + // This could be optimized, but I'm too lazy + + Vector a_field_set = p_a.prerelease.split("."); + Vector b_field_set = p_b.prerelease.split("."); + + int a_field_count = a_field_set.size(); + int b_field_count = b_field_set.size(); + + int min_field_count = MIN(a_field_count, b_field_count); + + for (int i = 0; i < min_field_count; i++) { + const String &a_field = a_field_set[i]; + const String &b_field = b_field_set[i]; + + if (a_field == b_field) { + continue; + } + + uint64_t a_num; + bool a_is_digit_only = parse_digit_only_field(a_field, a_num); + + uint64_t b_num; + bool b_is_digit_only = parse_digit_only_field(b_field, b_num); + + if (a_is_digit_only && b_is_digit_only) { + // Identifiers consisting of only digits are compared numerically. + + if (a_num == b_num) { + continue; + } + + return a_num > b_num ? 1 : -1; + } + + if (a_is_digit_only || b_is_digit_only) { + // Numeric identifiers always have lower precedence than non-numeric identifiers. + return b_is_digit_only ? 1 : -1; + } + + // Identifiers with letters or hyphens are compared lexically in ASCII sort order. + return a_field > b_field ? 1 : -1; + } + + if (a_field_count != b_field_count) { + // A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal. + return a_field_count > b_field_count ? 1 : -1; + } + } + + return 0; +} + +bool godotsharp::SemVerParser::parse(const String &p_ver_text, godotsharp::SemVer &r_semver) { + if (!regex.is_valid() && regex.get_pattern().is_empty()) { + regex.compile("^(?P0|[1-9]\\d*)\\.(?P0|[1-9]\\d*)\\.(?P0|[1-9]\\d*)(?:-(?P(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?P[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + ERR_FAIL_COND_V(!regex.is_valid(), false); + } + + Ref match = regex.search(p_ver_text); + + if (match.is_valid()) { + r_semver = SemVer( + match->get_string("major").to_int(), + match->get_string("minor").to_int(), + match->get_string("patch").to_int(), + match->get_string("prerelease"), + match->get_string("buildmetadata")); + return true; + } + + return false; +} diff --git a/modules/mono/editor/semver.h b/modules/mono/editor/semver.h new file mode 100644 index 00000000000..48ea8b043e0 --- /dev/null +++ b/modules/mono/editor/semver.h @@ -0,0 +1,106 @@ +/*************************************************************************/ +/* semver.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef SEMVER_H +#define SEMVER_H + +#include "core/string/ustring.h" +#include "modules/regex/regex.h" + +// is included somewhere, which defines major(dev) to gnu_dev_major(dev) +#if defined(major) +#undef major +#endif +#if defined(minor) +#undef minor +#endif + +namespace godotsharp { + +struct SemVer { +private: + static bool parse_digit_only_field(const String &p_field, uint64_t &r_result); + + static int cmp(const SemVer &p_a, const SemVer &p_b); + +public: + int major = 0; + int minor = 0; + int patch = 0; + String prerelease; + String build_metadata; + + bool operator==(const SemVer &b) const { + return cmp(*this, b) == 0; + } + + bool operator!=(const SemVer &b) const { + return !operator==(b); + } + + bool operator<(const SemVer &b) const { + return cmp(*this, b) < 0; + } + + bool operator>(const SemVer &b) const { + return cmp(*this, b) > 0; + } + + bool operator<=(const SemVer &b) const { + return cmp(*this, b) <= 0; + } + + bool operator>=(const SemVer &b) const { + return cmp(*this, b) >= 0; + } + + SemVer() {} + + SemVer(int p_major, int p_minor, int p_patch, + const String &p_prerelease, const String &p_build_metadata) : + major(p_major), + minor(p_minor), + patch(p_patch), + prerelease(p_prerelease), + build_metadata(p_build_metadata) { + } +}; + +struct SemVerParser { +private: + RegEx regex; + +public: + bool parse(const String &p_ver_text, SemVer &r_semver); +}; + +} //namespace godotsharp + +#endif // SEMVER_H diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index e698e92d7a1..3b87d9248a0 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -43,12 +43,13 @@ #include "../utils/path_utils.h" #include "gd_mono_cache.h" +#include "../thirdparty/coreclr_delegates.h" +#include "../thirdparty/hostfxr.h" + #ifdef TOOLS_ENABLED -#include +#include "../editor/hostfxr_resolver.h" #endif -#include -#include #ifdef UNIX_ENABLED #include #endif @@ -88,85 +89,43 @@ HostFxrCharString str_to_hostfxr(const String &p_str) { #endif } -#ifdef TOOLS_ENABLED -String str_from_hostfxr(const char_t *p_buffer) { -#ifdef _WIN32 - return String::utf16((const char16_t *)p_buffer); -#else - return String::utf8((const char *)p_buffer); -#endif -} -#endif - const char_t *get_data(const HostFxrCharString &p_char_str) { return (const char_t *)p_char_str.get_data(); } -#ifdef TOOLS_ENABLED -String find_hostfxr(size_t p_known_buffer_size, get_hostfxr_parameters *p_get_hostfxr_params) { - // Pre-allocate a large buffer for the path to hostfxr - Vector buffer; - buffer.resize(p_known_buffer_size); - - int rc = get_hostfxr_path(buffer.ptrw(), &p_known_buffer_size, p_get_hostfxr_params); - - ERR_FAIL_COND_V_MSG(rc != 0, String(), "get_hostfxr_path failed with code: " + itos(rc)); - - return str_from_hostfxr(buffer.ptr()); -} -#endif - String find_hostfxr() { #ifdef TOOLS_ENABLED - const int CoreHostLibMissingFailure = 0x80008083; - const int HostApiBufferTooSmall = 0x80008098; - - size_t buffer_size = 0; - int rc = get_hostfxr_path(nullptr, &buffer_size, nullptr); - - if (rc == HostApiBufferTooSmall) { - return find_hostfxr(buffer_size, nullptr); + String dotnet_root; + String fxr_path; + if (godotsharp::hostfxr_resolver::try_get_path(dotnet_root, fxr_path)) { + return fxr_path; } - if (rc == CoreHostLibMissingFailure) { - // Apparently `get_hostfxr_path` doesn't look for dotnet in `PATH`? (I suppose it needs the - // `DOTNET_ROOT` environment variable). If it fails, we try to find the dotnet executable - // in `PATH` ourselves and pass its location as `dotnet_root` to `get_hostfxr_path`. - String dotnet_exe = path::find_executable("dotnet"); + // hostfxr_resolver doesn't look for dotnet in `PATH`. If it fails, we try to find the dotnet + // executable in `PATH` here and pass its location as `dotnet_root` to `get_hostfxr_path`. + String dotnet_exe = path::find_executable("dotnet"); - if (!dotnet_exe.is_empty()) { - // The file found in PATH may be a symlink - dotnet_exe = path::abspath(path::realpath(dotnet_exe)); + if (!dotnet_exe.is_empty()) { + // The file found in PATH may be a symlink + dotnet_exe = path::abspath(path::realpath(dotnet_exe)); - // TODO: - // Sometimes, the symlink may not point to the dotnet executable in the dotnet root. - // That's the case with snaps. The snap install should have been found with the - // previous `get_hostfxr_path`, but it would still be better to do this properly - // and use something like `dotnet --list-sdks/runtimes` to find the actual location. - // This way we could also check if the proper sdk or runtime is installed. This would - // allow us to fail gracefully and show some helpful information in the editor. + // TODO: + // Sometimes, the symlink may not point to the dotnet executable in the dotnet root. + // That's the case with snaps. The snap install should have been found with the + // previous `get_hostfxr_path`, but it would still be better to do this properly + // and use something like `dotnet --list-sdks/runtimes` to find the actual location. + // This way we could also check if the proper sdk or runtime is installed. This would + // allow us to fail gracefully and show some helpful information in the editor. - HostFxrCharString dotnet_root = str_to_hostfxr(dotnet_exe.get_base_dir()); - - get_hostfxr_parameters get_hostfxr_parameters = { - sizeof(get_hostfxr_parameters), - nullptr, - get_data(dotnet_root) - }; - - buffer_size = 0; - rc = get_hostfxr_path(nullptr, &buffer_size, &get_hostfxr_parameters); - if (rc == HostApiBufferTooSmall) { - return find_hostfxr(buffer_size, &get_hostfxr_parameters); - } + dotnet_root = dotnet_exe.get_base_dir(); + if (godotsharp::hostfxr_resolver::try_get_path_from_dotnet_root(dotnet_root, fxr_path)) { + return fxr_path; } } - if (rc == CoreHostLibMissingFailure) { - ERR_PRINT(String() + ".NET: One of the dependent libraries is missing. " + - "Typically when the `hostfxr`, `hostpolicy` or `coreclr` dynamic " + - "libraries are not present in the expected locations."); - } + ERR_PRINT(String() + ".NET: One of the dependent libraries is missing. " + + "Typically when the `hostfxr`, `hostpolicy` or `coreclr` dynamic " + + "libraries are not present in the expected locations."); return String(); #else diff --git a/modules/mono/thirdparty/coreclr_delegates.h b/modules/mono/thirdparty/coreclr_delegates.h new file mode 100644 index 00000000000..914ab592df9 --- /dev/null +++ b/modules/mono/thirdparty/coreclr_delegates.h @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __CORECLR_DELEGATES_H__ +#define __CORECLR_DELEGATES_H__ + +#include + +#if defined(_WIN32) + #define CORECLR_DELEGATE_CALLTYPE __stdcall + #ifdef _WCHAR_T_DEFINED + typedef wchar_t char_t; + #else + typedef unsigned short char_t; + #endif +#else + #define CORECLR_DELEGATE_CALLTYPE + typedef char char_t; +#endif + +#define UNMANAGEDCALLERSONLY_METHOD ((const char_t*)-1) + +// Signature of delegate returned by coreclr_delegate_type::load_assembly_and_get_function_pointer +typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_and_get_function_pointer_fn)( + const char_t *assembly_path /* Fully qualified path to assembly */, + const char_t *type_name /* Assembly qualified type name */, + const char_t *method_name /* Public static method name compatible with delegateType */, + const char_t *delegate_type_name /* Assembly qualified delegate type name or null + or UNMANAGEDCALLERSONLY_METHOD if the method is marked with + the UnmanagedCallersOnlyAttribute. */, + void *reserved /* Extensibility parameter (currently unused and must be 0) */, + /*out*/ void **delegate /* Pointer where to store the function pointer result */); + +// Signature of delegate returned by load_assembly_and_get_function_pointer_fn when delegate_type_name == null (default) +typedef int (CORECLR_DELEGATE_CALLTYPE *component_entry_point_fn)(void *arg, int32_t arg_size_in_bytes); + +typedef int (CORECLR_DELEGATE_CALLTYPE *get_function_pointer_fn)( + const char_t *type_name /* Assembly qualified type name */, + const char_t *method_name /* Public static method name compatible with delegateType */, + const char_t *delegate_type_name /* Assembly qualified delegate type name or null, + or UNMANAGEDCALLERSONLY_METHOD if the method is marked with + the UnmanagedCallersOnlyAttribute. */, + void *load_context /* Extensibility parameter (currently unused and must be 0) */, + void *reserved /* Extensibility parameter (currently unused and must be 0) */, + /*out*/ void **delegate /* Pointer where to store the function pointer result */); + +#endif // __CORECLR_DELEGATES_H__ diff --git a/modules/mono/thirdparty/hostfxr.h b/modules/mono/thirdparty/hostfxr.h new file mode 100644 index 00000000000..591a8ebbea5 --- /dev/null +++ b/modules/mono/thirdparty/hostfxr.h @@ -0,0 +1,323 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef __HOSTFXR_H__ +#define __HOSTFXR_H__ + +#include +#include + +#if defined(_WIN32) + #define HOSTFXR_CALLTYPE __cdecl + #ifdef _WCHAR_T_DEFINED + typedef wchar_t char_t; + #else + typedef unsigned short char_t; + #endif +#else + #define HOSTFXR_CALLTYPE + typedef char char_t; +#endif + +enum hostfxr_delegate_type +{ + hdt_com_activation, + hdt_load_in_memory_assembly, + hdt_winrt_activation, + hdt_com_register, + hdt_com_unregister, + hdt_load_assembly_and_get_function_pointer, + hdt_get_function_pointer, +}; + +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_fn)(const int argc, const char_t **argv); +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_startupinfo_fn)( + const int argc, + const char_t **argv, + const char_t *host_path, + const char_t *dotnet_root, + const char_t *app_path); +typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_bundle_startupinfo_fn)( + const int argc, + const char_t** argv, + const char_t* host_path, + const char_t* dotnet_root, + const char_t* app_path, + int64_t bundle_header_offset); + +typedef void(HOSTFXR_CALLTYPE *hostfxr_error_writer_fn)(const char_t *message); + +// +// Sets a callback which is to be used to write errors to. +// +// Parameters: +// error_writer +// A callback function which will be invoked every time an error is to be reported. +// Or nullptr to unregister previously registered callback and return to the default behavior. +// Return value: +// The previously registered callback (which is now unregistered), or nullptr if no previous callback +// was registered +// +// The error writer is registered per-thread, so the registration is thread-local. On each thread +// only one callback can be registered. Subsequent registrations overwrite the previous ones. +// +// By default no callback is registered in which case the errors are written to stderr. +// +// Each call to the error writer is sort of like writing a single line (the EOL character is omitted). +// Multiple calls to the error writer may occur for one failure. +// +// If the hostfxr invokes functions in hostpolicy as part of its operation, the error writer +// will be propagated to hostpolicy for the duration of the call. This means that errors from +// both hostfxr and hostpolicy will be reporter through the same error writer. +// +typedef hostfxr_error_writer_fn(HOSTFXR_CALLTYPE *hostfxr_set_error_writer_fn)(hostfxr_error_writer_fn error_writer); + +typedef void* hostfxr_handle; +struct hostfxr_initialize_parameters +{ + size_t size; + const char_t *host_path; + const char_t *dotnet_root; +}; + +// +// Initializes the hosting components for a dotnet command line running an application +// +// Parameters: +// argc +// Number of argv arguments +// argv +// Command-line arguments for running an application (as if through the dotnet executable). +// Only command-line arguments which are accepted by runtime installation are supported, SDK/CLI commands are not supported. +// For example 'app.dll app_argument_1 app_argument_2`. +// parameters +// Optional. Additional parameters for initialization +// host_context_handle +// On success, this will be populated with an opaque value representing the initialized host context +// +// Return value: +// Success - Hosting components were successfully initialized +// HostInvalidState - Hosting components are already initialized +// +// This function parses the specified command-line arguments to determine the application to run. It will +// then find the corresponding .runtimeconfig.json and .deps.json with which to resolve frameworks and +// dependencies and prepare everything needed to load the runtime. +// +// This function only supports arguments for running an application. It does not support SDK commands. +// +// This function does not load the runtime. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_dotnet_command_line_fn)( + int argc, + const char_t **argv, + const struct hostfxr_initialize_parameters *parameters, + /*out*/ hostfxr_handle *host_context_handle); + +// +// Initializes the hosting components using a .runtimeconfig.json file +// +// Parameters: +// runtime_config_path +// Path to the .runtimeconfig.json file +// parameters +// Optional. Additional parameters for initialization +// host_context_handle +// On success, this will be populated with an opaque value representing the initialized host context +// +// Return value: +// Success - Hosting components were successfully initialized +// Success_HostAlreadyInitialized - Config is compatible with already initialized hosting components +// Success_DifferentRuntimeProperties - Config has runtime properties that differ from already initialized hosting components +// CoreHostIncompatibleConfig - Config is incompatible with already initialized hosting components +// +// This function will process the .runtimeconfig.json to resolve frameworks and prepare everything needed +// to load the runtime. It will only process the .deps.json from frameworks (not any app/component that +// may be next to the .runtimeconfig.json). +// +// This function does not load the runtime. +// +// If called when the runtime has already been loaded, this function will check if the specified runtime +// config is compatible with the existing runtime. +// +// Both Success_HostAlreadyInitialized and Success_DifferentRuntimeProperties codes are considered successful +// initializations. In the case of Success_DifferentRuntimeProperties, it is left to the consumer to verify that +// the difference in properties is acceptable. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_runtime_config_fn)( + const char_t *runtime_config_path, + const struct hostfxr_initialize_parameters *parameters, + /*out*/ hostfxr_handle *host_context_handle); + +// +// Gets the runtime property value for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// name +// Runtime property name +// value +// Out parameter. Pointer to a buffer with the property value. +// +// Return value: +// The error code result. +// +// The buffer pointed to by value is owned by the host context. The lifetime of the buffer is only +// guaranteed until any of the below occur: +// - a 'run' method is called for the host context +// - properties are changed via hostfxr_set_runtime_property_value +// - the host context is closed via 'hostfxr_close' +// +// If host_context_handle is nullptr and an active host context exists, this function will get the +// property value for the active host context. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t *name, + /*out*/ const char_t **value); + +// +// Sets the value of a runtime property for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// name +// Runtime property name +// value +// Value to set +// +// Return value: +// The error code result. +// +// Setting properties is only supported for the first host context, before the runtime has been loaded. +// +// If the property already exists in the host context, it will be overwritten. If value is nullptr, the +// property will be removed. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_set_runtime_property_value_fn)( + const hostfxr_handle host_context_handle, + const char_t *name, + const char_t *value); + +// +// Gets all the runtime properties for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// count +// [in] Size of the keys and values buffers +// [out] Number of properties returned (size of keys/values buffers used). If the input value is too +// small or keys/values is nullptr, this is populated with the number of available properties +// keys +// Array of pointers to buffers with runtime property keys +// values +// Array of pointers to buffers with runtime property values +// +// Return value: +// The error code result. +// +// The buffers pointed to by keys and values are owned by the host context. The lifetime of the buffers is only +// guaranteed until any of the below occur: +// - a 'run' method is called for the host context +// - properties are changed via hostfxr_set_runtime_property_value +// - the host context is closed via 'hostfxr_close' +// +// If host_context_handle is nullptr and an active host context exists, this function will get the +// properties for the active host context. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_properties_fn)( + const hostfxr_handle host_context_handle, + /*inout*/ size_t * count, + /*out*/ const char_t **keys, + /*out*/ const char_t **values); + +// +// Load CoreCLR and run the application for an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// +// Return value: +// If the app was successfully run, the exit code of the application. Otherwise, the error code result. +// +// The host_context_handle must have been initialized using hostfxr_initialize_for_dotnet_command_line. +// +// This function will not return until the managed application exits. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_run_app_fn)(const hostfxr_handle host_context_handle); + +// +// Gets a typed delegate from the currently loaded CoreCLR or from a newly created one. +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// type +// Type of runtime delegate requested +// delegate +// An out parameter that will be assigned the delegate. +// +// Return value: +// The error code result. +// +// If the host_context_handle was initialized using hostfxr_initialize_for_runtime_config, +// then all delegate types are supported. +// If the host_context_handle was initialized using hostfxr_initialize_for_dotnet_command_line, +// then only the following delegate types are currently supported: +// hdt_load_assembly_and_get_function_pointer +// hdt_get_function_pointer +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_delegate_fn)( + const hostfxr_handle host_context_handle, + enum hostfxr_delegate_type type, + /*out*/ void **delegate); + +// +// Closes an initialized host context +// +// Parameters: +// host_context_handle +// Handle to the initialized host context +// +// Return value: +// The error code result. +// +typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_close_fn)(const hostfxr_handle host_context_handle); + +struct hostfxr_dotnet_environment_sdk_info +{ + size_t size; + const char_t* version; + const char_t* path; +}; + +typedef void(HOSTFXR_CALLTYPE* hostfxr_get_dotnet_environment_info_result_fn)( + const struct hostfxr_dotnet_environment_info* info, + void* result_context); + +struct hostfxr_dotnet_environment_framework_info +{ + size_t size; + const char_t* name; + const char_t* version; + const char_t* path; +}; + +struct hostfxr_dotnet_environment_info +{ + size_t size; + + const char_t* hostfxr_version; + const char_t* hostfxr_commit_hash; + + size_t sdk_count; + const struct hostfxr_dotnet_environment_sdk_info* sdks; + + size_t framework_count; + const struct hostfxr_dotnet_environment_framework_info* frameworks; +}; + +#endif //__HOSTFXR_H__