diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index 00b211d2ece2..0e7dbf8e2be1 100644 --- a/.github/workflows/android_builds.yml +++ b/.github/workflows/android_builds.yml @@ -85,7 +85,7 @@ jobs: run: | cd platform/android/java ./gradlew generateGodotEditor - ./gradlew generateGodotMetaEditor + ./gradlew generateGodotHorizonOSEditor cd ../../.. ls -l bin/android_editor_builds/ diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index efb42288a57d..f96c9c5a8e15 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -524,15 +524,18 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { // Touchscreen bool has_touchscreen_ui = DisplayServer::get_singleton()->is_touchscreen_available(); - EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/increase_scrollbar_touch_area", has_touchscreen_ui, "") - set_restart_if_changed("interface/touchscreen/increase_scrollbar_touch_area", true); - EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_long_press_as_right_click", has_touchscreen_ui, "") - set_restart_if_changed("interface/touchscreen/enable_long_press_as_right_click", true); EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_pan_and_scale_gestures", has_touchscreen_ui, "") set_restart_if_changed("interface/touchscreen/enable_pan_and_scale_gestures", true); EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/touchscreen/scale_gizmo_handles", has_touchscreen_ui ? 3 : 1, "1,5,1") set_restart_if_changed("interface/touchscreen/scale_gizmo_handles", true); + // Disable some touchscreen settings by default for the XR Editor. + bool is_native_touchscreen = has_touchscreen_ui && !OS::get_singleton()->has_feature("xr_editor"); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/enable_long_press_as_right_click", is_native_touchscreen, "") + set_restart_if_changed("interface/touchscreen/enable_long_press_as_right_click", true); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/touchscreen/increase_scrollbar_touch_area", is_native_touchscreen, "") + set_restart_if_changed("interface/touchscreen/increase_scrollbar_touch_area", true); + // Scene tabs EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/scene_tabs/display_close_button", 1, "Never,If Tab Active,Always"); // TabBar::CloseButtonDisplayPolicy _initial_set("interface/scene_tabs/show_thumbnail_on_hover", true); diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index 28d0750d5a98..1c5aebbf3a90 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -221,6 +221,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 56eea8501092..3b6e11382549 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -165,6 +165,7 @@ class EditorExportPlugin : public RefCounted { 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 cad76d068902..ed2871cef526 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 000000000000..3926a4b22a30 --- /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 c4034f1f9f55..d10f9ae0ab04 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 d3720dcb727e..65fc7fca3071 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -240,7 +240,6 @@ private void _ExportBeginImpl(string[] features, bool isDebug, string path, long { publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet", $"{buildConfig}-{runtimeIdentifier}"); - } outputPaths.Add(publishOutputDir); @@ -317,6 +316,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)); diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 48caae8523cc..5293c5c896b3 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 614bfc63fb6e..fae3421ac9ec 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 000000000000..736603088187 Binary files /dev/null and b/modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar differ diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index a4052b8b7523..92977c14f33d 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 611a9c4a4022..597a4d5c1456 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 1098eacb0caa..104dbe262064 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 @@ import app.blazium.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 b63fa7f20af9..fbd9f9193499 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -25,11 +25,12 @@ allprojects { ext { supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"] supportedFlavors = ["editor", "template"] - supportedEditorVendors = ["google", "meta"] + supportedAndroidDistributions = ["android", "horizonos"] supportedFlavorsBuildTypes = [ "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 Blazium gradle build template") } @@ -94,17 +95,24 @@ def templateExcludedBuildTask() { /** * Generates the build tasks for the given flavor * @param flavor Must be one of the supported flavors ('template' / 'editor') - * @param editorVendor Must be one of the supported editor vendors ('google' / 'meta') + * @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 editorVendor = "google") { +def generateBuildTasks(String flavor = "template", String edition = "standard", String androidDistro = "android") { if (!supportedFlavors.contains(flavor)) { throw new GradleException("Invalid build flavor: $flavor") } - if (!supportedEditorVendors.contains(editorVendor)) { - throw new GradleException("Invalid editor vendor: $editorVendor") + 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 capitalizedEditorVendor = editorVendor.capitalize() + String capitalizedAndroidDistro = androidDistro.capitalize() def buildTasks = [] // Only build the binary files for which we have native shared libraries unless we intend @@ -126,6 +134,7 @@ def generateBuildTasks(String flavor = "template", String editorVendor = "google && 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,41 +166,42 @@ def generateBuildTasks(String flavor = "template", String editorVendor = "google // 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 { // Copy the generated editor apk to the bin directory. - String copyEditorApkTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}ApkToBin" + String copyEditorApkTaskName = "copyEditor${capitalizedAndroidDistro}${capitalizedTarget}ApkToBin" if (tasks.findByName(copyEditorApkTaskName) != null) { buildTasks += tasks.getByName(copyEditorApkTaskName) } else { buildTasks += tasks.create(name: copyEditorApkTaskName, type: Copy) { - dependsOn ":editor:assemble${capitalizedEditorVendor}${capitalizedTarget}" - from("editor/build/outputs/apk/${editorVendor}/${target}") + dependsOn ":editor:assemble${capitalizedAndroidDistro}${capitalizedTarget}" + from("editor/build/outputs/apk/${androidDistro}/${target}") into(androidEditorBuildsDir) - include("android_editor-${editorVendor}-${target}*.apk") + include("android_editor-${androidDistro}-${target}*.apk") } } // Copy the generated editor aab to the bin directory. - String copyEditorAabTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}AabToBin" + String copyEditorAabTaskName = "copyEditor${capitalizedAndroidDistro}${capitalizedTarget}AabToBin" if (tasks.findByName(copyEditorAabTaskName) != null) { buildTasks += tasks.getByName(copyEditorAabTaskName) } else { buildTasks += tasks.create(name: copyEditorAabTaskName, type: Copy) { - dependsOn ":editor:bundle${capitalizedEditorVendor}${capitalizedTarget}" - from("editor/build/outputs/bundle/${editorVendor}${capitalizedTarget}") + dependsOn ":editor:bundle${capitalizedAndroidDistro}${capitalizedTarget}" + from("editor/build/outputs/bundle/${androidDistro}${capitalizedTarget}") into(androidEditorBuildsDir) - include("android_editor-${editorVendor}-${target}*.aab") + include("android_editor-${androidDistro}-${target}*.aab") } } } @@ -204,7 +214,7 @@ def generateBuildTasks(String flavor = "template", String editorVendor = "google } /** - * Generate the Godot Editor Android binaries. + * Generate the Godot Editor binaries for Android devices. * * Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries * must have been generated (via scons) prior to running this gradle task. @@ -212,19 +222,19 @@ def generateBuildTasks(String flavor = "template", String editorVendor = "google */ task generateGodotEditor { gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() - dependsOn = generateBuildTasks("editor", "google") + dependsOn = generateBuildTasks("editor", "standard", "android") } /** - * Generate the Godot Editor Android binaries for Meta devices. + * Generate the Godot Editor binaries for HorizonOS devices. * * Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries * must have been generated (via scons) prior to running this gradle task. * The task will only build the binaries for which the shared libraries is available. */ -task generateGodotMetaEditor { +task generateGodotHorizonOSEditor { gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() - dependsOn = generateBuildTasks("editor", "meta") + 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") diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index 74feb4fbdbbd..7e53106b339d 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -145,14 +145,14 @@ android { } } - flavorDimensions = ["vendor"] + flavorDimensions = ["android_distribution"] productFlavors { - google { - dimension "vendor" + android { + dimension "android_distribution" missingDimensionStrategy 'products', 'editor' } - meta { - dimension "vendor" + horizonos { + dimension "android_distribution" missingDimensionStrategy 'products', 'editor' ndk { //noinspection ChromeOsAbiSupport @@ -176,5 +176,5 @@ dependencies { implementation "org.bouncycastle:bcprov-jdk15to18:1.77" // Meta dependencies - metaImplementation "org.godotengine:godot-openxr-vendors-meta:3.0.0-stable" + horizonosImplementation "org.godotengine:godot-openxr-vendors-meta:3.0.0-stable" } diff --git a/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/android/java/org/godotengine/editor/GodotEditor.kt similarity index 100% rename from platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt rename to platform/android/java/editor/src/android/java/org/godotengine/editor/GodotEditor.kt diff --git a/platform/android/java/editor/src/meta/AndroidManifest.xml b/platform/android/java/editor/src/horizonos/AndroidManifest.xml similarity index 100% rename from platform/android/java/editor/src/meta/AndroidManifest.xml rename to platform/android/java/editor/src/horizonos/AndroidManifest.xml diff --git a/platform/android/java/editor/src/meta/assets/vr_splash.png b/platform/android/java/editor/src/horizonos/assets/vr_splash.png similarity index 100% rename from platform/android/java/editor/src/meta/assets/vr_splash.png rename to platform/android/java/editor/src/horizonos/assets/vr_splash.png diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotEditor.kt similarity index 99% rename from platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt rename to platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotEditor.kt index 1c05820f0f20..c8b81c4557ac 100644 --- a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotEditor.kt @@ -36,7 +36,7 @@ import app.blazium.godot.utils.isNativeXRDevice /** * Primary window of the Godot Editor. * - * This is the implementation of the editor used when running on Meta devices. + * This is the implementation of the editor used when running on HorizonOS devices. */ open class GodotEditor : BaseGodotEditor() { diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotXRGame.kt similarity index 100% rename from platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt rename to platform/android/java/editor/src/horizonos/java/org/godotengine/editor/GodotXRGame.kt diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt index 6b32c05233f2..a468c32c90fa 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt @@ -510,4 +510,16 @@ abstract class BaseGodotEditor : GodotActivity() { val godot = godot ?: return Error.ERR_UNCONFIGURED return verifyApk(godot.fileAccessHandler, apkPath) } + + override fun supportsFeature(featureTag: String): Boolean { + if (featureTag == "xr_editor") { + return isNativeXRDevice(); + } + + if (featureTag == "horizonos") { + return isHorizonOSDevice() + } + + return false + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 4e0ee85773df..4b88a42fd122 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -1004,6 +1004,10 @@ class Godot(private val context: Context) { */ @Keep private fun hasFeature(feature: String): Boolean { + if (primaryHost?.supportsFeature(feature) ?: false) { + return true; + } + for (plugin in pluginRegistry.allPlugins) { if (plugin.supportsFeature(feature)) { return true diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index 7721b13a64e4..ccea1f5f16e5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -501,4 +501,12 @@ public Error verifyApk(@NonNull String apkPath) { } return Error.ERR_UNAVAILABLE; } + + @Override + public boolean supportsFeature(String featureTag) { + if (parentHost != null) { + return parentHost.supportsFeature(featureTag); + } + return false; + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java index 7ebe69aa19ef..2d04512ad57d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotHost.java @@ -136,4 +136,13 @@ default Error signApk(@NonNull String inputPath, @NonNull String outputPath, @No default Error verifyApk(@NonNull String apkPath) { return Error.ERR_UNAVAILABLE; } + + /** + * Returns whether the given feature tag is supported. + * + * @see Feature tags + */ + default boolean supportsFeature(String featureTag) { + return false; + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt index 112db75914e4..a830277724ce 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt @@ -38,7 +38,7 @@ package app.blazium.godot.utils import android.os.Build /** - * Returns true if running on Meta's Horizon OS. + * Returns true if running on Meta Horizon OS. */ fun isHorizonOSDevice(): Boolean { return "Oculus".equals(Build.BRAND, true)