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).
This commit is contained in:
parent
a75bacebef
commit
0aa46e19c5
14 changed files with 301 additions and 41 deletions
|
@ -229,6 +229,10 @@ bool EditorExportPlugin::supports_platform(const Ref<EditorExportPlatform> &p_ex
|
||||||
return ret;
|
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 EditorExportPlugin::get_android_dependencies(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const {
|
||||||
PackedStringArray ret;
|
PackedStringArray ret;
|
||||||
GDVIRTUAL_CALL(_get_android_dependencies, p_export_platform, p_debug, ret);
|
GDVIRTUAL_CALL(_get_android_dependencies, p_export_platform, p_debug, ret);
|
||||||
|
|
|
@ -166,6 +166,7 @@ public:
|
||||||
virtual String get_name() const;
|
virtual String get_name() const;
|
||||||
|
|
||||||
virtual bool supports_platform(const Ref<EditorExportPlatform> &p_export_platform) 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(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;
|
virtual PackedStringArray get_android_dependencies_maven_repos(const Ref<EditorExportPlatform> &p_export_platform, bool p_debug) const;
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
<None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk">
|
<None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk">
|
||||||
<Link>Sdk\SdkPackageVersions.props</Link>
|
<Link>Sdk\SdkPackageVersions.props</Link>
|
||||||
</None>
|
</None>
|
||||||
|
<None Include="Sdk\Android.props" Pack="true" PackagePath="Sdk" />
|
||||||
<None Include="Sdk\iOSNativeAOT.props" Pack="true" PackagePath="Sdk" />
|
<None Include="Sdk\iOSNativeAOT.props" Pack="true" PackagePath="Sdk" />
|
||||||
<None Include="Sdk\iOSNativeAOT.targets" Pack="true" PackagePath="Sdk" />
|
<None Include="Sdk\iOSNativeAOT.targets" Pack="true" PackagePath="Sdk" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' and '$(PublishAot)' != 'true' ">true</UseMonoRuntime>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
|
@ -112,5 +112,6 @@
|
||||||
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
|
<DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Import Project="$(MSBuildThisFileDirectory)\Android.props" Condition=" '$(GodotTargetPlatform)' == 'android' " />
|
||||||
<Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " />
|
<Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " />
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -245,7 +245,6 @@ namespace GodotTools.Export
|
||||||
{
|
{
|
||||||
publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet",
|
publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet",
|
||||||
$"{buildConfig}-{runtimeIdentifier}");
|
$"{buildConfig}-{runtimeIdentifier}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outputPaths.Add(publishOutputDir);
|
outputPaths.Add(publishOutputDir);
|
||||||
|
@ -322,6 +321,30 @@ namespace GodotTools.Export
|
||||||
{
|
{
|
||||||
if (embedBuildResults)
|
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));
|
string filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputDir, path));
|
||||||
byte[] fileData = File.ReadAllBytes(path);
|
byte[] fileData = File.ReadAllBytes(path);
|
||||||
string hash = Convert.ToBase64String(SHA512.HashData(fileData));
|
string hash = Convert.ToBase64String(SHA512.HashData(fileData));
|
||||||
|
|
|
@ -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_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr;
|
||||||
hostfxr_close_fn hostfxr_close = 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
|
#ifdef _WIN32
|
||||||
static_assert(sizeof(char_t) == sizeof(char16_t));
|
static_assert(sizeof(char_t) == sizeof(char16_t));
|
||||||
using HostFxrCharString = Char16String;
|
using HostFxrCharString = Char16String;
|
||||||
|
@ -142,6 +150,56 @@ String find_hostfxr() {
|
||||||
#endif
|
#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) {
|
bool load_hostfxr(void *&r_hostfxr_dll_handle) {
|
||||||
String hostfxr_path = find_hostfxr();
|
String hostfxr_path = find_hostfxr();
|
||||||
|
|
||||||
|
@ -182,6 +240,47 @@ bool load_hostfxr(void *&r_hostfxr_dll_handle) {
|
||||||
hostfxr_close);
|
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
|
#ifdef TOOLS_ENABLED
|
||||||
load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) {
|
load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) {
|
||||||
hostfxr_handle cxt = nullptr;
|
hostfxr_handle cxt = nullptr;
|
||||||
|
@ -339,6 +438,56 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle)
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
} // namespace
|
||||||
|
|
||||||
bool GDMono::should_initialize() {
|
bool GDMono::should_initialize() {
|
||||||
|
@ -382,14 +531,21 @@ void GDMono::initialize() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!load_hostfxr(hostfxr_dll_handle)) {
|
if (load_hostfxr(hostfxr_dll_handle)) {
|
||||||
#if !defined(TOOLS_ENABLED)
|
godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized);
|
||||||
godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle);
|
ERR_FAIL_NULL(godot_plugins_initialize);
|
||||||
|
|
||||||
if (godot_plugins_initialize != nullptr) {
|
|
||||||
is_native_aot = true;
|
|
||||||
runtime_initialized = true;
|
|
||||||
} else {
|
} else {
|
||||||
|
#if !defined(TOOLS_ENABLED)
|
||||||
|
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");
|
ERR_FAIL_MSG(".NET: Failed to load hostfxr");
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -400,11 +556,6 @@ void GDMono::initialize() {
|
||||||
#endif
|
#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;
|
int32_t interop_funcs_size = 0;
|
||||||
const void **interop_funcs = godotsharp::get_runtime_interop_funcs(interop_funcs_size);
|
const void **interop_funcs = godotsharp::get_runtime_interop_funcs(interop_funcs_size);
|
||||||
|
|
||||||
|
@ -553,6 +704,9 @@ GDMono::~GDMono() {
|
||||||
if (hostfxr_dll_handle) {
|
if (hostfxr_dll_handle) {
|
||||||
OS::get_singleton()->close_dynamic_library(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;
|
finalizing_scripts_domain = false;
|
||||||
runtime_initialized = false;
|
runtime_initialized = false;
|
||||||
|
|
|
@ -64,7 +64,7 @@ class GDMono {
|
||||||
bool finalizing_scripts_domain = false;
|
bool finalizing_scripts_domain = false;
|
||||||
|
|
||||||
void *hostfxr_dll_handle = nullptr;
|
void *hostfxr_dll_handle = nullptr;
|
||||||
bool is_native_aot = false;
|
void *coreclr_dll_handle = nullptr;
|
||||||
|
|
||||||
String project_assembly_path;
|
String project_assembly_path;
|
||||||
uint64_t project_assembly_modified_time = 0;
|
uint64_t project_assembly_modified_time = 0;
|
||||||
|
|
BIN
modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar
vendored
Executable file
BIN
modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar
vendored
Executable file
Binary file not shown.
|
@ -2381,19 +2381,6 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
|
||||||
#ifdef MODULE_MONO_ENABLED
|
#ifdef MODULE_MONO_ENABLED
|
||||||
// Android export is still a work in progress, keep a message as a warning.
|
// 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";
|
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
|
#endif
|
||||||
|
|
||||||
// Look for export templates (first official, and if defined custom templates).
|
// Look for export templates (first official, and if defined custom templates).
|
||||||
|
@ -3201,6 +3188,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
|
||||||
PluginConfigAndroid::get_plugins_custom_maven_repos(enabled_plugins, android_dependencies_maven_repos);
|
PluginConfigAndroid::get_plugins_custom_maven_repos(enabled_plugins, android_dependencies_maven_repos);
|
||||||
#endif // DISABLE_DEPRECATED
|
#endif // DISABLE_DEPRECATED
|
||||||
|
|
||||||
|
bool has_dotnet_project = false;
|
||||||
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
|
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
|
||||||
for (int i = 0; i < export_plugins.size(); i++) {
|
for (int i = 0; i < export_plugins.size(); i++) {
|
||||||
if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) {
|
if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) {
|
||||||
|
@ -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);
|
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);
|
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);
|
bool clean_build_required = _is_clean_build_required(p_preset);
|
||||||
|
@ -3231,12 +3224,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
|
||||||
cmdline.push_back("clean");
|
cmdline.push_back("clean");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String edition = has_dotnet_project ? "Mono" : "Standard";
|
||||||
String build_type = p_debug ? "Debug" : "Release";
|
String build_type = p_debug ? "Debug" : "Release";
|
||||||
if (export_format == EXPORT_FORMAT_AAB) {
|
if (export_format == EXPORT_FORMAT_AAB) {
|
||||||
String bundle_build_command = vformat("bundle%s", build_type);
|
String bundle_build_command = vformat("bundle%s", build_type);
|
||||||
cmdline.push_back(bundle_build_command);
|
cmdline.push_back(bundle_build_command);
|
||||||
} else if (export_format == EXPORT_FORMAT_APK) {
|
} 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);
|
cmdline.push_back(apk_build_command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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("-p"); // argument to specify the start directory.
|
||||||
copy_args.push_back(build_path); // 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());
|
copy_args.push_back("-Pexport_build_type=" + build_type.to_lower());
|
||||||
|
|
||||||
String export_format_arg = export_format == EXPORT_FORMAT_AAB ? "aab" : "apk";
|
String export_format_arg = export_format == EXPORT_FORMAT_AAB ? "aab" : "apk";
|
||||||
|
|
|
@ -29,6 +29,8 @@ allprojects {
|
||||||
configurations {
|
configurations {
|
||||||
// Initializes a placeholder for the devImplementation dependency configuration.
|
// Initializes a placeholder for the devImplementation dependency configuration.
|
||||||
devImplementation {}
|
devImplementation {}
|
||||||
|
// Initializes a placeholder for the monoImplementation dependency configuration.
|
||||||
|
monoImplementation {}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -42,9 +44,9 @@ dependencies {
|
||||||
} else {
|
} else {
|
||||||
// Godot gradle build mode. In this scenario this project is the only one around and the Godot
|
// Godot gradle build mode. In this scenario this project is the only one around and the Godot
|
||||||
// library is available through the pre-generated godot-lib.*.aar android archive files.
|
// library is available through the pre-generated godot-lib.*.aar android archive files.
|
||||||
debugImplementation fileTree(dir: 'libs/debug', include: ['*.jar', '*.aar'])
|
debugImplementation fileTree(dir: 'libs/debug', include: ['**/*.jar', '*.aar'])
|
||||||
devImplementation fileTree(dir: 'libs/dev', include: ['*.jar', '*.aar'])
|
devImplementation fileTree(dir: 'libs/dev', include: ['**/*.jar', '*.aar'])
|
||||||
releaseImplementation fileTree(dir: 'libs/release', include: ['*.jar', '*.aar'])
|
releaseImplementation fileTree(dir: 'libs/release', include: ['**/*.jar', '*.aar'])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Godot user plugins remote dependencies
|
// Godot user plugins remote dependencies
|
||||||
|
@ -60,6 +62,12 @@ dependencies {
|
||||||
if (pluginsBinaries != null && pluginsBinaries.size() > 0) {
|
if (pluginsBinaries != null && pluginsBinaries.size() > 0) {
|
||||||
implementation files(pluginsBinaries)
|
implementation files(pluginsBinaries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// .NET dependencies
|
||||||
|
String jar = '../../../../modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar'
|
||||||
|
if (file(jar).exists()) {
|
||||||
|
monoImplementation files(jar)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
@ -155,6 +163,10 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
||||||
debug {
|
debug {
|
||||||
|
@ -192,6 +204,13 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions 'edition'
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
standard {}
|
||||||
|
mono {}
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
manifest.srcFile 'AndroidManifest.xml'
|
manifest.srcFile 'AndroidManifest.xml'
|
||||||
|
@ -207,7 +226,8 @@ android {
|
||||||
|
|
||||||
applicationVariants.all { variant ->
|
applicationVariants.all { variant ->
|
||||||
variant.outputs.all { output ->
|
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 exportPath = getExportPath()
|
||||||
String exportFilename = getExportFilename()
|
String exportFilename = getExportFilename()
|
||||||
|
String exportEdition = getExportEdition()
|
||||||
String exportBuildType = getExportBuildType()
|
String exportBuildType = getExportBuildType()
|
||||||
|
String exportBuildTypeCapitalized = exportBuildType.capitalize()
|
||||||
String exportFormat = getExportFormat()
|
String exportFormat = getExportFormat()
|
||||||
|
|
||||||
boolean isAab = exportFormat == "aab"
|
boolean isAab = exportFormat == "aab"
|
||||||
String sourceFilepath = isAab ? "$buildDir/outputs/bundle/$exportBuildType/build-${exportBuildType}.aab" : "$buildDir/outputs/apk/$exportBuildType/android_${exportBuildType}.apk"
|
boolean isMono = exportEdition == "mono"
|
||||||
String sourceFilename = isAab ? "build-${exportBuildType}.aab" : "android_${exportBuildType}.apk"
|
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
|
from sourceFilepath
|
||||||
into exportPath
|
into exportPath
|
||||||
|
|
|
@ -224,6 +224,14 @@ ext.getExportFilename = {
|
||||||
return exportFilename
|
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 = {
|
ext.getExportBuildType = {
|
||||||
String exportBuildType = project.hasProperty("export_build_type") ? project.property("export_build_type") : ""
|
String exportBuildType = project.hasProperty("export_build_type") ? project.property("export_build_type") : ""
|
||||||
if (exportBuildType == null || exportBuildType.isEmpty()) {
|
if (exportBuildType == null || exportBuildType.isEmpty()) {
|
||||||
|
|
|
@ -33,14 +33,29 @@ package com.godot.game;
|
||||||
import org.godotengine.godot.GodotActivity;
|
import org.godotengine.godot.GodotActivity;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.splashscreen.SplashScreen;
|
import androidx.core.splashscreen.SplashScreen;
|
||||||
|
|
||||||
|
import com.godot.game.BuildConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Template activity for Godot Android builds.
|
* Template activity for Godot Android builds.
|
||||||
* Feel free to extend and modify this class for your custom logic.
|
* Feel free to extend and modify this class for your custom logic.
|
||||||
*/
|
*/
|
||||||
public class GodotApp extends GodotActivity {
|
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
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
SplashScreen.installSplashScreen(this);
|
SplashScreen.installSplashScreen(this);
|
||||||
|
|
|
@ -30,6 +30,7 @@ ext {
|
||||||
"editor": ["dev", "debug", "release"],
|
"editor": ["dev", "debug", "release"],
|
||||||
"template": ["dev", "debug", "release"]
|
"template": ["dev", "debug", "release"]
|
||||||
]
|
]
|
||||||
|
supportedEditions = ["standard", "mono"]
|
||||||
|
|
||||||
// Used by gradle to specify which architecture to build for by default when running
|
// Used by gradle to specify which architecture to build for by default when running
|
||||||
// `./gradlew build` (this command is usually used by Android Studio).
|
// `./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.
|
* The zip file also includes some gradle tools to enable gradle builds from the Godot Editor.
|
||||||
*/
|
*/
|
||||||
task zipGradleBuild(type: Zip) {
|
task zipGradleBuild(type: Zip) {
|
||||||
onlyIf { generateGodotTemplates.state.executed || generateDevTemplate.state.executed }
|
onlyIf { generateGodotTemplates.state.executed || generateGodotMonoTemplates.state.executed || generateDevTemplate.state.executed }
|
||||||
doFirst {
|
doFirst {
|
||||||
logger.lifecycle("Generating Godot gradle build template")
|
logger.lifecycle("Generating Godot gradle build template")
|
||||||
}
|
}
|
||||||
|
@ -94,15 +95,22 @@ def templateExcludedBuildTask() {
|
||||||
/**
|
/**
|
||||||
* Generates the build tasks for the given flavor
|
* Generates the build tasks for the given flavor
|
||||||
* @param flavor Must be one of the supported flavors ('template' / 'editor')
|
* @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')
|
* @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)) {
|
if (!supportedFlavors.contains(flavor)) {
|
||||||
throw new GradleException("Invalid build flavor: $flavor")
|
throw new GradleException("Invalid build flavor: $flavor")
|
||||||
}
|
}
|
||||||
if (!supportedAndroidDistributions.contains(androidDistro)) {
|
if (!supportedAndroidDistributions.contains(androidDistro)) {
|
||||||
throw new GradleException("Invalid Android distribution: $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()
|
String capitalizedAndroidDistro = androidDistro.capitalize()
|
||||||
def buildTasks = []
|
def buildTasks = []
|
||||||
|
@ -126,6 +134,7 @@ def generateBuildTasks(String flavor = "template", String androidDistro = "andro
|
||||||
&& targetLibs.listFiles().length > 0)) {
|
&& targetLibs.listFiles().length > 0)) {
|
||||||
|
|
||||||
String capitalizedTarget = target.capitalize()
|
String capitalizedTarget = target.capitalize()
|
||||||
|
String capitalizedEdition = edition.capitalize()
|
||||||
if (isTemplate) {
|
if (isTemplate) {
|
||||||
// Copy the Godot android library archive file into the app module libs directory.
|
// 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.
|
// 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.
|
// 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.
|
// 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) {
|
if (tasks.findByName(copyBinaryTaskName) != null) {
|
||||||
buildTasks += tasks.getByName(copyBinaryTaskName)
|
buildTasks += tasks.getByName(copyBinaryTaskName)
|
||||||
} else {
|
} else {
|
||||||
buildTasks += tasks.create(name: copyBinaryTaskName, type: Copy) {
|
buildTasks += tasks.create(name: copyBinaryTaskName, type: Copy) {
|
||||||
dependsOn ":app:assemble${capitalizedTarget}"
|
String filenameSuffix = edition == "mono" ? "${edition}${capitalizedTarget}" : target
|
||||||
from("app/build/outputs/apk/${target}")
|
dependsOn ":app:assemble${capitalizedEdition}${capitalizedTarget}"
|
||||||
|
from("app/build/outputs/apk/${edition}/${target}")
|
||||||
into(binDir)
|
into(binDir)
|
||||||
include("android_${target}.apk")
|
include("android_${filenameSuffix}.apk")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -212,7 +222,7 @@ def generateBuildTasks(String flavor = "template", String androidDistro = "andro
|
||||||
*/
|
*/
|
||||||
task generateGodotEditor {
|
task generateGodotEditor {
|
||||||
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
|
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
|
||||||
dependsOn = generateBuildTasks("editor", "android")
|
dependsOn = generateBuildTasks("editor", "standard", "android")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -224,7 +234,7 @@ task generateGodotEditor {
|
||||||
*/
|
*/
|
||||||
task generateGodotHorizonOSEditor {
|
task generateGodotHorizonOSEditor {
|
||||||
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
|
gradle.startParameter.excludedTaskNames += templateExcludedBuildTask()
|
||||||
dependsOn = generateBuildTasks("editor", "horizonos")
|
dependsOn = generateBuildTasks("editor", "standard", "horizonos")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -237,6 +247,17 @@ task generateGodotTemplates {
|
||||||
finalizedBy 'zipGradleBuild'
|
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
|
* 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_debug.apk")
|
||||||
delete("$binDir/android_dev.apk")
|
delete("$binDir/android_dev.apk")
|
||||||
delete("$binDir/android_release.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/android_source.zip")
|
||||||
delete("$binDir/godot-lib.template_debug.aar")
|
delete("$binDir/godot-lib.template_debug.aar")
|
||||||
delete("$binDir/godot-lib.template_debug.dev.aar")
|
delete("$binDir/godot-lib.template_debug.dev.aar")
|
||||||
|
|
Loading…
Reference in a new issue