From 0aa46e19c5a1864454451891fb3f40f5ef3ff742 Mon Sep 17 00:00:00 2001 From: Raul Santos Date: Fri, 9 Feb 2024 17:26:53 +0100 Subject: [PATCH] C#: Fallback to CoreCLR/MonoVM hosting APIs when hostfxr/NativeAOT fails Some platforms don't support hostfxr but we can use the coreclr/monosgen library directly to initialize the runtime. Android exports now use the `android` runtime identifier instead of `linux-bionic`, this removes the restrictions we previously had: - Adds support for all Android architectures (arm32, arm64, x32, and x64), previously only the 64-bit architectures were supported. - Loads `System.Security.Cryptography.Native.Android` (the .NET library that binds to the Android OS crypto functions). --- editor/export/editor_export_plugin.cpp | 4 + editor/export/editor_export_plugin.h | 1 + .../Godot.NET.Sdk/Godot.NET.Sdk.csproj | 1 + .../Godot.NET.Sdk/Sdk/Android.props | 5 + .../Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props | 1 + .../GodotTools/Export/ExportPlugin.cs | 25 ++- modules/mono/mono_gd/gd_mono.cpp | 176 ++++++++++++++++-- modules/mono/mono_gd/gd_mono.h | 2 +- ...m.Security.Cryptography.Native.Android.jar | Bin 0 -> 8552 bytes platform/android/export/export_plugin.cpp | 24 +-- platform/android/java/app/build.gradle | 40 +++- platform/android/java/app/config.gradle | 8 + .../java/app/src/com/godot/game/GodotApp.java | 15 ++ platform/android/java/build.gradle | 40 +++- 14 files changed, 301 insertions(+), 41 deletions(-) create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props create mode 100755 modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index 3f1b8aa8634..5945c024135 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -229,6 +229,10 @@ bool EditorExportPlugin::supports_platform(const Ref &p_ex return ret; } +PackedStringArray EditorExportPlugin::get_export_features(const Ref &p_export_platform, bool p_debug) const { + return _get_export_features(p_export_platform, p_debug); +} + PackedStringArray EditorExportPlugin::get_android_dependencies(const Ref &p_export_platform, bool p_debug) const { PackedStringArray ret; GDVIRTUAL_CALL(_get_android_dependencies, p_export_platform, p_debug, ret); diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index ae186d4425c..4c0107af721 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -166,6 +166,7 @@ public: virtual String get_name() const; virtual bool supports_platform(const Ref &p_export_platform) const; + PackedStringArray get_export_features(const Ref &p_export_platform, bool p_debug) const; virtual PackedStringArray get_android_dependencies(const Ref &p_export_platform, bool p_debug) const; virtual PackedStringArray get_android_dependencies_maven_repos(const Ref &p_export_platform, bool p_debug) const; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index ee624a443d9..c5f2dfee4b3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -30,6 +30,7 @@ Sdk\SdkPackageVersions.props + diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props new file mode 100644 index 00000000000..3926a4b22a3 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props @@ -0,0 +1,5 @@ + + + true + + diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index c4034f1f9f5..d10f9ae0ab0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -112,5 +112,6 @@ $(GodotDefineConstants);$(DefineConstants) + diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index a5f24fb67bf..6fd84d38349 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -245,7 +245,6 @@ namespace GodotTools.Export { publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet", $"{buildConfig}-{runtimeIdentifier}"); - } outputPaths.Add(publishOutputDir); @@ -322,6 +321,30 @@ namespace GodotTools.Export { 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)); diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 48caae8523c..5293c5c896b 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -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; @@ -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(); @@ -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; @@ -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() { @@ -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 @@ -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); @@ -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; diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 614bfc63fb6..fae3421ac9e 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -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; diff --git a/modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar b/modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar new file mode 100755 index 0000000000000000000000000000000000000000..736603088187993cf62014b17e619502133dc211 GIT binary patch literal 8552 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 I$o{(fAB|b+00000 literal 0 HcmV?d00001 diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index fd073245574..e3d9807af79 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2381,19 +2381,6 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref 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). @@ -3201,6 +3188,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref> export_plugins = EditorExport::get_singleton()->get_export_plugins(); for (int i = 0; i < export_plugins.size(); i++) { if (export_plugins[i]->supports_platform(Ref(this))) { @@ -3218,6 +3206,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget_android_dependencies_maven_repos(Ref(this), p_debug); android_dependencies_maven_repos.append_array(export_plugin_android_dependencies_maven_repos); } + + PackedStringArray features = export_plugins[i]->get_export_features(Ref(this), p_debug); + if (features.has("dotnet")) { + has_dotnet_project = true; + } } bool clean_build_required = _is_clean_build_required(p_preset); @@ -3231,12 +3224,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref 0) { implementation files(pluginsBinaries) } + + // .NET dependencies + String jar = '../../../../modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar' + if (file(jar).exists()) { + monoImplementation files(jar) + } } android { @@ -155,6 +163,10 @@ android { } } + buildFeatures { + buildConfig = true + } + buildTypes { debug { @@ -192,6 +204,13 @@ android { } } + flavorDimensions 'edition' + + productFlavors { + standard {} + mono {} + } + sourceSets { main { manifest.srcFile 'AndroidManifest.xml' @@ -207,7 +226,8 @@ android { applicationVariants.all { variant -> variant.outputs.all { output -> - output.outputFileName = "android_${variant.name}.apk" + String filenameSuffix = variant.flavorName == "mono" ? variant.name : variant.buildType.name + output.outputFileName = "android_${filenameSuffix}.apk" } } } @@ -220,12 +240,20 @@ task copyAndRenameBinary(type: Copy) { String exportPath = getExportPath() String exportFilename = getExportFilename() + String exportEdition = getExportEdition() String exportBuildType = getExportBuildType() + String exportBuildTypeCapitalized = exportBuildType.capitalize() String exportFormat = getExportFormat() boolean isAab = exportFormat == "aab" - String sourceFilepath = isAab ? "$buildDir/outputs/bundle/$exportBuildType/build-${exportBuildType}.aab" : "$buildDir/outputs/apk/$exportBuildType/android_${exportBuildType}.apk" - String sourceFilename = isAab ? "build-${exportBuildType}.aab" : "android_${exportBuildType}.apk" + boolean isMono = exportEdition == "mono" + String filenameSuffix = exportBuildType + if (isMono) { + filenameSuffix = isAab ? "${exportEdition}-${exportBuildType}" : "${exportEdition}${exportBuildTypeCapitalized}" + } + + String sourceFilename = isAab ? "build-${filenameSuffix}.aab" : "android_${filenameSuffix}.apk" + String sourceFilepath = isAab ? "$buildDir/outputs/bundle/${exportEdition}${exportBuildTypeCapitalized}/$sourceFilename" : "$buildDir/outputs/apk/$exportEdition/$exportBuildType/$sourceFilename" from sourceFilepath into exportPath diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 611a9c4a402..597a4d5c145 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -224,6 +224,14 @@ ext.getExportFilename = { return exportFilename } +ext.getExportEdition = { + String exportEdition = project.hasProperty("export_edition") ? project.property("export_edition") : "" + if (exportEdition == null || exportEdition.isEmpty()) { + exportEdition = "standard" + } + return exportEdition +} + ext.getExportBuildType = { String exportBuildType = project.hasProperty("export_build_type") ? project.property("export_build_type") : "" if (exportBuildType == null || exportBuildType.isEmpty()) { diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java index 22e617f6e73..9d4991e1200 100644 --- a/platform/android/java/app/src/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/com/godot/game/GodotApp.java @@ -33,14 +33,29 @@ package com.godot.game; import org.godotengine.godot.GodotActivity; import android.os.Bundle; +import android.util.Log; import androidx.core.splashscreen.SplashScreen; +import com.godot.game.BuildConfig; + /** * Template activity for Godot Android builds. * Feel free to extend and modify this class for your custom logic. */ public class GodotApp extends GodotActivity { + static { + // .NET libraries. + if (BuildConfig.FLAVOR.equals("mono")) { + try { + Log.v("GODOT", "Loading System.Security.Cryptography.Native.Android library"); + System.loadLibrary("System.Security.Cryptography.Native.Android"); + } catch (UnsatisfiedLinkError e) { + Log.e("GODOT", "Unable to load System.Security.Cryptography.Native.Android library"); + } + } + } + @Override public void onCreate(Bundle savedInstanceState) { SplashScreen.installSplashScreen(this); diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index d60f97e3e79..9184e8c5d5b 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -30,6 +30,7 @@ ext { "editor": ["dev", "debug", "release"], "template": ["dev", "debug", "release"] ] + supportedEditions = ["standard", "mono"] // Used by gradle to specify which architecture to build for by default when running // `./gradlew build` (this command is usually used by Android Studio). @@ -53,7 +54,7 @@ def getSconsTaskName(String flavor, String buildType, String abi) { * The zip file also includes some gradle tools to enable gradle builds from the Godot Editor. */ task zipGradleBuild(type: Zip) { - onlyIf { generateGodotTemplates.state.executed || generateDevTemplate.state.executed } + onlyIf { generateGodotTemplates.state.executed || generateGodotMonoTemplates.state.executed || generateDevTemplate.state.executed } doFirst { logger.lifecycle("Generating Godot gradle build template") } @@ -94,15 +95,22 @@ def templateExcludedBuildTask() { /** * Generates the build tasks for the given flavor * @param flavor Must be one of the supported flavors ('template' / 'editor') + * @param edition Must be one of the supported editions ('standard' / 'mono') * @param androidDistro Must be one of the supported Android distributions ('android' / 'horizonos') */ -def generateBuildTasks(String flavor = "template", String androidDistro = "android") { +def generateBuildTasks(String flavor = "template", String edition = "standard", String androidDistro = "android") { if (!supportedFlavors.contains(flavor)) { throw new GradleException("Invalid build flavor: $flavor") } if (!supportedAndroidDistributions.contains(androidDistro)) { throw new GradleException("Invalid Android distribution: $androidDistro") } + if (!supportedEditions.contains(edition)) { + throw new GradleException("Invalid build edition: $edition") + } + if (edition == "mono" && flavor != "template") { + throw new GradleException("'mono' edition only supports the 'template' flavor.") + } String capitalizedAndroidDistro = androidDistro.capitalize() def buildTasks = [] @@ -126,6 +134,7 @@ def generateBuildTasks(String flavor = "template", String androidDistro = "andro && targetLibs.listFiles().length > 0)) { String capitalizedTarget = target.capitalize() + String capitalizedEdition = edition.capitalize() if (isTemplate) { // Copy the Godot android library archive file into the app module libs directory. // Depends on the library build task to ensure the AAR file is generated prior to copying. @@ -157,15 +166,16 @@ def generateBuildTasks(String flavor = "template", String androidDistro = "andro // Copy the generated binary template into the Godot bin directory. // Depends on the app build task to ensure the binary is generated prior to copying. - String copyBinaryTaskName = "copy${capitalizedTarget}BinaryToBin" + String copyBinaryTaskName = "copy${capitalizedEdition}${capitalizedTarget}BinaryToBin" if (tasks.findByName(copyBinaryTaskName) != null) { buildTasks += tasks.getByName(copyBinaryTaskName) } else { buildTasks += tasks.create(name: copyBinaryTaskName, type: Copy) { - dependsOn ":app:assemble${capitalizedTarget}" - from("app/build/outputs/apk/${target}") + String filenameSuffix = edition == "mono" ? "${edition}${capitalizedTarget}" : target + dependsOn ":app:assemble${capitalizedEdition}${capitalizedTarget}" + from("app/build/outputs/apk/${edition}/${target}") into(binDir) - include("android_${target}.apk") + include("android_${filenameSuffix}.apk") } } } else { @@ -212,7 +222,7 @@ def generateBuildTasks(String flavor = "template", String androidDistro = "andro */ task generateGodotEditor { gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() - dependsOn = generateBuildTasks("editor", "android") + dependsOn = generateBuildTasks("editor", "standard", "android") } /** @@ -224,7 +234,7 @@ task generateGodotEditor { */ task generateGodotHorizonOSEditor { gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() - dependsOn = generateBuildTasks("editor", "horizonos") + dependsOn = generateBuildTasks("editor", "standard", "horizonos") } /** @@ -237,6 +247,17 @@ task generateGodotTemplates { finalizedBy 'zipGradleBuild' } +/** + * Master task used to coordinate the tasks defined above to generate the set of Godot templates + * for the 'mono' edition of the engine. + */ +task generateGodotMonoTemplates { + gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() + dependsOn = generateBuildTasks("template", "mono") + + finalizedBy 'zipGradleBuild' +} + /** * Generates the same output as generateGodotTemplates but with dev symbols */ @@ -295,6 +316,9 @@ task cleanGodotTemplates(type: Delete) { delete("$binDir/android_debug.apk") delete("$binDir/android_dev.apk") delete("$binDir/android_release.apk") + delete("$binDir/android_monoDebug.apk") + delete("$binDir/android_monoDev.apk") + delete("$binDir/android_monoRelease.apk") delete("$binDir/android_source.zip") delete("$binDir/godot-lib.template_debug.aar") delete("$binDir/godot-lib.template_debug.dev.aar")