Merge pull request #30292 from neikeq/android_fixes

Mono: Android build and shared libraries fixes
This commit is contained in:
Rémi Verschelde 2019-07-03 21:26:12 +02:00 committed by GitHub
commit e1478b7a87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 285 additions and 49 deletions

View file

@ -0,0 +1,69 @@
def generate_compressed_config(config_src, output_dir):
import os.path
from compat import byte_to_str
# Header file
with open(os.path.join(output_dir, 'android_mono_config.gen.h'), 'w') as header:
header.write('''/* THIS FILE IS GENERATED DO NOT EDIT */
#ifndef ANDROID_MONO_CONFIG_GEN_H
#define ANDROID_MONO_CONFIG_GEN_H
#ifdef ANDROID_ENABLED
#include "core/ustring.h"
String get_godot_android_mono_config();
#endif // ANDROID_ENABLED
#endif // ANDROID_MONO_CONFIG_GEN_H
''')
# Source file
with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp:
with open(config_src, 'rb') as f:
buf = f.read()
decompr_size = len(buf)
import zlib
buf = zlib.compress(buf)
compr_size = len(buf)
bytes_seq_str = ''
for i, buf_idx in enumerate(range(compr_size)):
if i > 0:
bytes_seq_str += ', '
bytes_seq_str += byte_to_str(buf[buf_idx])
cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */
#include "android_mono_config.gen.h"
#ifdef ANDROID_ENABLED
#include "core/io/compression.h"
#include "core/pool_vector.h"
namespace {
// config
static const int config_compressed_size = %d;
static const int config_uncompressed_size = %d;
static const unsigned char config_compressed_data[] = { %s };
} // namespace
String get_godot_android_mono_config() {
PoolVector<uint8_t> data;
data.resize(config_uncompressed_size);
PoolVector<uint8_t>::Write w = data.write();
Compression::decompress(w.ptr(), config_uncompressed_size, config_compressed_data,
config_compressed_size, Compression::MODE_DEFLATE);
String s;
if (s.parse_utf8((const char *)w.ptr(), data.size())) {
ERR_FAIL_V(String());
}
return s;
}
#endif // ANDROID_ENABLED
''' % (compr_size, decompr_size, bytes_seq_str))

View file

@ -23,30 +23,31 @@ def generate_header(src, dst, version_dst):
latest_mtime = mtime if mtime > latest_mtime else latest_mtime
with open(filepath, 'rb') as f:
buf = f.read()
decomp_size = len(buf)
decompr_size = len(buf)
import zlib
buf = zlib.compress(buf)
compr_size = len(buf)
name = str(cs_file_count)
header.write('\n')
header.write('// ' + filepath_src_rel + '\n')
header.write('static const int _cs_' + name + '_compressed_size = ' + str(len(buf)) + ';\n')
header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decomp_size) + ';\n')
header.write('static const int _cs_' + name + '_compressed_size = ' + str(compr_size) + ';\n')
header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decompr_size) + ';\n')
header.write('static const unsigned char _cs_' + name + '_compressed[] = { ')
for i, buf_idx in enumerate(range(len(buf))):
for i, buf_idx in enumerate(range(compr_size)):
if i > 0:
header.write(', ')
header.write(byte_to_str(buf[buf_idx]))
header.write(' };\n')
inserted_files += '\tr_files.insert("' + filepath_src_rel.replace('\\', '\\\\') + '", ' \
'CompressedFile(_cs_' + name + '_compressed_size, ' \
'GodotCsCompressedFile(_cs_' + name + '_compressed_size, ' \
'_cs_' + name + '_uncompressed_size, ' \
'_cs_' + name + '_compressed));\n'
header.write(' };\n')
header.write('\nstruct CompressedFile\n' '{\n'
header.write('\nstruct GodotCsCompressedFile\n' '{\n'
'\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n'
'\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n'
'\n\tGodotCsCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n'
'\t{\n' '\t\tcompressed_size = p_comp_size;\n' '\t\tuncompressed_size = p_uncomp_size;\n'
'\t\tdata = p_data;\n' '\t}\n' '\n\tCompressedFile() {}\n' '};\n'
'\nvoid get_compressed_files(Map<String, CompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n'
'\t\tdata = p_data;\n' '\t}\n' '\n\tGodotCsCompressedFile() {}\n' '};\n'
'\nvoid get_compressed_files(Map<String, GodotCsCompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n'
)
header.write('\n#endif // TOOLS_ENABLED\n')
header.write('\n#endif // CS_COMPRESSED_H\n')

View file

@ -41,7 +41,7 @@ def copy_file(src_dir, dst_dir, name):
dst_dir = Dir(dst_dir).abspath
if not os.path.isdir(dst_dir):
os.mkdir(dst_dir)
os.makedirs(dst_dir)
copy(src_path, dst_dir)
@ -65,6 +65,10 @@ def configure(env, env_mono):
# TODO: Implement this. We have to add the data directory to the apk, concretely the Api and Tools folders.
raise RuntimeError('This module does not currently support building for android with tools enabled')
if is_android and mono_static:
# When static linking and doing something that requires libmono-native, we get a dlopen error as libmono-native seems to depend on libmonosgen-2.0
raise RuntimeError('Linking Mono statically is not currently supported on Android')
if (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')) and not mono_prefix:
print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead")
@ -188,7 +192,7 @@ def configure(env, env_mono):
if is_apple:
env.Append(LIBS=['iconv', 'pthread'])
elif is_android:
env.Append(LIBS=['m', 'dl'])
pass # Nothing
else:
env.Append(LIBS=['m', 'rt', 'dl', 'pthread'])
@ -236,6 +240,14 @@ def configure(env, env_mono):
mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip()
make_template_dir(env, mono_root)
elif not tools_enabled and is_android:
# Compress Android Mono Config
from . import make_android_mono_config
config_file_path = os.path.join(mono_root, 'etc', 'mono', 'config')
make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/')
# Copy the required shared libraries
copy_mono_shared_libs(env, mono_root, None)
if copy_mono_root:
if not mono_root:
@ -270,9 +282,8 @@ def make_template_dir(env, mono_root):
# Copy etc/mono/
if platform != 'android':
template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono')
copy_mono_etc_dir(mono_root, template_mono_config_dir, env['platform'])
template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono')
copy_mono_etc_dir(mono_root, template_mono_config_dir, env['platform'])
# Copy the required shared libraries

View file

@ -682,19 +682,20 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
String name = ProjectSettings::get_singleton()->get("application/config/name");
if (name.empty()) {
name = "UnnamedProject";
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
if (appname_safe.empty()) {
appname_safe = "UnnamedProject";
}
name += ".dll";
appname_safe += ".dll";
if (proj_assembly) {
String proj_asm_path = proj_assembly->get_path();
if (!FileAccess::exists(proj_assembly->get_path())) {
// Maybe it wasn't loaded from the default path, so check this as well
proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name);
proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe);
if (!FileAccess::exists(proj_asm_path))
return false; // No assembly to load
}
@ -702,7 +703,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time())
return false; // Already up to date
} else {
if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name)))
if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe)))
return false; // No assembly to load
}

View file

@ -914,12 +914,12 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
// Generate sources from compressed files
Map<String, CompressedFile> compressed_files;
Map<String, GodotCsCompressedFile> compressed_files;
get_compressed_files(compressed_files);
for (Map<String, CompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) {
for (Map<String, GodotCsCompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) {
const String &file_name = E->key();
const CompressedFile &file_data = E->value();
const GodotCsCompressedFile &file_data = E->value();
String output_file = path_join(core_dir, file_name);

View file

@ -63,16 +63,17 @@ bool GodotSharpEditor::_create_project_solution() {
pr.step(TTR("Generating C# project..."));
String path = OS::get_singleton()->get_resource_dir();
String name = ProjectSettings::get_singleton()->get("application/config/name");
if (name.empty()) {
name = "UnnamedProject";
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
if (appname_safe.empty()) {
appname_safe = "UnnamedProject";
}
String guid = CSharpProject::generate_game_project(path, name);
String guid = CSharpProject::generate_game_project(path, appname_safe);
if (guid.length()) {
DotNetSolution solution(name);
DotNetSolution solution(appname_safe);
if (!solution.set_path(path)) {
show_error_dialog(TTR("Failed to create solution."));
@ -81,12 +82,12 @@ bool GodotSharpEditor::_create_project_solution() {
DotNetSolution::ProjectInfo proj_info;
proj_info.guid = guid;
proj_info.relpath = name + ".csproj";
proj_info.relpath = appname_safe + ".csproj";
proj_info.configs.push_back("Debug");
proj_info.configs.push_back("Release");
proj_info.configs.push_back("Tools");
solution.add_new_project(name, proj_info);
solution.add_new_project(appname_safe, proj_info);
Error sln_error = solution.save();

View file

@ -106,13 +106,14 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
Map<String, String> dependencies;
String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name");
if (project_dll_name.empty()) {
project_dll_name = "UnnamedProject";
String project_dll_name_safe = OS::get_singleton()->get_safe_dir_name(project_dll_name);
if (project_dll_name_safe.empty()) {
project_dll_name_safe = "UnnamedProject";
}
String project_dll_src_dir = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config);
String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name + ".dll");
dependencies.insert(project_dll_name, project_dll_src_path);
String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name_safe + ".dll");
dependencies.insert(project_dll_name_safe, project_dll_src_path);
{
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
@ -122,10 +123,10 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
_GDMONO_SCOPE_DOMAIN_(export_domain);
GDMonoAssembly *scripts_assembly = NULL;
bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name,
bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name_safe,
project_dll_src_path, &scripts_assembly, /* refonly: */ true);
ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name);
ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name_safe);
ERR_FAIL_COND(!load_success);
Vector<String> search_dirs;

View file

@ -39,6 +39,10 @@
#include "editor/editor_settings.h"
#endif
#ifdef __ANDROID__
#include "utils/android_utils.h"
#endif
namespace GodotSharpDirs {
String _get_expected_build_config() {
@ -129,15 +133,16 @@ private:
mono_solutions_dir = mono_user_dir.plus_file("solutions");
build_logs_dir = mono_user_dir.plus_file("build_logs");
String name = ProjectSettings::get_singleton()->get("application/config/name");
if (name.empty()) {
name = "UnnamedProject";
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
if (appname_safe.empty()) {
appname_safe = "UnnamedProject";
}
String base_path = ProjectSettings::get_singleton()->globalize_path("res://");
sln_filepath = base_path.plus_file(name + ".sln");
csproj_filepath = base_path.plus_file(name + ".csproj");
sln_filepath = base_path.plus_file(appname_safe + ".sln");
csproj_filepath = base_path.plus_file(appname_safe + ".csproj");
#endif
String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir();
@ -150,7 +155,12 @@ private:
String data_mono_root_dir = data_dir_root.plus_file("Mono");
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
#if __ANDROID__
data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir();
#else
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
#endif
#ifdef WINDOWS_ENABLED
data_mono_bin_dir = data_mono_root_dir.plus_file("bin");
@ -173,15 +183,21 @@ private:
#else
String appname = OS::get_singleton()->get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name"));
String data_dir_root = exe_dir.plus_file("data_" + appname);
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
String data_dir_root = exe_dir.plus_file("data_" + appname_safe);
if (!DirAccess::exists(data_dir_root)) {
data_dir_root = exe_dir.plus_file("data_Godot");
}
String data_mono_root_dir = data_dir_root.plus_file("Mono");
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
#if __ANDROID__
data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir();
#else
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
#endif
#ifdef WINDOWS_ENABLED
data_mono_bin_dir = data_mono_root_dir.plus_file("bin");

View file

@ -56,6 +56,10 @@
#include "main/main.h"
#endif
#ifdef ANDROID_ENABLED
#include "android_mono_config.gen.h"
#endif
#define OUT_OF_SYNC_ERR_MESSAGE(m_assembly_name) "The assembly '" m_assembly_name "' is out of sync. " \
"This error is expected if you just upgraded to a newer Godot version. " \
"Building the project will update the assembly to the correct version."
@ -287,7 +291,11 @@ void GDMono::initialize() {
gdmono_debug_init();
#endif
#ifdef ANDROID_ENABLED
mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data());
#else
mono_config_parse(NULL);
#endif
mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL);
@ -651,12 +659,13 @@ bool GDMono::_load_project_assembly() {
if (project_assembly)
return true;
String name = ProjectSettings::get_singleton()->get("application/config/name");
if (name.empty()) {
name = "UnnamedProject";
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
if (appname_safe.empty()) {
appname_safe = "UnnamedProject";
}
bool success = load_assembly(name, &project_assembly);
bool success = load_assembly(appname_safe, &project_assembly);
if (success) {
mono_assembly_set_main(project_assembly->get_assembly());

View file

@ -270,7 +270,18 @@ Error GDMonoAssembly::load(bool p_refonly) {
Vector<uint8_t> data = FileAccess::get_file_as_array(path);
ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ);
String image_filename = ProjectSettings::get_singleton()->globalize_path(path);
String image_filename;
#ifdef ANDROID_ENABLED
if (path.begins_with("res://")) {
image_filename = path.substr(6, path.length());
} else {
image_filename = ProjectSettings::get_singleton()->globalize_path(path);
}
#else
// FIXME: globalize_path does not work on exported games
image_filename = ProjectSettings::get_singleton()->globalize_path(path);
#endif
MonoImageOpenStatus status = MONO_IMAGE_OK;

View file

@ -0,0 +1,68 @@
/*************************************************************************/
/* android_utils.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "android_utils.h"
#ifdef __ANDROID__
#include "platform/android/thread_jandroid.h"
namespace GDMonoUtils {
namespace Android {
String get_app_native_lib_dir() {
JNIEnv *env = ThreadAndroid::get_env();
jclass activityThreadClass = env->FindClass("android/app/ActivityThread");
jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;");
jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread);
jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;");
jobject ctx = env->CallObjectMethod(activityThread, getApplication);
jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo);
jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;");
jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField);
String result;
const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL);
if (nativeLibraryDir_utf8) {
result.parse_utf8(nativeLibraryDir_utf8);
env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8);
}
return result;
}
} // namespace Android
} // namespace GDMonoUtils
#endif // __ANDROID__

View file

@ -0,0 +1,48 @@
/*************************************************************************/
/* android_utils.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef ANDROID_UTILS_H
#define ANDROID_UTILS_H
#ifdef __ANDROID__
#include "core/ustring.h"
namespace GDMonoUtils {
namespace Android {
String get_app_native_lib_dir();
} // namespace Android
} // namespace GDMonoUtils
#endif // __ANDROID__
#endif // ANDROID_UTILS_H