Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C#: Fallback to CoreCLR/MonoVM hosting APIs when hostfxr/NativeAOT fails #88803

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions editor/export/editor_export_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ bool EditorExportPlugin::supports_platform(const Ref<EditorExportPlatform> &p_ex
return ret;
}

PackedStringArray EditorExportPlugin::get_export_features(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const {
return _get_export_features(p_export_platform, p_debug);
}

PackedStringArray EditorExportPlugin::get_android_dependencies(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const {
PackedStringArray ret;
GDVIRTUAL_CALL(_get_android_dependencies, p_export_platform, p_debug, ret);
Expand Down
1 change: 1 addition & 0 deletions editor/export/editor_export_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class EditorExportPlugin : public RefCounted {
virtual String get_name() const;

virtual bool supports_platform(const Ref<EditorExportPlatform> &p_export_platform) const;
PackedStringArray get_export_features(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const;

virtual PackedStringArray get_android_dependencies(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const;
virtual PackedStringArray get_android_dependencies_maven_repos(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk">
<Link>Sdk\SdkPackageVersions.props</Link>
</None>
<None Include="Sdk\Android.props" Pack="true" PackagePath="Sdk" />
<None Include="Sdk\iOSNativeAOT.props" Pack="true" PackagePath="Sdk" />
<None Include="Sdk\iOSNativeAOT.targets" Pack="true" PackagePath="Sdk" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' and '$(PublishAot)' != 'true' ">true</UseMonoRuntime>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,6 @@
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
</PropertyGroup>

<Import Project="$(MSBuildThisFileDirectory)\Android.props" Condition=" '$(GodotTargetPlatform)' == 'android' " />
<Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " />
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ private void _ExportBeginImpl(string[] features, bool isDebug, string path, long
{
publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet",
$"{buildConfig}-{runtimeIdentifier}");

}

outputPaths.Add(publishOutputDir);
Expand Down Expand Up @@ -322,6 +321,30 @@ private void _ExportBeginImpl(string[] features, bool isDebug, string path, long
{
if (embedBuildResults)
{
if (platform == OS.Platforms.Android)
{
if (IsSharedObject(Path.GetFileName(path)))
{
AddSharedObject(path, tags: new string[] { arch },
Path.Join(projectDataDirName,
Path.GetRelativePath(publishOutputDir,
Path.GetDirectoryName(path)!)));

return;
}

static bool IsSharedObject(string fileName)
{
if (fileName.EndsWith(".so") || fileName.EndsWith(".a")
|| fileName.EndsWith(".jar") || fileName.EndsWith(".dex"))
{
return true;
}

return false;
}
}

string filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputDir, path));
byte[] fileData = File.ReadAllBytes(path);
string hash = Convert.ToBase64String(SHA512.HashData(fileData));
Expand Down
176 changes: 165 additions & 11 deletions modules/mono/mono_gd/gd_mono.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config =
hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr;
hostfxr_close_fn hostfxr_close = nullptr;

#ifndef TOOLS_ENABLED
typedef int(CORECLR_DELEGATE_CALLTYPE *coreclr_create_delegate_fn)(void *hostHandle, unsigned int domainId, const char *entryPointAssemblyName, const char *entryPointTypeName, const char *entryPointMethodName, void **delegate);
typedef int(CORECLR_DELEGATE_CALLTYPE *coreclr_initialize_fn)(const char *exePath, const char *appDomainFriendlyName, int propertyCount, const char **propertyKeys, const char **propertyValues, void **hostHandle, unsigned int *domainId);

coreclr_create_delegate_fn coreclr_create_delegate = nullptr;
coreclr_initialize_fn coreclr_initialize = nullptr;
#endif

#ifdef _WIN32
static_assert(sizeof(char_t) == sizeof(char16_t));
using HostFxrCharString = Char16String;
Expand Down Expand Up @@ -142,6 +150,56 @@ String find_hostfxr() {
#endif
}

#ifndef TOOLS_ENABLED
String find_monosgen() {
#if defined(ANDROID_ENABLED)
// Android includes all native libraries in the libs directory of the APK
// so we assume it exists and use only the name to dlopen it.
return "libmonosgen-2.0.so";
#else
#if defined(WINDOWS_ENABLED)
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
.path_join("monosgen-2.0.dll");
#elif defined(MACOS_ENABLED)
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
.path_join("libmonosgen-2.0.dylib");
#elif defined(UNIX_ENABLED)
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
.path_join("libmonosgen-2.0.so");
#else
#error "Platform not supported (yet?)"
#endif

if (FileAccess::exists(probe_path)) {
return probe_path;
}

return String();
#endif
}

String find_coreclr() {
#if defined(WINDOWS_ENABLED)
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
.path_join("coreclr.dll");
#elif defined(MACOS_ENABLED)
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
.path_join("libcoreclr.dylib");
#elif defined(UNIX_ENABLED)
String probe_path = GodotSharpDirs::get_api_assemblies_dir()
.path_join("libcoreclr.so");
#else
#error "Platform not supported (yet?)"
#endif

if (FileAccess::exists(probe_path)) {
return probe_path;
}

return String();
}
#endif

bool load_hostfxr(void *&r_hostfxr_dll_handle) {
String hostfxr_path = find_hostfxr();

Expand Down Expand Up @@ -182,6 +240,47 @@ bool load_hostfxr(void *&r_hostfxr_dll_handle) {
hostfxr_close);
}

#ifndef TOOLS_ENABLED
bool load_coreclr(void *&r_coreclr_dll_handle) {
String coreclr_path = find_coreclr();

bool is_monovm = false;
if (coreclr_path.is_empty()) {
// Fallback to MonoVM (should have the same API as CoreCLR).
coreclr_path = find_monosgen();
is_monovm = true;
}

if (coreclr_path.is_empty()) {
return false;
}

const String coreclr_name = is_monovm ? "monosgen" : "coreclr";
print_verbose("Found " + coreclr_name + ": " + coreclr_path);

Error err = OS::get_singleton()->open_dynamic_library(coreclr_path, r_coreclr_dll_handle);

if (err != OK) {
return false;
}

void *lib = r_coreclr_dll_handle;

void *symbol = nullptr;

err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_initialize", symbol);
ERR_FAIL_COND_V(err != OK, false);
coreclr_initialize = (coreclr_initialize_fn)symbol;

err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_create_delegate", symbol);
ERR_FAIL_COND_V(err != OK, false);
coreclr_create_delegate = (coreclr_create_delegate_fn)symbol;

return (coreclr_initialize &&
coreclr_create_delegate);
}
#endif

#ifdef TOOLS_ENABLED
load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) {
hostfxr_handle cxt = nullptr;
Expand Down Expand Up @@ -339,6 +438,56 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle)
}
#endif

#ifndef TOOLS_ENABLED
String make_tpa_list() {
String tpa_list;

#if defined(WINDOWS_ENABLED)
String separator = ";";
#else
String separator = ":";
#endif

String assemblies_dir = GodotSharpDirs::get_api_assemblies_dir();
PackedStringArray files = DirAccess::get_files_at(assemblies_dir);
for (const String &file : files) {
tpa_list += assemblies_dir.path_join(file);
tpa_list += separator;
}

return tpa_list;
}

godot_plugins_initialize_fn initialize_coreclr_and_godot_plugins(bool &r_runtime_initialized) {
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;

String assembly_name = path::get_csharp_project_name();

String tpa_list = make_tpa_list();
const char *prop_keys[] = { HOSTFXR_STR("TRUSTED_PLATFORM_ASSEMBLIES") };
const char *prop_values[] = { get_data(str_to_hostfxr(tpa_list)) };
int nprops = sizeof(prop_keys) / sizeof(prop_keys[0]);

void *coreclr_handle = nullptr;
unsigned int domain_id = 0;
int rc = coreclr_initialize(nullptr, nullptr, nprops, (const char **)&prop_keys, (const char **)&prop_values, &coreclr_handle, &domain_id);
ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to initialize CoreCLR.");

r_runtime_initialized = true;

print_verbose(".NET: CoreCLR initialized");

coreclr_create_delegate(coreclr_handle, domain_id,
get_data(str_to_hostfxr(assembly_name)),
HOSTFXR_STR("GodotPlugins.Game.Main"),
HOSTFXR_STR("InitializeFromGameProject"),
(void **)&godot_plugins_initialize);
ERR_FAIL_NULL_V_MSG(godot_plugins_initialize, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer");

return godot_plugins_initialize;
}
#endif

} // namespace

bool GDMono::should_initialize() {
Expand Down Expand Up @@ -382,14 +531,21 @@ void GDMono::initialize() {
}
#endif

if (!load_hostfxr(hostfxr_dll_handle)) {
if (load_hostfxr(hostfxr_dll_handle)) {
godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized);
ERR_FAIL_NULL(godot_plugins_initialize);
} else {
#if !defined(TOOLS_ENABLED)
godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle);

if (godot_plugins_initialize != nullptr) {
is_native_aot = true;
runtime_initialized = true;
if (load_coreclr(coreclr_dll_handle)) {
godot_plugins_initialize = initialize_coreclr_and_godot_plugins(runtime_initialized);
} else {
godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle);
if (godot_plugins_initialize != nullptr) {
runtime_initialized = true;
}
}

if (godot_plugins_initialize == nullptr) {
ERR_FAIL_MSG(".NET: Failed to load hostfxr");
}
#else
Expand All @@ -400,11 +556,6 @@ void GDMono::initialize() {
#endif
}

if (!is_native_aot) {
godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized);
ERR_FAIL_NULL(godot_plugins_initialize);
}

int32_t interop_funcs_size = 0;
const void **interop_funcs = godotsharp::get_runtime_interop_funcs(interop_funcs_size);

Expand Down Expand Up @@ -553,6 +704,9 @@ GDMono::~GDMono() {
if (hostfxr_dll_handle) {
OS::get_singleton()->close_dynamic_library(hostfxr_dll_handle);
}
if (coreclr_dll_handle) {
OS::get_singleton()->close_dynamic_library(coreclr_dll_handle);
}

finalizing_scripts_domain = false;
runtime_initialized = false;
Expand Down
2 changes: 1 addition & 1 deletion modules/mono/mono_gd/gd_mono.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class GDMono {
bool finalizing_scripts_domain = false;

void *hostfxr_dll_handle = nullptr;
bool is_native_aot = false;
void *coreclr_dll_handle = nullptr;

String project_assembly_path;
uint64_t project_assembly_modified_time = 0;
Expand Down
Binary file not shown.
24 changes: 10 additions & 14 deletions platform/android/export/export_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2381,19 +2381,6 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
#ifdef MODULE_MONO_ENABLED
// Android export is still a work in progress, keep a message as a warning.
err += TTR("Exporting to Android when using C#/.NET is experimental.") + "\n";

bool unsupported_arch = false;
Vector<ABI> enabled_abis = get_enabled_abis(p_preset);
for (ABI abi : enabled_abis) {
if (abi.arch != "arm64" && abi.arch != "x86_64") {
err += vformat(TTR("Android architecture %s not supported in C# projects."), abi.arch) + "\n";
unsupported_arch = true;
}
}
if (unsupported_arch) {
r_error = err;
return false;
}
#endif

// Look for export templates (first official, and if defined custom templates).
Expand Down Expand Up @@ -3201,6 +3188,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
PluginConfigAndroid::get_plugins_custom_maven_repos(enabled_plugins, android_dependencies_maven_repos);
#endif // DISABLE_DEPRECATED

bool has_dotnet_project = false;
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
for (int i = 0; i < export_plugins.size(); i++) {
if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) {
Expand All @@ -3218,6 +3206,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
PackedStringArray export_plugin_android_dependencies_maven_repos = export_plugins[i]->get_android_dependencies_maven_repos(Ref<EditorExportPlatform>(this), p_debug);
android_dependencies_maven_repos.append_array(export_plugin_android_dependencies_maven_repos);
}

PackedStringArray features = export_plugins[i]->get_export_features(Ref<EditorExportPlatform>(this), p_debug);
if (features.has("dotnet")) {
has_dotnet_project = true;
}
}

bool clean_build_required = _is_clean_build_required(p_preset);
Expand All @@ -3231,12 +3224,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
cmdline.push_back("clean");
}

String edition = has_dotnet_project ? "Mono" : "Standard";
String build_type = p_debug ? "Debug" : "Release";
if (export_format == EXPORT_FORMAT_AAB) {
String bundle_build_command = vformat("bundle%s", build_type);
cmdline.push_back(bundle_build_command);
} else if (export_format == EXPORT_FORMAT_APK) {
String apk_build_command = vformat("assemble%s", build_type);
String apk_build_command = vformat("assemble%s%s", edition, build_type);
cmdline.push_back(apk_build_command);
}

Expand Down Expand Up @@ -3319,6 +3313,8 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
copy_args.push_back("-p"); // argument to specify the start directory.
copy_args.push_back(build_path); // start directory.

copy_args.push_back("-Pexport_edition=" + edition.to_lower());

copy_args.push_back("-Pexport_build_type=" + build_type.to_lower());

String export_format_arg = export_format == EXPORT_FORMAT_AAB ? "aab" : "apk";
Expand Down
Loading
Loading