Mono/C#: Add iOS support
Right now, games only work on devices when exported with FullAOT+Interpreter. There are some issues left that need to addressed for FullAOT alone. Right now, it's giving issues with the Godot.NativeCalls static constructor.
This commit is contained in:
parent
6b38fe691a
commit
445f1c6e7b
|
@ -582,6 +582,14 @@ String EditorExportPlugin::get_ios_cpp_code() const {
|
|||
return ios_cpp_code;
|
||||
}
|
||||
|
||||
void EditorExportPlugin::add_ios_project_static_lib(const String &p_path) {
|
||||
ios_project_static_libs.push_back(p_path);
|
||||
}
|
||||
|
||||
Vector<String> EditorExportPlugin::get_ios_project_static_libs() const {
|
||||
return ios_project_static_libs;
|
||||
}
|
||||
|
||||
void EditorExportPlugin::_export_file_script(const String &p_path, const String &p_type, const PoolVector<String> &p_features) {
|
||||
|
||||
if (get_script_instance()) {
|
||||
|
@ -617,6 +625,7 @@ void EditorExportPlugin::skip() {
|
|||
void EditorExportPlugin::_bind_methods() {
|
||||
|
||||
ClassDB::bind_method(D_METHOD("add_shared_object", "path", "tags"), &EditorExportPlugin::add_shared_object);
|
||||
ClassDB::bind_method(D_METHOD("add_ios_project_static_lib", "path"), &EditorExportPlugin::add_ios_project_static_lib);
|
||||
ClassDB::bind_method(D_METHOD("add_file", "path", "file", "remap"), &EditorExportPlugin::add_file);
|
||||
ClassDB::bind_method(D_METHOD("add_ios_framework", "path"), &EditorExportPlugin::add_ios_framework);
|
||||
ClassDB::bind_method(D_METHOD("add_ios_plist_content", "plist_content"), &EditorExportPlugin::add_ios_plist_content);
|
||||
|
|
|
@ -291,6 +291,7 @@ class EditorExportPlugin : public Reference {
|
|||
bool skipped;
|
||||
|
||||
Vector<String> ios_frameworks;
|
||||
Vector<String> ios_project_static_libs;
|
||||
String ios_plist_content;
|
||||
String ios_linker_flags;
|
||||
Vector<String> ios_bundle_files;
|
||||
|
@ -322,6 +323,7 @@ protected:
|
|||
void add_shared_object(const String &p_path, const Vector<String> &tags);
|
||||
|
||||
void add_ios_framework(const String &p_path);
|
||||
void add_ios_project_static_lib(const String &p_path);
|
||||
void add_ios_plist_content(const String &p_plist_content);
|
||||
void add_ios_linker_flags(const String &p_flags);
|
||||
void add_ios_bundle_file(const String &p_path);
|
||||
|
@ -336,6 +338,7 @@ protected:
|
|||
|
||||
public:
|
||||
Vector<String> get_ios_frameworks() const;
|
||||
Vector<String> get_ios_project_static_libs() const;
|
||||
String get_ios_plist_content() const;
|
||||
String get_ios_linker_flags() const;
|
||||
Vector<String> get_ios_bundle_files() const;
|
||||
|
|
|
@ -21,9 +21,6 @@ if env_mono['mono_glue']:
|
|||
if not os.path.isfile('glue/mono_glue.gen.cpp'):
|
||||
raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?")
|
||||
|
||||
if env_mono['tools'] or env_mono['target'] != 'release':
|
||||
env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD'])
|
||||
|
||||
# Configure Thread Local Storage
|
||||
|
||||
conf = Configure(env_mono)
|
||||
|
@ -50,5 +47,11 @@ env_mono.add_source_files(env.modules_sources, 'glue/*.cpp')
|
|||
env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp')
|
||||
env_mono.add_source_files(env.modules_sources, 'utils/*.cpp')
|
||||
|
||||
env_mono.add_source_files(env.modules_sources, 'mono_gd/support/*.cpp')
|
||||
|
||||
if env['platform'] in ['osx', 'iphone']:
|
||||
env_mono.add_source_files(env.modules_sources, 'mono_gd/support/*.mm')
|
||||
env_mono.add_source_files(env.modules_sources, 'mono_gd/support/*.m')
|
||||
|
||||
if env['tools']:
|
||||
env_mono.add_source_files(env.modules_sources, 'editor/*.cpp')
|
||||
|
|
|
@ -46,16 +46,19 @@ def find_file_in_dir(directory, names, prefixes=[''], extensions=['']):
|
|||
return ''
|
||||
|
||||
|
||||
def copy_file(src_dir, dst_dir, name):
|
||||
def copy_file(src_dir, dst_dir, src_name, dst_name=''):
|
||||
from shutil import copy
|
||||
|
||||
src_path = os.path.join(Dir(src_dir).abspath, name)
|
||||
src_path = os.path.join(Dir(src_dir).abspath, src_name)
|
||||
dst_dir = Dir(dst_dir).abspath
|
||||
|
||||
if not os.path.isdir(dst_dir):
|
||||
os.makedirs(dst_dir)
|
||||
|
||||
copy(src_path, dst_dir)
|
||||
if dst_name:
|
||||
copy(src_path, os.path.join(dst_dir, dst_name))
|
||||
else:
|
||||
copy(src_path, dst_dir)
|
||||
|
||||
|
||||
def is_desktop(platform):
|
||||
|
@ -63,11 +66,11 @@ def is_desktop(platform):
|
|||
|
||||
|
||||
def is_unix_like(platform):
|
||||
return platform in ['osx', 'x11', 'server', 'android', 'haiku']
|
||||
return platform in ['osx', 'x11', 'server', 'android', 'haiku', 'iphone']
|
||||
|
||||
|
||||
def module_supports_tools_on(platform):
|
||||
return platform not in ['android', 'javascript']
|
||||
return platform not in ['android', 'javascript', 'iphone']
|
||||
|
||||
|
||||
def find_wasm_src_dir(mono_root):
|
||||
|
@ -85,6 +88,8 @@ def configure(env, env_mono):
|
|||
bits = env['bits']
|
||||
is_android = env['platform'] == 'android'
|
||||
is_javascript = env['platform'] == 'javascript'
|
||||
is_ios = env['platform'] == 'iphone'
|
||||
is_ios_sim = is_ios and env['arch'] in ['x86', 'x86_64']
|
||||
|
||||
tools_enabled = env['tools']
|
||||
mono_static = env['mono_static']
|
||||
|
@ -109,15 +114,30 @@ def configure(env, env_mono):
|
|||
raise RuntimeError('This module does not currently support building for this platform with tools enabled')
|
||||
|
||||
if is_android and mono_static:
|
||||
# Android: 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('Statically linking Mono is not currently supported on this platform')
|
||||
# FIXME: 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'. Could be fixed by re-directing to '__Internal' with a dllmap or in the dlopen hook.
|
||||
raise RuntimeError('Statically linking Mono is not currently supported for this platform')
|
||||
|
||||
if is_javascript:
|
||||
mono_static = True
|
||||
if not mono_static and (is_javascript or is_ios):
|
||||
raise RuntimeError('Dynamically linking Mono is not currently supported for this platform')
|
||||
|
||||
if not mono_prefix and (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')):
|
||||
print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead")
|
||||
|
||||
# Although we don't support building with tools for any platform where we currently use static AOT,
|
||||
# if these are supported in the future, we won't be using static AOT for them as that would be
|
||||
# too restrictive for the editor. These builds would probably be made to only use the interpreter.
|
||||
mono_aot_static = (is_ios and not is_ios_sim) and not env['tools']
|
||||
|
||||
# Static AOT is only supported on the root domain
|
||||
mono_single_appdomain = mono_aot_static
|
||||
|
||||
if mono_single_appdomain:
|
||||
env_mono.Append(CPPDEFINES=['GD_MONO_SINGLE_APPDOMAIN'])
|
||||
|
||||
if (env['tools'] or env['target'] != 'release') and not mono_single_appdomain:
|
||||
env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD'])
|
||||
|
||||
if env['platform'] == 'windows':
|
||||
mono_root = mono_prefix
|
||||
|
||||
|
@ -185,6 +205,7 @@ def configure(env, env_mono):
|
|||
copy_file(mono_bin_path, '#bin', mono_dll_file)
|
||||
else:
|
||||
is_apple = env['platform'] in ['osx', 'iphone']
|
||||
is_macos = is_apple and not is_ios
|
||||
|
||||
sharedlib_ext = '.dylib' if is_apple else '.so'
|
||||
|
||||
|
@ -192,10 +213,10 @@ def configure(env, env_mono):
|
|||
mono_lib_path = ''
|
||||
mono_so_file = ''
|
||||
|
||||
if not mono_root and (is_android or is_javascript):
|
||||
if not mono_root and (is_android or is_javascript or is_ios):
|
||||
raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter")
|
||||
|
||||
if not mono_root and is_apple:
|
||||
if not mono_root and is_macos:
|
||||
# Try with some known directories under OSX
|
||||
hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current', '/usr/local/var/homebrew/linked/mono']
|
||||
for hint_dir in hint_dirs:
|
||||
|
@ -211,6 +232,9 @@ def configure(env, env_mono):
|
|||
raise RuntimeError("Building with mono_static=yes, but failed to find the mono prefix with pkg-config; " + \
|
||||
"specify one manually with the 'mono_prefix' SCons parameter")
|
||||
|
||||
if is_ios and not is_ios_sim:
|
||||
env_mono.Append(CPPDEFINES=['IOS_DEVICE'])
|
||||
|
||||
if mono_root:
|
||||
print('Found Mono root directory: ' + mono_root)
|
||||
|
||||
|
@ -232,7 +256,23 @@ def configure(env, env_mono):
|
|||
mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a')
|
||||
|
||||
if is_apple:
|
||||
env.Append(LINKFLAGS=['-Wl,-force_load,' + mono_lib_file])
|
||||
if is_macos:
|
||||
env.Append(LINKFLAGS=['-Wl,-force_load,' + mono_lib_file])
|
||||
else:
|
||||
arch = env['arch']
|
||||
def copy_mono_lib(libname_wo_ext):
|
||||
copy_file(mono_lib_path, '#bin', libname_wo_ext + '.a', '%s.iphone.%s.a' % (libname_wo_ext, arch))
|
||||
|
||||
# Copy Mono libraries to the output folder. These are meant to be bundled with
|
||||
# the export templates and added to the Xcode project when exporting a game.
|
||||
copy_mono_lib('lib' + mono_lib)
|
||||
copy_mono_lib('libmono-native')
|
||||
copy_mono_lib('libmono-profiler-log')
|
||||
|
||||
if not is_ios_sim:
|
||||
copy_mono_lib('libmono-ee-interp')
|
||||
copy_mono_lib('libmono-icall-table')
|
||||
copy_mono_lib('libmono-ilgen')
|
||||
else:
|
||||
assert is_desktop(env['platform']) or is_android or is_javascript
|
||||
env.Append(LINKFLAGS=['-Wl,-whole-archive', mono_lib_file, '-Wl,-no-whole-archive'])
|
||||
|
@ -261,10 +301,12 @@ def configure(env, env_mono):
|
|||
else:
|
||||
env.Append(LIBS=[mono_lib])
|
||||
|
||||
if is_apple:
|
||||
if is_macos:
|
||||
env.Append(LIBS=['iconv', 'pthread'])
|
||||
elif is_android:
|
||||
pass # Nothing
|
||||
elif is_ios:
|
||||
pass # Nothing, linking is delegated to the exported Xcode project
|
||||
elif is_javascript:
|
||||
env.Append(LIBS=['m', 'rt', 'dl', 'pthread'])
|
||||
else:
|
||||
|
@ -319,6 +361,8 @@ def configure(env, env_mono):
|
|||
copy_mono_shared_libs(env, mono_root, None)
|
||||
elif is_javascript:
|
||||
pass # No data directory for this platform
|
||||
elif is_ios:
|
||||
pass # No data directory for this platform
|
||||
|
||||
if copy_mono_root:
|
||||
if not mono_root:
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
|
||||
supported_platforms = ['windows', 'osx', 'x11', 'server', 'android', 'haiku', 'javascript', 'iphone']
|
||||
|
||||
|
||||
def can_build(env, platform):
|
||||
return True
|
||||
|
||||
|
||||
def configure(env):
|
||||
if env['platform'] not in ['windows', 'osx', 'x11', 'server', 'android', 'haiku', 'javascript']:
|
||||
platform = env['platform']
|
||||
|
||||
if platform not in supported_platforms:
|
||||
raise RuntimeError('This module does not currently support building for this platform')
|
||||
|
||||
env.use_ptrcall = True
|
||||
|
@ -11,18 +17,25 @@ def configure(env):
|
|||
|
||||
from SCons.Script import BoolVariable, PathVariable, Variables, Help
|
||||
|
||||
default_mono_static = platform in ['iphone', 'javascript']
|
||||
default_mono_bundles_zlib = platform in ['javascript']
|
||||
|
||||
envvars = Variables()
|
||||
envvars.Add(PathVariable('mono_prefix', 'Path to the mono installation directory for the target platform and architecture', '', PathVariable.PathAccept))
|
||||
envvars.Add(BoolVariable('mono_static', 'Statically link mono', False))
|
||||
envvars.Add(BoolVariable('mono_static', 'Statically link mono', default_mono_static))
|
||||
envvars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True))
|
||||
envvars.Add(BoolVariable('copy_mono_root', 'Make a copy of the mono installation directory to bundle with the editor', False))
|
||||
envvars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False))
|
||||
|
||||
# TODO: It would be great if this could be detected automatically instead
|
||||
envvars.Add(BoolVariable('mono_bundles_zlib', 'Specify if the Mono runtime was built with bundled zlib', default_mono_bundles_zlib))
|
||||
|
||||
envvars.Update(env)
|
||||
Help(envvars.GenerateHelpText(env))
|
||||
|
||||
if env['platform'] == 'javascript':
|
||||
# Mono wasm already has zlib builtin, so we need this workaround to avoid symbol collisions
|
||||
print('Compiling with Mono wasm disables \'builtin_zlib\'')
|
||||
if env['mono_bundles_zlib']:
|
||||
# Mono may come with zlib bundled for WASM or on newer version when built with MinGW.
|
||||
print('This Mono runtime comes with zlib bundled. Disabling \'builtin_zlib\'...')
|
||||
env['builtin_zlib'] = False
|
||||
thirdparty_zlib_dir = "#thirdparty/zlib/"
|
||||
env.Prepend(CPPPATH=[thirdparty_zlib_dir])
|
||||
|
|
|
@ -737,7 +737,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
|
|||
if (proj_assembly) {
|
||||
String proj_asm_path = proj_assembly->get_path();
|
||||
|
||||
if (!FileAccess::exists(proj_assembly->get_path())) {
|
||||
if (!FileAccess::exists(proj_asm_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(appname_safe);
|
||||
if (!FileAccess::exists(proj_asm_path))
|
||||
|
|
|
@ -0,0 +1,618 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using GodotTools.Internals;
|
||||
using Directory = GodotTools.Utils.Directory;
|
||||
using File = GodotTools.Utils.File;
|
||||
using OS = GodotTools.Utils.OS;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace GodotTools.Export
|
||||
{
|
||||
public struct AotOptions
|
||||
{
|
||||
public bool EnableLLVM;
|
||||
public bool LLVMOnly;
|
||||
public string LLVMPath;
|
||||
public string LLVMOutputPath;
|
||||
|
||||
public bool FullAot;
|
||||
|
||||
private bool _useInterpreter;
|
||||
public bool UseInterpreter { get => _useInterpreter && !LLVMOnly; set => _useInterpreter = value; }
|
||||
|
||||
public string[] ExtraAotOptions;
|
||||
public string[] ExtraOptimizerOptions;
|
||||
|
||||
public string ToolchainPath;
|
||||
}
|
||||
|
||||
public static class AotBuilder
|
||||
{
|
||||
public static void CompileAssemblies(ExportPlugin exporter, AotOptions aotOpts, string[] features, string platform, bool isDebug, string bclDir, string outputDir, string outputDataDir, IDictionary<string, string> assemblies)
|
||||
{
|
||||
// TODO: WASM
|
||||
|
||||
string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}");
|
||||
|
||||
if (!Directory.Exists(aotTempDir))
|
||||
Directory.CreateDirectory(aotTempDir);
|
||||
|
||||
var assembliesPrepared = new Dictionary<string, string>();
|
||||
|
||||
foreach (var dependency in assemblies)
|
||||
{
|
||||
string assemblyName = dependency.Key;
|
||||
string assemblyPath = dependency.Value;
|
||||
|
||||
string assemblyPathInBcl = Path.Combine(bclDir, assemblyName + ".dll");
|
||||
|
||||
if (File.Exists(assemblyPathInBcl))
|
||||
{
|
||||
// Don't create teporaries for assemblies from the BCL
|
||||
assembliesPrepared.Add(assemblyName, assemblyPathInBcl);
|
||||
}
|
||||
else
|
||||
{
|
||||
string tempAssemblyPath = Path.Combine(aotTempDir, assemblyName + ".dll");
|
||||
File.Copy(assemblyPath, tempAssemblyPath);
|
||||
assembliesPrepared.Add(assemblyName, tempAssemblyPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (platform == OS.Platforms.iOS)
|
||||
{
|
||||
var architectures = GetEnablediOSArchs(features).ToArray();
|
||||
CompileAssembliesForiOS(exporter, isDebug, architectures, aotOpts, aotTempDir, assembliesPrepared, bclDir);
|
||||
}
|
||||
else if (platform == OS.Platforms.Android)
|
||||
{
|
||||
var abis = GetEnabledAndroidAbis(features).ToArray();
|
||||
CompileAssembliesForAndroid(exporter, isDebug, abis, aotOpts, aotTempDir, assembliesPrepared, bclDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
string bits = features.Contains("64") ? "64" : features.Contains("32") ? "32" : null;
|
||||
CompileAssembliesForDesktop(exporter, platform, isDebug, bits, aotOpts, aotTempDir, outputDataDir, assembliesPrepared, bclDir);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CompileAssembliesForAndroid(ExportPlugin exporter, bool isDebug, string[] abis, AotOptions aotOpts, string aotTempDir, IDictionary<string, string> assemblies, string bclDir)
|
||||
{
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
string assemblyName = assembly.Key;
|
||||
string assemblyPath = assembly.Value;
|
||||
|
||||
// Not sure if the 'lib' prefix is an Android thing or just Godot being picky,
|
||||
// but we use '-aot-' as well just in case to avoid conflicts with other libs.
|
||||
string outputFileName = "lib-aot-" + assemblyName + ".dll.so";
|
||||
|
||||
foreach (string abi in abis)
|
||||
{
|
||||
string aotAbiTempDir = Path.Combine(aotTempDir, abi);
|
||||
string soFilePath = Path.Combine(aotAbiTempDir, outputFileName);
|
||||
|
||||
var compilerArgs = GetAotCompilerArgs(OS.Platforms.Android, isDebug, abi, aotOpts, assemblyPath, soFilePath);
|
||||
|
||||
// Make sure the output directory exists
|
||||
Directory.CreateDirectory(aotAbiTempDir);
|
||||
|
||||
string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", $"{OS.Platforms.Android}-{abi}");
|
||||
|
||||
ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
|
||||
|
||||
// The Godot exporter expects us to pass the abi in the tags parameter
|
||||
exporter.AddSharedObject(soFilePath, tags: new[] { abi });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CompileAssembliesForDesktop(ExportPlugin exporter, string platform, bool isDebug, string bits, AotOptions aotOpts, string aotTempDir, string outputDataDir, IDictionary<string, string> assemblies, string bclDir)
|
||||
{
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
string assemblyName = assembly.Key;
|
||||
string assemblyPath = assembly.Value;
|
||||
|
||||
string outputFileExtension = platform == OS.Platforms.Windows ? ".dll" :
|
||||
platform == OS.Platforms.OSX ? ".dylib" :
|
||||
".so";
|
||||
|
||||
string outputFileName = assemblyName + ".dll" + outputFileExtension;
|
||||
string tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
|
||||
|
||||
var compilerArgs = GetAotCompilerArgs(platform, isDebug, bits, aotOpts, assemblyPath, tempOutputFilePath);
|
||||
|
||||
string compilerDirPath = GetMonoCrossDesktopDirName(platform, bits);
|
||||
|
||||
ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
|
||||
|
||||
if (platform == OS.Platforms.OSX)
|
||||
{
|
||||
exporter.AddSharedObject(tempOutputFilePath, tags: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
string outputDataLibDir = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib");
|
||||
File.Copy(tempOutputFilePath, Path.Combine(outputDataLibDir, outputFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CompileAssembliesForiOS(ExportPlugin exporter, bool isDebug, string[] architectures, AotOptions aotOpts, string aotTempDir, IDictionary<string, string> assemblies, string bclDir)
|
||||
{
|
||||
var cppCode = new StringBuilder();
|
||||
var aotModuleInfoSymbols = new List<string>(assemblies.Count);
|
||||
|
||||
// {arch: paths}
|
||||
var objFilePathsForiOSArch = architectures.ToDictionary(arch => arch, arch => new List<string>(assemblies.Count));
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
string assemblyName = assembly.Key;
|
||||
string assemblyPath = assembly.Value;
|
||||
|
||||
string asmFileName = assemblyName + ".dll.S";
|
||||
string objFileName = assemblyName + ".dll.o";
|
||||
|
||||
foreach (string arch in architectures)
|
||||
{
|
||||
string aotArchTempDir = Path.Combine(aotTempDir, arch);
|
||||
string asmFilePath = Path.Combine(aotArchTempDir, asmFileName);
|
||||
|
||||
var compilerArgs = GetAotCompilerArgs(OS.Platforms.iOS, isDebug, arch, aotOpts, assemblyPath, asmFilePath);
|
||||
|
||||
// Make sure the output directory exists
|
||||
Directory.CreateDirectory(aotArchTempDir);
|
||||
|
||||
string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", $"{OS.Platforms.iOS}-{arch}");
|
||||
|
||||
ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
|
||||
|
||||
// Assembling
|
||||
bool isSim = arch == "i386" || arch == "x86_64"; // Shouldn't really happen as we don't do AOT for the simulator
|
||||
string versionMinName = isSim ? "iphonesimulator" : "iphoneos";
|
||||
string iOSPlatformName = isSim ? "iPhoneSimulator" : "iPhoneOS";
|
||||
const string versionMin = "10.0"; // TODO: Turn this hard-coded version into an exporter setting
|
||||
string iOSSdkPath = Path.Combine(XcodeHelper.XcodePath,
|
||||
$"Contents/Developer/Platforms/{iOSPlatformName}.platform/Developer/SDKs/{iOSPlatformName}.sdk");
|
||||
|
||||
string objFilePath = Path.Combine(aotArchTempDir, objFileName);
|
||||
|
||||
var clangArgs = new List<string>()
|
||||
{
|
||||
"-isysroot", iOSSdkPath,
|
||||
"-Qunused-arguments",
|
||||
$"-m{versionMinName}-version-min={versionMin}",
|
||||
"-arch", arch,
|
||||
"-c",
|
||||
"-o", objFilePath,
|
||||
"-x", "assembler"
|
||||
};
|
||||
|
||||
if (isDebug)
|
||||
clangArgs.Add("-DDEBUG");
|
||||
|
||||
clangArgs.Add(asmFilePath);
|
||||
|
||||
int clangExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("clang"), clangArgs);
|
||||
if (clangExitCode != 0)
|
||||
throw new Exception($"Command 'clang' exited with code: {clangExitCode}");
|
||||
|
||||
objFilePathsForiOSArch[arch].Add(objFilePath);
|
||||
}
|
||||
|
||||
aotModuleInfoSymbols.Add($"mono_aot_module_{AssemblyNameToAotSymbol(assemblyName)}_info");
|
||||
}
|
||||
|
||||
// Generate driver code
|
||||
cppCode.AppendLine("#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)");
|
||||
cppCode.AppendLine("#define IOS_DEVICE");
|
||||
cppCode.AppendLine("#endif");
|
||||
|
||||
cppCode.AppendLine("#ifdef IOS_DEVICE");
|
||||
cppCode.AppendLine("extern \"C\" {");
|
||||
cppCode.AppendLine("// Mono API");
|
||||
cppCode.AppendLine(@"
|
||||
typedef enum {
|
||||
MONO_AOT_MODE_NONE,
|
||||
MONO_AOT_MODE_NORMAL,
|
||||
MONO_AOT_MODE_HYBRID,
|
||||
MONO_AOT_MODE_FULL,
|
||||
MONO_AOT_MODE_LLVMONLY,
|
||||
MONO_AOT_MODE_INTERP,
|
||||
MONO_AOT_MODE_INTERP_LLVMONLY,
|
||||
MONO_AOT_MODE_LLVMONLY_INTERP,
|
||||
MONO_AOT_MODE_LAST = 1000,
|
||||
} MonoAotMode;");
|
||||
cppCode.AppendLine("void mono_jit_set_aot_mode(MonoAotMode);");
|
||||
cppCode.AppendLine("void mono_aot_register_module(void *);");
|
||||
|
||||
if (aotOpts.UseInterpreter)
|
||||
{
|
||||
cppCode.AppendLine("void mono_ee_interp_init(const char *);");
|
||||
cppCode.AppendLine("void mono_icall_table_init();");
|
||||
cppCode.AppendLine("void mono_marshal_ilgen_init();");
|
||||
cppCode.AppendLine("void mono_method_builder_ilgen_init();");
|
||||
cppCode.AppendLine("void mono_sgen_mono_ilgen_init();");
|
||||
}
|
||||
|
||||
foreach (string symbol in aotModuleInfoSymbols)
|
||||
cppCode.AppendLine($"extern void *{symbol};");
|
||||
|
||||
cppCode.AppendLine("void gd_mono_setup_aot() {");
|
||||
|
||||
foreach (string symbol in aotModuleInfoSymbols)
|
||||
cppCode.AppendLine($"\tmono_aot_register_module({symbol});");
|
||||
|
||||
if (aotOpts.UseInterpreter)
|
||||
{
|
||||
cppCode.AppendLine("\tmono_icall_table_init();");
|
||||
cppCode.AppendLine("\tmono_marshal_ilgen_init();");
|
||||
cppCode.AppendLine("\tmono_method_builder_ilgen_init();");
|
||||
cppCode.AppendLine("\tmono_sgen_mono_ilgen_init();");
|
||||
cppCode.AppendLine("\tmono_ee_interp_init(0);");
|
||||
}
|
||||
|
||||
string aotModeStr = null;
|
||||
|
||||
if (aotOpts.LLVMOnly)
|
||||
{
|
||||
aotModeStr = "MONO_AOT_MODE_LLVMONLY"; // --aot=llvmonly
|
||||
}
|
||||
else
|
||||
{
|
||||
if (aotOpts.UseInterpreter)
|
||||
aotModeStr = "MONO_AOT_MODE_INTERP"; // --aot=interp or --aot=interp,full
|
||||
else if (aotOpts.FullAot)
|
||||
aotModeStr = "MONO_AOT_MODE_FULL"; // --aot=full
|
||||
}
|
||||
|
||||
// One of the options above is always set for iOS
|
||||
Debug.Assert(aotModeStr != null);
|
||||
|
||||
cppCode.AppendLine($"\tmono_jit_set_aot_mode({aotModeStr});");
|
||||
|
||||
cppCode.AppendLine("} // gd_mono_setup_aot");
|
||||
cppCode.AppendLine("} // extern \"C\"");
|
||||
cppCode.AppendLine("#endif // IOS_DEVICE");
|
||||
|
||||
// Add the driver code to the Xcode project
|
||||
exporter.AddIosCppCode(cppCode.ToString());
|
||||
|
||||
// Archive the AOT object files into a static library
|
||||
|
||||
var arFilePathsForAllArchs = new List<string>();
|
||||
string projectAssemblyName = GodotSharpEditor.ProjectAssemblyName;
|
||||
|
||||
foreach (var archPathsPair in objFilePathsForiOSArch)
|
||||
{
|
||||
string arch = archPathsPair.Key;
|
||||
var objFilePaths = archPathsPair.Value;
|
||||
|
||||
string arOutputFilePath = Path.Combine(aotTempDir, $"lib-aot-{projectAssemblyName}.{arch}.a");
|
||||
|
||||
var arArgs = new List<string>()
|
||||
{
|
||||
"cr",
|
||||
arOutputFilePath
|
||||
};
|
||||
|
||||
foreach (string objFilePath in objFilePaths)
|
||||
arArgs.Add(objFilePath);
|
||||
|
||||
int arExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("ar"), arArgs);
|
||||
if (arExitCode != 0)
|
||||
throw new Exception($"Command 'ar' exited with code: {arExitCode}");
|
||||
|
||||
arFilePathsForAllArchs.Add(arOutputFilePath);
|
||||
}
|
||||
|
||||
// It's lipo time
|
||||
|
||||
string fatOutputFileName = $"lib-aot-{projectAssemblyName}.fat.a";
|
||||
string fatOutputFilePath = Path.Combine(aotTempDir, fatOutputFileName);
|
||||
|
||||
var lipoArgs = new List<string>();
|
||||
lipoArgs.Add("-create");
|
||||
lipoArgs.AddRange(arFilePathsForAllArchs);
|
||||
lipoArgs.Add("-output");
|
||||
lipoArgs.Add(fatOutputFilePath);
|
||||
|
||||
int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs);
|
||||
if (lipoExitCode != 0)
|
||||
throw new Exception($"Command 'lipo' exited with code: {lipoExitCode}");
|
||||
|
||||
// TODO: Add the AOT lib and interpreter libs as device only to supress warnings when targeting the simulator
|
||||
|
||||
// Add the fat AOT static library to the Xcode project
|
||||
exporter.AddIosProjectStaticLib(fatOutputFilePath);
|
||||
|
||||
// Add the required Mono libraries to the Xcode project
|
||||
|
||||
string MonoLibFile(string libFileName) => libFileName + ".iphone.fat.a";
|
||||
|
||||
string MonoLibFromTemplate(string libFileName) =>
|
||||
Path.Combine(Internal.FullTemplatesDir, "iphone-mono-libs", MonoLibFile(libFileName));
|
||||
|
||||
exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmonosgen-2.0"));
|
||||
|
||||
exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-native"));
|
||||
|
||||
if (aotOpts.UseInterpreter)
|
||||
{
|
||||
exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-ee-interp"));
|
||||
exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-icall-table"));
|
||||
exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-ilgen"));
|
||||
}
|
||||
|
||||
// TODO: Turn into an exporter option
|
||||
bool enableProfiling = false;
|
||||
if (enableProfiling)
|
||||
exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-profiler-log"));
|
||||
|
||||
// Add frameworks required by Mono to the Xcode project
|
||||
exporter.AddIosFramework("libiconv.tbd");
|
||||
exporter.AddIosFramework("GSS.framework");
|
||||
exporter.AddIosFramework("CFNetwork.framework");
|
||||
|
||||
// Force load and export dynamic are needed for the linker to not strip required symbols.
|
||||
// In theory we shouldn't be relying on this for P/Invoked functions (as is the case with
|
||||
// functions in System.Native/libmono-native). Instead, we should use cecil to search for
|
||||
// DllImports in assemblies and pass them to 'ld' as '-u/--undefined {pinvoke_symbol}'.
|
||||
exporter.AddIosLinkerFlags("-rdynamic");
|
||||
exporter.AddIosLinkerFlags($"-force_load \"$(SRCROOT)/{MonoLibFile("libmono-native")}\"");
|
||||
}
|
||||
|
||||
/// Converts an assembly name to a valid symbol name in the same way the AOT compiler does
|
||||
private static string AssemblyNameToAotSymbol(string assemblyName)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
foreach (var charByte in Encoding.UTF8.GetBytes(assemblyName))
|
||||
{
|
||||
char @char = (char)charByte;
|
||||
builder.Append(Char.IsLetterOrDigit(@char) || @char == '_' ? @char : '_');
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetAotCompilerArgs(string platform, bool isDebug, string target, AotOptions aotOpts, string assemblyPath, string outputFilePath)
|
||||
{
|
||||
// TODO: LLVM
|
||||
|
||||
bool aotSoftDebug = isDebug && !aotOpts.EnableLLVM;
|
||||
bool aotDwarfDebug = platform == OS.Platforms.iOS;
|
||||
|
||||
var aotOptions = new List<string>();
|
||||
var optimizerOptions = new List<string>();
|
||||
|
||||
if (aotOpts.LLVMOnly)
|
||||
{
|
||||
aotOptions.Add("llvmonly");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Can be both 'interp' and 'full'
|
||||
if (aotOpts.UseInterpreter)
|
||||
aotOptions.Add("interp");
|
||||
if (aotOpts.FullAot)
|
||||
aotOptions.Add("full");
|
||||
}
|
||||
|
||||
aotOptions.Add(aotSoftDebug ? "soft-debug" : "nodebug");
|
||||
|
||||
if (aotDwarfDebug)
|
||||
aotOptions.Add("dwarfdebug");
|
||||
|
||||
if (platform == OS.Platforms.Android)
|
||||
{
|
||||
string abi = target;
|
||||
|
||||
string androidToolchain = aotOpts.ToolchainPath;
|
||||
|
||||
if (string.IsNullOrEmpty(androidToolchain))
|
||||
{
|
||||
androidToolchain = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "android-toolchains", $"{abi}"); // TODO: $"{abi}-{apiLevel}{(clang?"clang":"")}"
|
||||
|
||||
if (!Directory.Exists(androidToolchain))
|
||||
throw new FileNotFoundException("Missing android toolchain. Specify one in the AOT export settings.");
|
||||
}
|
||||
else if (!Directory.Exists(androidToolchain))
|
||||
{
|
||||
throw new FileNotFoundException("Android toolchain not found: " + androidToolchain);
|
||||
}
|
||||
|
||||
var androidToolPrefixes = new Dictionary<string, string>
|
||||
{
|
||||
["armeabi-v7a"] = "arm-linux-androideabi-",
|
||||
["arm64-v8a"] = "aarch64-linux-android-",
|
||||
["x86"] = "i686-linux-android-",
|
||||
["x86_64"] = "x86_64-linux-android-"
|
||||
};
|
||||
|
||||
aotOptions.Add("tool-prefix=" + Path.Combine(androidToolchain, "bin", androidToolPrefixes[abi]));
|
||||
|
||||
string triple = GetAndroidTriple(abi);
|
||||
aotOptions.Add($"mtriple={triple}");
|
||||
}
|
||||
else if (platform == OS.Platforms.iOS)
|
||||
{
|
||||
if (!aotOpts.LLVMOnly && !aotOpts.UseInterpreter)
|
||||
optimizerOptions.Add("gsharedvt");
|
||||
|
||||
aotOptions.Add("static");
|
||||
|
||||
// I couldn't get the Mono cross-compiler to do assembling, so we'll have to do it ourselves
|
||||
aotOptions.Add("asmonly");
|
||||
|
||||
aotOptions.Add("direct-icalls");
|
||||
|
||||
if (aotSoftDebug)
|
||||
aotOptions.Add("no-direct-calls");
|
||||
|
||||
if (aotOpts.LLVMOnly || !aotOpts.UseInterpreter)
|
||||
aotOptions.Add("direct-pinvoke");
|
||||
|
||||
string arch = target;
|
||||
aotOptions.Add($"mtriple={arch}-ios");
|
||||
}
|
||||
|
||||
aotOptions.Add($"outfile={outputFilePath}");
|
||||
|
||||
if (aotOpts.EnableLLVM)
|
||||
{
|
||||
aotOptions.Add($"llvm-path={aotOpts.LLVMPath}");
|
||||
aotOptions.Add($"llvm-outfile={aotOpts.LLVMOutputPath}");
|
||||
}
|
||||
|
||||
if (aotOpts.ExtraAotOptions.Length > 0)
|
||||
aotOptions.AddRange(aotOpts.ExtraAotOptions);
|
||||
|
||||
if (aotOpts.ExtraOptimizerOptions.Length > 0)
|
||||
optimizerOptions.AddRange(aotOpts.ExtraOptimizerOptions);
|
||||
|
||||
string EscapeOption(string option) => option.Contains(',') ? $"\"{option}\"" : option;
|
||||
string OptionsToString(IEnumerable<string> options) => string.Join(",", options.Select(EscapeOption));
|
||||
|
||||
var runtimeArgs = new List<string>();
|
||||
|
||||
// The '--debug' runtime option is required when using the 'soft-debug' and 'dwarfdebug' AOT options
|
||||
if (aotSoftDebug || aotDwarfDebug)
|
||||
runtimeArgs.Add("--debug");
|
||||
|
||||
if (aotOpts.EnableLLVM)
|
||||
runtimeArgs.Add("--llvm");
|
||||
|
||||
runtimeArgs.Add(aotOptions.Count > 0 ? $"--aot={OptionsToString(aotOptions)}" : "--aot");
|
||||
|
||||
if (optimizerOptions.Count > 0)
|
||||
runtimeArgs.Add($"-O={OptionsToString(optimizerOptions)}");
|
||||
|
||||
runtimeArgs.Add(assemblyPath);
|
||||
|
||||
return runtimeArgs;
|
||||
}
|
||||
|
||||
private static void ExecuteCompiler(string compiler, IEnumerable<string> compilerArgs, string bclDir)
|
||||
{
|
||||
// TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
|
||||
string CmdLineArgsToString(IEnumerable<string> args)
|
||||
{
|
||||
// Not perfect, but as long as we are careful...
|
||||
return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
|
||||
}
|
||||
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo = new ProcessStartInfo(compiler, CmdLineArgsToString(compilerArgs))
|
||||
{
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
process.StartInfo.EnvironmentVariables.Remove("MONO_ENV_OPTIONS");
|
||||
process.StartInfo.EnvironmentVariables.Remove("MONO_THREADS_SUSPEND");
|
||||
process.StartInfo.EnvironmentVariables.Add("MONO_PATH", bclDir);
|
||||
|
||||
Console.WriteLine($"Running: \"{process.StartInfo.FileName}\" {process.StartInfo.Arguments}");
|
||||
|
||||
if (!process.Start())
|
||||
throw new Exception("Failed to start process for Mono AOT compiler");
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
throw new Exception($"Mono AOT compiler exited with code: {process.ExitCode}");
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetEnablediOSArchs(string[] features)
|
||||
{
|
||||
var iosArchs = new[]
|
||||
{
|
||||
"armv7",
|
||||
"arm64"
|
||||
};
|
||||
|
||||
return iosArchs.Where(features.Contains);
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetEnabledAndroidAbis(string[] features)
|
||||
{
|
||||
var androidAbis = new[]
|
||||
{
|
||||
"armeabi-v7a",
|
||||
"arm64-v8a",
|
||||
"x86",
|
||||
"x86_64"
|
||||
};
|
||||
|
||||
return androidAbis.Where(features.Contains);
|
||||
}
|
||||
|
||||
private static string GetAndroidTriple(string abi)
|
||||
{
|
||||
var abiArchs = new Dictionary<string, string>
|
||||
{
|
||||
["armeabi-v7a"] = "armv7",
|
||||
["arm64-v8a"] = "aarch64-v8a",
|
||||
["x86"] = "i686",
|
||||
["x86_64"] = "x86_64"
|
||||
};
|
||||
|
||||
string arch = abiArchs[abi];
|
||||
|
||||
return $"{arch}-linux-android";
|
||||
}
|
||||
|
||||
private static string GetMonoCrossDesktopDirName(string platform, string bits)
|
||||
{
|
||||
switch (platform)
|
||||
{
|
||||
case OS.Platforms.Windows:
|
||||
case OS.Platforms.UWP:
|
||||
{
|
||||
string arch = bits == "64" ? "x86_64" : "i686";
|
||||
return $"windows-{arch}";
|
||||
}
|
||||
case OS.Platforms.OSX:
|
||||
{
|
||||
Debug.Assert(bits == null || bits == "64");
|
||||
string arch = "x86_64";
|
||||
return $"{platform}-{arch}";
|
||||
}
|
||||
case OS.Platforms.X11:
|
||||
case OS.Platforms.Server:
|
||||
{
|
||||
string arch = bits == "64" ? "x86_64" : "i686";
|
||||
return $"linux-{arch}";
|
||||
}
|
||||
case OS.Platforms.Haiku:
|
||||
{
|
||||
string arch = bits == "64" ? "x86_64" : "i686";
|
||||
return $"{platform}-{arch}";
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException($"Platform not supported: {platform}");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Replace this for a specific path for each platform
|
||||
private static string FindCrossCompiler(string monoCrossBin)
|
||||
{
|
||||
string exeExt = OS.IsWindows ? ".exe" : string.Empty;
|
||||
|
||||
var files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono-sgen{exeExt}", SearchOption.TopDirectoryOnly);
|
||||
if (files.Length > 0)
|
||||
return Path.Combine(monoCrossBin, files[0].Name);
|
||||
|
||||
throw new FileNotFoundException($"Cannot find the mono runtime executable in {monoCrossBin}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,15 +29,13 @@ namespace GodotTools.Export
|
|||
All = CJK | MidEast | Other | Rare | West
|
||||
}
|
||||
|
||||
private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string platform)
|
||||
private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string bclDir)
|
||||
{
|
||||
var codesets = (I18NCodesets) ProjectSettings.GetSetting("mono/export/i18n_codesets");
|
||||
var codesets = (I18NCodesets)ProjectSettings.GetSetting("mono/export/i18n_codesets");
|
||||
|
||||
if (codesets == I18NCodesets.None)
|
||||
return;
|
||||
|
||||
string bclDir = DeterminePlatformBclDir(platform) ?? typeof(object).Assembly.Location.GetBaseDir();
|
||||
|
||||
void AddI18NAssembly(string name) => assemblies.Add(name, Path.Combine(bclDir, $"{name}.dll"));
|
||||
|
||||
AddI18NAssembly("I18N");
|
||||
|
@ -73,6 +71,7 @@ namespace GodotTools.Export
|
|||
|
||||
GlobalDef("mono/export/aot/enabled", false);
|
||||
GlobalDef("mono/export/aot/full_aot", false);
|
||||
GlobalDef("mono/export/aot/use_interpreter", true);
|
||||
|
||||
// --aot or --aot=opt1,opt2 (use 'mono --aot=help AuxAssembly.dll' to list AOT options)
|
||||
GlobalDef("mono/export/aot/extra_aot_options", new string[] { });
|
||||
|
@ -86,9 +85,11 @@ namespace GodotTools.Export
|
|||
|
||||
private void AddFile(string srcPath, string dstPath, bool remap = false)
|
||||
{
|
||||
// Add file to the PCK
|
||||
AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap);
|
||||
}
|
||||
|
||||
// With this method we can override how a file is exported in the PCK
|
||||
public override void _ExportFile(string path, string type, string[] features)
|
||||
{
|
||||
base._ExportFile(path, type, features);
|
||||
|
@ -110,6 +111,8 @@ namespace GodotTools.Export
|
|||
// Sadly, Godot prints errors when adding an empty file (nothing goes wrong, it's just noise).
|
||||
// Because of this, we add a file which contains a line break.
|
||||
AddFile(path, System.Text.Encoding.UTF8.GetBytes("\n"), remap: false);
|
||||
|
||||
// Tell the Godot exporter that we already took care of the file
|
||||
Skip();
|
||||
}
|
||||
}
|
||||
|
@ -167,12 +170,7 @@ namespace GodotTools.Export
|
|||
|
||||
var dependencies = new Godot.Collections.Dictionary<string, string>();
|
||||
|
||||
var projectDllName = (string)ProjectSettings.GetSetting("application/config/name");
|
||||
if (projectDllName.Empty())
|
||||
{
|
||||
projectDllName = "UnnamedProject";
|
||||
}
|
||||
|
||||
string projectDllName = GodotSharpEditor.ProjectAssemblyName;
|
||||
string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
|
||||
string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
|
||||
|
||||
|
@ -189,10 +187,12 @@ namespace GodotTools.Export
|
|||
dependencies["Mono.Android"] = monoAndroidAssemblyPath;
|
||||
}
|
||||
|
||||
var initialDependencies = dependencies.Duplicate();
|
||||
internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, DeterminePlatformBclDir(platform), dependencies);
|
||||
string bclDir = DeterminePlatformBclDir(platform);
|
||||
|
||||
AddI18NAssemblies(dependencies, platform);
|
||||
var initialDependencies = dependencies.Duplicate();
|
||||
internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, bclDir, dependencies);
|
||||
|
||||
AddI18NAssemblies(dependencies, bclDir);
|
||||
|
||||
string outputDataDir = null;
|
||||
|
||||
|
@ -227,11 +227,34 @@ namespace GodotTools.Export
|
|||
}
|
||||
}
|
||||
|
||||
// AOT
|
||||
// AOT compilation
|
||||
bool aotEnabled = platform == OS.Platforms.iOS || (bool)ProjectSettings.GetSetting("mono/export/aot/enabled");
|
||||
|
||||
if ((bool)ProjectSettings.GetSetting("mono/export/aot/enabled"))
|
||||
if (aotEnabled)
|
||||
{
|
||||
AotCompileDependencies(features, platform, isDebug, outputDir, outputDataDir, dependencies);
|
||||
string aotToolchainPath = null;
|
||||
|
||||
if (platform == OS.Platforms.Android)
|
||||
aotToolchainPath = (string)ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path");
|
||||
|
||||
if (aotToolchainPath == string.Empty)
|
||||
aotToolchainPath = null; // Don't risk it being used as current working dir
|
||||
|
||||
// TODO: LLVM settings are hard-coded and disabled for now
|
||||
var aotOpts = new AotOptions
|
||||
{
|
||||
EnableLLVM = false,
|
||||
LLVMOnly = false,
|
||||
LLVMPath = "",
|
||||
LLVMOutputPath = "",
|
||||
FullAot = platform == OS.Platforms.iOS || (bool)(ProjectSettings.GetSetting("mono/export/aot/full_aot") ?? false),
|
||||
UseInterpreter = (bool)ProjectSettings.GetSetting("mono/export/aot/use_interpreter"),
|
||||
ExtraAotOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_aot_options") ?? new string[] { },
|
||||
ExtraOptimizerOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options") ?? new string[] { },
|
||||
ToolchainPath = aotToolchainPath
|
||||
};
|
||||
|
||||
AotBuilder.CompileAssemblies(this, aotOpts, features, platform, isDebug, bclDir, outputDir, outputDataDir, dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,7 +281,8 @@ namespace GodotTools.Export
|
|||
{
|
||||
string target = isDebug ? "release_debug" : "release";
|
||||
|
||||
// NOTE: Bits is ok for now as all platforms with a data directory have it, but that may change in the future.
|
||||
// NOTE: Bits is ok for now as all platforms with a data directory only have one or two architectures.
|
||||
// However, this may change in the future if we add arm linux or windows desktop templates.
|
||||
string bits = features.Contains("64") ? "64" : "32";
|
||||
|
||||
string TemplateDirName() => $"data.mono.{platform}.{bits}.{target}";
|
||||
|
@ -284,7 +308,7 @@ namespace GodotTools.Export
|
|||
if (!validTemplatePathFound)
|
||||
throw new FileNotFoundException("Data template directory not found", templateDirPath);
|
||||
|
||||
string outputDataDir = Path.Combine(outputDir, DataDirName);
|
||||
string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject());
|
||||
|
||||
if (Directory.Exists(outputDataDir))
|
||||
Directory.Delete(outputDataDir, recursive: true); // Clean first
|
||||
|
@ -304,333 +328,10 @@ namespace GodotTools.Export
|
|||
return outputDataDir;
|
||||
}
|
||||
|
||||
private void AotCompileDependencies(string[] features, string platform, bool isDebug, string outputDir, string outputDataDir, IDictionary<string, string> dependencies)
|
||||
{
|
||||
// TODO: WASM
|
||||
|
||||
string bclDir = DeterminePlatformBclDir(platform) ?? typeof(object).Assembly.Location.GetBaseDir();
|
||||
|
||||
string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}");
|
||||
|
||||
if (!Directory.Exists(aotTempDir))
|
||||
Directory.CreateDirectory(aotTempDir);
|
||||
|
||||
var assemblies = new Dictionary<string, string>();
|
||||
|
||||
foreach (var dependency in dependencies)
|
||||
{
|
||||
string assemblyName = dependency.Key;
|
||||
string assemblyPath = dependency.Value;
|
||||
|
||||
string assemblyPathInBcl = Path.Combine(bclDir, assemblyName + ".dll");
|
||||
|
||||
if (File.Exists(assemblyPathInBcl))
|
||||
{
|
||||
// Don't create teporaries for assemblies from the BCL
|
||||
assemblies.Add(assemblyName, assemblyPathInBcl);
|
||||
}
|
||||
else
|
||||
{
|
||||
string tempAssemblyPath = Path.Combine(aotTempDir, assemblyName + ".dll");
|
||||
File.Copy(assemblyPath, tempAssemblyPath);
|
||||
assemblies.Add(assemblyName, tempAssemblyPath);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
string assemblyName = assembly.Key;
|
||||
string assemblyPath = assembly.Value;
|
||||
|
||||
string sharedLibExtension = platform == OS.Platforms.Windows ? ".dll" :
|
||||
platform == OS.Platforms.OSX ? ".dylib" :
|
||||
platform == OS.Platforms.HTML5 ? ".wasm" :
|
||||
".so";
|
||||
|
||||
string outputFileName = assemblyName + ".dll" + sharedLibExtension;
|
||||
|
||||
if (platform == OS.Platforms.Android)
|
||||
{
|
||||
// Not sure if the 'lib' prefix is an Android thing or just Godot being picky,
|
||||
// but we use '-aot-' as well just in case to avoid conflicts with other libs.
|
||||
outputFileName = "lib-aot-" + outputFileName;
|
||||
}
|
||||
|
||||
string outputFilePath = null;
|
||||
string tempOutputFilePath;
|
||||
|
||||
switch (platform)
|
||||
{
|
||||
case OS.Platforms.OSX:
|
||||
tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
|
||||
break;
|
||||
case OS.Platforms.Android:
|
||||
tempOutputFilePath = Path.Combine(aotTempDir, "%%ANDROID_ABI%%", outputFileName);
|
||||
break;
|
||||
case OS.Platforms.HTML5:
|
||||
tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
|
||||
outputFilePath = Path.Combine(outputDir, outputFileName);
|
||||
break;
|
||||
default:
|
||||
tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
|
||||
outputFilePath = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib", outputFileName);
|
||||
break;
|
||||
}
|
||||
|
||||
var data = new Dictionary<string, string>();
|
||||
var enabledAndroidAbis = platform == OS.Platforms.Android ? GetEnabledAndroidAbis(features).ToArray() : null;
|
||||
|
||||
if (platform == OS.Platforms.Android)
|
||||
{
|
||||
Debug.Assert(enabledAndroidAbis != null);
|
||||
|
||||
foreach (var abi in enabledAndroidAbis)
|
||||
{
|
||||
data["abi"] = abi;
|
||||
var outputFilePathForThisAbi = tempOutputFilePath.Replace("%%ANDROID_ABI%%", abi);
|
||||
|
||||
AotCompileAssembly(platform, isDebug, data, assemblyPath, outputFilePathForThisAbi);
|
||||
|
||||
AddSharedObject(outputFilePathForThisAbi, tags: new[] { abi });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string bits = features.Contains("64") ? "64" : features.Contains("64") ? "32" : null;
|
||||
|
||||
if (bits != null)
|
||||
data["bits"] = bits;
|
||||
|
||||
AotCompileAssembly(platform, isDebug, data, assemblyPath, tempOutputFilePath);
|
||||
|
||||
if (platform == OS.Platforms.OSX)
|
||||
{
|
||||
AddSharedObject(tempOutputFilePath, tags: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Assert(outputFilePath != null);
|
||||
File.Copy(tempOutputFilePath, outputFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AotCompileAssembly(string platform, bool isDebug, Dictionary<string, string> data, string assemblyPath, string outputFilePath)
|
||||
{
|
||||
// Make sure the output directory exists
|
||||
Directory.CreateDirectory(outputFilePath.GetBaseDir());
|
||||
|
||||
string exeExt = OS.IsWindows ? ".exe" : string.Empty;
|
||||
|
||||
string monoCrossDirName = DetermineMonoCrossDirName(platform, data);
|
||||
string monoCrossRoot = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", monoCrossDirName);
|
||||
string monoCrossBin = Path.Combine(monoCrossRoot, "bin");
|
||||
|
||||
string toolPrefix = DetermineToolPrefix(monoCrossBin);
|
||||
string monoExeName = System.IO.File.Exists(Path.Combine(monoCrossBin, $"{toolPrefix}mono{exeExt}")) ? "mono" : "mono-sgen";
|
||||
|
||||
string compilerCommand = Path.Combine(monoCrossBin, $"{toolPrefix}{monoExeName}{exeExt}");
|
||||
|
||||
bool fullAot = (bool)ProjectSettings.GetSetting("mono/export/aot/full_aot");
|
||||
|
||||
string EscapeOption(string option) => option.Contains(',') ? $"\"{option}\"" : option;
|
||||
string OptionsToString(IEnumerable<string> options) => string.Join(",", options.Select(EscapeOption));
|
||||
|
||||
var aotOptions = new List<string>();
|
||||
var optimizerOptions = new List<string>();
|
||||
|
||||
if (fullAot)
|
||||
aotOptions.Add("full");
|
||||
|
||||
aotOptions.Add(isDebug ? "soft-debug" : "nodebug");
|
||||
|
||||
if (platform == OS.Platforms.Android)
|
||||
{
|
||||
string abi = data["abi"];
|
||||
|
||||
string androidToolchain = (string)ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path");
|
||||
|
||||
if (string.IsNullOrEmpty(androidToolchain))
|
||||
{
|
||||
androidToolchain = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "android-toolchains", $"{abi}"); // TODO: $"{abi}-{apiLevel}{(clang?"clang":"")}"
|
||||
|
||||
if (!Directory.Exists(androidToolchain))
|
||||
throw new FileNotFoundException("Missing android toolchain. Specify one in the AOT export settings.");
|
||||
}
|
||||
else if (!Directory.Exists(androidToolchain))
|
||||
{
|
||||
throw new FileNotFoundException("Android toolchain not found: " + androidToolchain);
|
||||
}
|
||||
|
||||
var androidToolPrefixes = new Dictionary<string, string>
|
||||
{
|
||||
["armeabi-v7a"] = "arm-linux-androideabi-",
|
||||
["arm64-v8a"] = "aarch64-linux-android-",
|
||||
["x86"] = "i686-linux-android-",
|
||||
["x86_64"] = "x86_64-linux-android-"
|
||||
};
|
||||
|
||||
aotOptions.Add("tool-prefix=" + Path.Combine(androidToolchain, "bin", androidToolPrefixes[abi]));
|
||||
|
||||
string triple = GetAndroidTriple(abi);
|
||||
aotOptions.Add($"mtriple={triple}");
|
||||
}
|
||||
|
||||
aotOptions.Add($"outfile={outputFilePath}");
|
||||
|
||||
var extraAotOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_aot_options");
|
||||
var extraOptimizerOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options");
|
||||
|
||||
if (extraAotOptions.Length > 0)
|
||||
aotOptions.AddRange(extraAotOptions);
|
||||
|
||||
if (extraOptimizerOptions.Length > 0)
|
||||
optimizerOptions.AddRange(extraOptimizerOptions);
|
||||
|
||||
var compilerArgs = new List<string>();
|
||||
|
||||
if (isDebug)
|
||||
compilerArgs.Add("--debug"); // Required for --aot=soft-debug
|
||||
|
||||
compilerArgs.Add(aotOptions.Count > 0 ? $"--aot={OptionsToString(aotOptions)}" : "--aot");
|
||||
|
||||
if (optimizerOptions.Count > 0)
|
||||
compilerArgs.Add($"-O={OptionsToString(optimizerOptions)}");
|
||||
|
||||
compilerArgs.Add(ProjectSettings.GlobalizePath(assemblyPath));
|
||||
|
||||
// TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
|
||||
string CmdLineArgsToString(IEnumerable<string> args)
|
||||
{
|
||||
// Not perfect, but as long as we are careful...
|
||||
return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
|
||||
}
|
||||
|
||||
using (var process = new Process())
|
||||
{
|
||||
process.StartInfo = new ProcessStartInfo(compilerCommand, CmdLineArgsToString(compilerArgs))
|
||||
{
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
string platformBclDir = DeterminePlatformBclDir(platform);
|
||||
process.StartInfo.EnvironmentVariables.Add("MONO_PATH", string.IsNullOrEmpty(platformBclDir) ?
|
||||
typeof(object).Assembly.Location.GetBaseDir() :
|
||||
platformBclDir);
|
||||
|
||||
Console.WriteLine($"Running: \"{process.StartInfo.FileName}\" {process.StartInfo.Arguments}");
|
||||
|
||||
if (!process.Start())
|
||||
throw new Exception("Failed to start process for Mono AOT compiler");
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
throw new Exception($"Mono AOT compiler exited with error code: {process.ExitCode}");
|
||||
|
||||
if (!System.IO.File.Exists(outputFilePath))
|
||||
throw new Exception("Mono AOT compiler finished successfully but the output file is missing");
|
||||
}
|
||||
}
|
||||
|
||||
private static string DetermineMonoCrossDirName(string platform, IReadOnlyDictionary<string, string> data)
|
||||
{
|
||||
switch (platform)
|
||||
{
|
||||
case OS.Platforms.Windows:
|
||||
case OS.Platforms.UWP:
|
||||
{
|
||||
string arch = data["bits"] == "64" ? "x86_64" : "i686";
|
||||
return $"windows-{arch}";
|
||||
}
|
||||
case OS.Platforms.OSX:
|
||||
{
|
||||
string arch = "x86_64";
|
||||
return $"{platform}-{arch}";
|
||||
}
|
||||
case OS.Platforms.X11:
|
||||
case OS.Platforms.Server:
|
||||
{
|
||||
string arch = data["bits"] == "64" ? "x86_64" : "i686";
|
||||
return $"linux-{arch}";
|
||||
}
|
||||
case OS.Platforms.Haiku:
|
||||
{
|
||||
string arch = data["bits"] == "64" ? "x86_64" : "i686";
|
||||
return $"{platform}-{arch}";
|
||||
}
|
||||
case OS.Platforms.Android:
|
||||
{
|
||||
string abi = data["abi"];
|
||||
return $"{platform}-{abi}";
|
||||
}
|
||||
case OS.Platforms.HTML5:
|
||||
return "wasm-wasm32";
|
||||
default:
|
||||
throw new NotSupportedException($"Platform not supported: {platform}");
|
||||
}
|
||||
}
|
||||
|
||||
private static string DetermineToolPrefix(string monoCrossBin)
|
||||
{
|
||||
string exeExt = OS.IsWindows ? ".exe" : string.Empty;
|
||||
|
||||
if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono{exeExt}")))
|
||||
return string.Empty;
|
||||
|
||||
if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono-sgen{exeExt}" + exeExt)))
|
||||
return string.Empty;
|
||||
|
||||
var files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono{exeExt}" + exeExt, SearchOption.TopDirectoryOnly);
|
||||
if (files.Length > 0)
|
||||
{
|
||||
string fileName = files[0].Name;
|
||||
return fileName.Substring(0, fileName.Length - $"mono{exeExt}".Length);
|
||||
}
|
||||
|
||||
files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono-sgen{exeExt}" + exeExt, SearchOption.TopDirectoryOnly);
|
||||
if (files.Length > 0)
|
||||
{
|
||||
string fileName = files[0].Name;
|
||||
return fileName.Substring(0, fileName.Length - $"mono-sgen{exeExt}".Length);
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"Cannot find the mono runtime executable in {monoCrossBin}");
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetEnabledAndroidAbis(string[] features)
|
||||
{
|
||||
var androidAbis = new[]
|
||||
{
|
||||
"armeabi-v7a",
|
||||
"arm64-v8a",
|
||||
"x86",
|
||||
"x86_64"
|
||||
};
|
||||
|
||||
return androidAbis.Where(features.Contains);
|
||||
}
|
||||
|
||||
private static string GetAndroidTriple(string abi)
|
||||
{
|
||||
var abiArchs = new Dictionary<string, string>
|
||||
{
|
||||
["armeabi-v7a"] = "armv7",
|
||||
["arm64-v8a"] = "aarch64-v8a",
|
||||
["x86"] = "i686",
|
||||
["x86_64"] = "x86_64"
|
||||
};
|
||||
|
||||
string arch = abiArchs[abi];
|
||||
|
||||
return $"{arch}-linux-android";
|
||||
}
|
||||
|
||||
private static bool PlatformHasTemplateDir(string platform)
|
||||
{
|
||||
// OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest.
|
||||
return !new[] { OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.HTML5 }.Contains(platform);
|
||||
return !new[] { OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform);
|
||||
}
|
||||
|
||||
private static string DeterminePlatformFromFeatures(IEnumerable<string> features)
|
||||
|
@ -665,7 +366,7 @@ namespace GodotTools.Export
|
|||
if (PlatformRequiresCustomBcl(platform))
|
||||
throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}");
|
||||
|
||||
platformBclDir = null; // Use the one we're running on
|
||||
platformBclDir = typeof(object).Assembly.Location; // Use the one we're running on
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,7 +379,7 @@ namespace GodotTools.Export
|
|||
/// </summary>
|
||||
private static bool PlatformRequiresCustomBcl(string platform)
|
||||
{
|
||||
if (new[] { OS.Platforms.Android, OS.Platforms.HTML5 }.Contains(platform))
|
||||
if (new[] { OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform))
|
||||
return true;
|
||||
|
||||
// The 'net_4_x' BCL is not compatible between Windows and the other platforms.
|
||||
|
@ -707,6 +408,8 @@ namespace GodotTools.Export
|
|||
return "net_4_x";
|
||||
case OS.Platforms.Android:
|
||||
return "monodroid";
|
||||
case OS.Platforms.iOS:
|
||||
return "monotouch";
|
||||
case OS.Platforms.HTML5:
|
||||
return "wasm";
|
||||
default:
|
||||
|
@ -714,14 +417,11 @@ namespace GodotTools.Export
|
|||
}
|
||||
}
|
||||
|
||||
private static string DataDirName
|
||||
private static string DetermineDataDirNameForProject()
|
||||
{
|
||||
get
|
||||
{
|
||||
var appName = (string)ProjectSettings.GetSetting("application/config/name");
|
||||
string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false);
|
||||
return $"data_{appNameSafe}";
|
||||
}
|
||||
var appName = (string)ProjectSettings.GetSetting("application/config/name");
|
||||
string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false);
|
||||
return $"data_{appNameSafe}";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace GodotTools.Export
|
||||
{
|
||||
public static class XcodeHelper
|
||||
{
|
||||
private static string _XcodePath = null;
|
||||
|
||||
public static string XcodePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_XcodePath == null)
|
||||
{
|
||||
_XcodePath = FindXcode();
|
||||
|
||||
if (_XcodePath == null)
|
||||
throw new Exception("Could not find Xcode");
|
||||
}
|
||||
|
||||
return _XcodePath;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FindSelectedXcode()
|
||||
{
|
||||
var outputWrapper = new Godot.Collections.Array();
|
||||
|
||||
int exitCode = Godot.OS.Execute("xcode-select", new string[] { "--print-path" }, blocking: true, output: outputWrapper);
|
||||
|
||||
if (exitCode == 0)
|
||||
{
|
||||
string output = (string)outputWrapper[0];
|
||||
return output.Trim();
|
||||
}
|
||||
|
||||
Console.Error.WriteLine($"'xcode-select --print-path' exited with code: {exitCode}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string FindXcode()
|
||||
{
|
||||
string selectedXcode = FindSelectedXcode();
|
||||
if (selectedXcode != null)
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer")))
|
||||
return selectedXcode;
|
||||
|
||||
// The path already pointed to Contents/Developer
|
||||
var dirInfo = new DirectoryInfo(selectedXcode);
|
||||
if (dirInfo.Name != "Developer" || dirInfo.Parent.Name != "Contents")
|
||||
{
|
||||
Console.WriteLine(Path.GetDirectoryName(selectedXcode));
|
||||
Console.WriteLine(System.IO.Directory.GetParent(selectedXcode).Name);
|
||||
Console.Error.WriteLine("Unrecognized path for selected Xcode");
|
||||
}
|
||||
else
|
||||
{
|
||||
return System.IO.Path.GetFullPath($"{selectedXcode}/../..");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine("Could not find the selected Xcode; trying with a hint path");
|
||||
}
|
||||
|
||||
const string XcodeHintPath = "/Applications/Xcode.app";
|
||||
|
||||
if (Directory.Exists(XcodeHintPath))
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(XcodeHintPath, "Contents", "Developer")))
|
||||
return XcodeHintPath;
|
||||
|
||||
Console.Error.WriteLine($"Found Xcode at '{XcodeHintPath}' but it's missing the 'Contents/Developer' sub-directory");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string FindXcodeTool(string toolName)
|
||||
{
|
||||
string XcodeDefaultToolchain = Path.Combine(XcodePath, "Contents", "Developer", "Toolchains", "XcodeDefault.xctoolchain");
|
||||
|
||||
string path = Path.Combine(XcodeDefaultToolchain, "usr", "bin", toolName);
|
||||
if (File.Exists(path))
|
||||
return path;
|
||||
|
||||
throw new FileNotFoundException($"Cannot find Xcode tool: {toolName}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,17 @@ namespace GodotTools
|
|||
|
||||
public BottomPanel BottomPanel { get; private set; }
|
||||
|
||||
public static string ProjectAssemblyName
|
||||
{
|
||||
get
|
||||
{
|
||||
var projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name");
|
||||
if (string.IsNullOrEmpty(projectAssemblyName))
|
||||
projectAssemblyName = "UnnamedProject";
|
||||
return projectAssemblyName;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateProjectSolution()
|
||||
{
|
||||
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3))
|
||||
|
@ -45,9 +56,7 @@ namespace GodotTools
|
|||
string resourceDir = ProjectSettings.GlobalizePath("res://");
|
||||
|
||||
string path = resourceDir;
|
||||
string name = (string)ProjectSettings.GetSetting("application/config/name");
|
||||
if (name.Empty())
|
||||
name = "UnnamedProject";
|
||||
string name = ProjectAssemblyName;
|
||||
|
||||
string guid = CsProjOperations.GenerateGameProject(path, name);
|
||||
|
||||
|
|
|
@ -51,7 +51,9 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Build\MsBuildFinder.cs" />
|
||||
<Compile Include="Export\AotBuilder.cs" />
|
||||
<Compile Include="Export\ExportPlugin.cs" />
|
||||
<Compile Include="Export\XcodeHelper.cs" />
|
||||
<Compile Include="ExternalEditorId.cs" />
|
||||
<Compile Include="Ides\GodotIdeManager.cs" />
|
||||
<Compile Include="Ides\GodotIdeServer.cs" />
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace GodotTools.Utils
|
||||
{
|
||||
|
@ -26,6 +27,7 @@ namespace GodotTools.Utils
|
|||
public const string UWP = "UWP";
|
||||
public const string Haiku = "Haiku";
|
||||
public const string Android = "Android";
|
||||
public const string iOS = "iOS";
|
||||
public const string HTML5 = "HTML5";
|
||||
}
|
||||
|
||||
|
@ -38,6 +40,7 @@ namespace GodotTools.Utils
|
|||
public const string UWP = "uwp";
|
||||
public const string Haiku = "haiku";
|
||||
public const string Android = "android";
|
||||
public const string iOS = "iphone";
|
||||
public const string HTML5 = "javascript";
|
||||
}
|
||||
|
||||
|
@ -50,6 +53,7 @@ namespace GodotTools.Utils
|
|||
[Names.UWP] = Platforms.UWP,
|
||||
[Names.Haiku] = Platforms.Haiku,
|
||||
[Names.Android] = Platforms.Android,
|
||||
[Names.iOS] = Platforms.iOS,
|
||||
[Names.HTML5] = Platforms.HTML5
|
||||
};
|
||||
|
||||
|
@ -65,6 +69,7 @@ namespace GodotTools.Utils
|
|||
private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP));
|
||||
private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku));
|
||||
private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android));
|
||||
private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS));
|
||||
private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5));
|
||||
|
||||
public static bool IsWindows => _isWindows.Value || IsUWP;
|
||||
|
@ -74,10 +79,11 @@ namespace GodotTools.Utils
|
|||
public static bool IsUWP => _isUWP.Value;
|
||||
public static bool IsHaiku => _isHaiku.Value;
|
||||
public static bool IsAndroid => _isAndroid.Value;
|
||||
public static bool IsiOS => _isiOS.Value;
|
||||
public static bool IsHTML5 => _isHTML5.Value;
|
||||
|
||||
private static bool? _isUnixCache;
|
||||
private static readonly string[] UnixLikePlatforms = { Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android };
|
||||
private static readonly string[] UnixLikePlatforms = { Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android, Names.iOS };
|
||||
|
||||
public static bool IsUnixLike()
|
||||
{
|
||||
|
@ -91,12 +97,12 @@ namespace GodotTools.Utils
|
|||
|
||||
public static char PathSep => IsWindows ? ';' : ':';
|
||||
|
||||
public static string PathWhich(string name)
|
||||
public static string PathWhich([NotNull] string name)
|
||||
{
|
||||
return IsWindows ? PathWhichWindows(name) : PathWhichUnix(name);
|
||||
}
|
||||
|
||||
private static string PathWhichWindows(string name)
|
||||
private static string PathWhichWindows([NotNull] string name)
|
||||
{
|
||||
string[] windowsExts = Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? new string[] { };
|
||||
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
|
||||
|
@ -121,7 +127,7 @@ namespace GodotTools.Utils
|
|||
select path + ext).FirstOrDefault(File.Exists);
|
||||
}
|
||||
|
||||
private static string PathWhichUnix(string name)
|
||||
private static string PathWhichUnix([NotNull] string name)
|
||||
{
|
||||
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
|
||||
|
||||
|
@ -163,5 +169,33 @@ namespace GodotTools.Utils
|
|||
User32Dll.AllowSetForegroundWindow(process.Id); // allows application to focus itself
|
||||
}
|
||||
}
|
||||
|
||||
public static int ExecuteCommand(string command, IEnumerable<string> arguments)
|
||||
{
|
||||
// TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
|
||||
string CmdLineArgsToString(IEnumerable<string> args)
|
||||
{
|
||||
// Not perfect, but as long as we are careful...
|
||||
return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments));
|
||||
|
||||
Console.WriteLine($"Executing: \"{startInfo.FileName}\" {startInfo.Arguments}");
|
||||
|
||||
// Print the output
|
||||
startInfo.RedirectStandardOutput = false;
|
||||
startInfo.RedirectStandardError = false;
|
||||
|
||||
startInfo.UseShellExecute = false;
|
||||
|
||||
using (var process = new Process { StartInfo = startInfo })
|
||||
{
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,8 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String>
|
|||
|
||||
ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
|
||||
|
||||
r_dependencies[ref_name] = ref_assembly->get_path();
|
||||
// Use the path we got from the search. Don't try to get the path from the loaded assembly as we can't trust it will be from the selected BCL dir.
|
||||
r_dependencies[ref_name] = path;
|
||||
|
||||
Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
#endif
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
#include "mono_gd/gd_mono_android.h"
|
||||
#include "mono_gd/support/mono-support.h"
|
||||
#endif
|
||||
|
||||
#include "mono_gd/gd_mono.h"
|
||||
|
@ -169,7 +169,7 @@ private:
|
|||
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir();
|
||||
data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir();
|
||||
#else
|
||||
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
|
||||
#endif
|
||||
|
@ -206,7 +206,7 @@ private:
|
|||
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir();
|
||||
data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir();
|
||||
#else
|
||||
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
|
||||
data_game_assemblies_dir = data_dir_root.plus_file("Assemblies");
|
||||
|
|
|
@ -57,7 +57,18 @@
|
|||
|
||||
#ifdef ANDROID_ENABLED
|
||||
#include "android_mono_config.h"
|
||||
#include "gd_mono_android.h"
|
||||
#include "support/android_support.h"
|
||||
#elif defined(IPHONE_ENABLED)
|
||||
#include "support/ios_support.h"
|
||||
#endif
|
||||
|
||||
#if defined(TOOL_ENABLED) && defined(GD_MONO_SINGLE_APPDOMAIN)
|
||||
// This will no longer be the case if we replace appdomains with AssemblyLoadContext
|
||||
#error "Editor build requires support for multiple appdomains"
|
||||
#endif
|
||||
|
||||
#if defined(GD_MONO_HOT_RELOAD) && defined(GD_MONO_SINGLE_APPDOMAIN)
|
||||
#error "Hot reloading requires multiple appdomains"
|
||||
#endif
|
||||
|
||||
// TODO:
|
||||
|
@ -177,7 +188,14 @@ MonoDomain *gd_initialize_mono_runtime() {
|
|||
gd_mono_debug_init();
|
||||
#endif
|
||||
|
||||
return mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319");
|
||||
#if defined(IPHONE_ENABLED) || defined(ANDROID_ENABLED)
|
||||
// I don't know whether this actually matters or not
|
||||
const char *runtime_version = "mobile";
|
||||
#else
|
||||
const char *runtime_version = "v4.0.30319";
|
||||
#endif
|
||||
|
||||
return mono_jit_init_version("GodotEngine.RootDomain", runtime_version);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -319,8 +337,16 @@ void GDMono::initialize() {
|
|||
add_mono_shared_libs_dir_to_path();
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data());
|
||||
#else
|
||||
mono_config_parse(NULL);
|
||||
#endif
|
||||
|
||||
#if defined(ANDROID_ENABLED)
|
||||
GDMonoAndroid::initialize();
|
||||
gdmono::android::support::initialize();
|
||||
#elif defined(IPHONE_ENABLED)
|
||||
gdmono::ios::support::initialize();
|
||||
#endif
|
||||
|
||||
GDMonoAssembly::initialize();
|
||||
|
@ -329,12 +355,6 @@ void GDMono::initialize() {
|
|||
gd_mono_profiler_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);
|
||||
|
||||
#ifndef TOOLS_ENABLED
|
||||
|
@ -370,15 +390,19 @@ void GDMono::initialize() {
|
|||
print_verbose("Mono: Runtime initialized");
|
||||
|
||||
#if defined(ANDROID_ENABLED)
|
||||
GDMonoAndroid::register_internal_calls();
|
||||
gdmono::android::support::register_internal_calls();
|
||||
#endif
|
||||
|
||||
// mscorlib assembly MUST be present at initialization
|
||||
bool corlib_loaded = _load_corlib_assembly();
|
||||
ERR_FAIL_COND_MSG(!corlib_loaded, "Mono: Failed to load mscorlib assembly.");
|
||||
|
||||
#ifndef GD_MONO_SINGLE_APPDOMAIN
|
||||
Error domain_load_err = _load_scripts_domain();
|
||||
ERR_FAIL_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain.");
|
||||
#else
|
||||
scripts_domain = root_domain;
|
||||
#endif
|
||||
|
||||
_register_internal_calls();
|
||||
|
||||
|
@ -490,11 +514,15 @@ void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) {
|
|||
assemblies[p_domain_id][p_assembly->get_name()] = p_assembly;
|
||||
}
|
||||
|
||||
GDMonoAssembly **GDMono::get_loaded_assembly(const String &p_name) {
|
||||
GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {
|
||||
|
||||
if (p_name == "mscorlib")
|
||||
return get_corlib_assembly();
|
||||
|
||||
MonoDomain *domain = mono_domain_get();
|
||||
uint32_t domain_id = domain ? mono_domain_get_id(domain) : 0;
|
||||
return assemblies[domain_id].getptr(p_name);
|
||||
GDMonoAssembly **result = assemblies[domain_id].getptr(p_name);
|
||||
return result ? *result : NULL;
|
||||
}
|
||||
|
||||
bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) {
|
||||
|
@ -548,14 +576,6 @@ bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMo
|
|||
if (!assembly)
|
||||
return false;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
uint32_t domain_id = mono_domain_get_id(mono_domain_get());
|
||||
GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);
|
||||
|
||||
ERR_FAIL_COND_V(stored_assembly == NULL, false);
|
||||
ERR_FAIL_COND_V(*stored_assembly != assembly, false);
|
||||
#endif
|
||||
|
||||
*r_assembly = assembly;
|
||||
|
||||
print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path());
|
||||
|
@ -893,8 +913,8 @@ void GDMono::_load_api_assemblies() {
|
|||
|
||||
bool api_assemblies_loaded = _try_load_api_assemblies_preset();
|
||||
|
||||
#if defined(TOOLS_ENABLED) && !defined(GD_MONO_SINGLE_APPDOMAIN)
|
||||
if (!api_assemblies_loaded) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
// The API assemblies are out of sync or some other error happened. Fine, try one more time, but
|
||||
// this time update them from the prebuilt assemblies directory before trying to load them again.
|
||||
|
||||
|
@ -915,8 +935,8 @@ void GDMono::_load_api_assemblies() {
|
|||
|
||||
// 4. Try loading the updated assemblies
|
||||
api_assemblies_loaded = _try_load_api_assemblies_preset();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!api_assemblies_loaded) {
|
||||
// welp... too bad
|
||||
|
@ -990,6 +1010,7 @@ void GDMono::_install_trace_listener() {
|
|||
#endif
|
||||
}
|
||||
|
||||
#ifndef GD_MONO_SINGLE_APPDOMAIN
|
||||
Error GDMono::_load_scripts_domain() {
|
||||
|
||||
ERR_FAIL_COND_V(scripts_domain != NULL, ERR_BUG);
|
||||
|
@ -1009,7 +1030,7 @@ Error GDMono::_unload_scripts_domain() {
|
|||
|
||||
ERR_FAIL_NULL_V(scripts_domain, ERR_BUG);
|
||||
|
||||
print_verbose("Mono: Unloading scripts domain...");
|
||||
print_verbose("Mono: Finalizing scripts domain...");
|
||||
|
||||
if (mono_domain_get() != root_domain)
|
||||
mono_domain_set(root_domain, true);
|
||||
|
@ -1042,6 +1063,8 @@ Error GDMono::_unload_scripts_domain() {
|
|||
MonoDomain *domain = scripts_domain;
|
||||
scripts_domain = NULL;
|
||||
|
||||
print_verbose("Mono: Unloading scripts domain...");
|
||||
|
||||
MonoException *exc = NULL;
|
||||
mono_domain_try_unload(domain, (MonoObject **)&exc);
|
||||
|
||||
|
@ -1053,6 +1076,7 @@ Error GDMono::_unload_scripts_domain() {
|
|||
|
||||
return OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef GD_MONO_HOT_RELOAD
|
||||
Error GDMono::reload_scripts_domain() {
|
||||
|
@ -1091,6 +1115,7 @@ Error GDMono::reload_scripts_domain() {
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifndef GD_MONO_SINGLE_APPDOMAIN
|
||||
Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
|
||||
|
||||
CRASH_COND(p_domain == NULL);
|
||||
|
@ -1122,6 +1147,7 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
|
|||
|
||||
return OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) {
|
||||
|
||||
|
@ -1149,13 +1175,17 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) {
|
|||
|
||||
GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) {
|
||||
|
||||
GDMonoClass *klass = corlib_assembly->get_class(p_namespace, p_name);
|
||||
if (klass)
|
||||
return klass;
|
||||
|
||||
uint32_t domain_id = mono_domain_get_id(mono_domain_get());
|
||||
HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id];
|
||||
|
||||
const String *k = NULL;
|
||||
while ((k = domain_assemblies.next(k))) {
|
||||
GDMonoAssembly *assembly = domain_assemblies.get(*k);
|
||||
GDMonoClass *klass = assembly->get_class(p_namespace, p_name);
|
||||
klass = assembly->get_class(p_namespace, p_name);
|
||||
if (klass)
|
||||
return klass;
|
||||
}
|
||||
|
@ -1222,12 +1252,44 @@ GDMono::GDMono() {
|
|||
GDMono::~GDMono() {
|
||||
|
||||
if (is_runtime_initialized()) {
|
||||
#ifndef GD_MONO_SINGLE_APPDOMAIN
|
||||
if (scripts_domain) {
|
||||
Error err = _unload_scripts_domain();
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Mono: Failed to unload scripts domain.");
|
||||
}
|
||||
}
|
||||
#else
|
||||
CRASH_COND(scripts_domain != root_domain);
|
||||
|
||||
print_verbose("Mono: Finalizing scripts domain...");
|
||||
|
||||
if (mono_domain_get() != root_domain)
|
||||
mono_domain_set(root_domain, true);
|
||||
|
||||
finalizing_scripts_domain = true;
|
||||
|
||||
if (!mono_domain_finalize(root_domain, 2000)) {
|
||||
ERR_PRINT("Mono: Domain finalization timeout.");
|
||||
}
|
||||
|
||||
finalizing_scripts_domain = false;
|
||||
|
||||
mono_gc_collect(mono_gc_max_generation());
|
||||
|
||||
GDMonoCache::clear_godot_api_cache();
|
||||
|
||||
_domain_assemblies_cleanup(mono_domain_get_id(root_domain));
|
||||
|
||||
core_api_assembly.assembly = NULL;
|
||||
|
||||
project_assembly = NULL;
|
||||
|
||||
root_domain = NULL;
|
||||
scripts_domain = NULL;
|
||||
|
||||
// Leave the rest to 'mono_jit_cleanup'
|
||||
#endif
|
||||
|
||||
const uint32_t *k = NULL;
|
||||
while ((k = assemblies.next(k))) {
|
||||
|
@ -1244,15 +1306,15 @@ GDMono::~GDMono() {
|
|||
|
||||
mono_jit_cleanup(root_domain);
|
||||
|
||||
#if defined(ANDROID_ENABLED)
|
||||
GDMonoAndroid::cleanup();
|
||||
#endif
|
||||
|
||||
print_verbose("Mono: Finalized");
|
||||
|
||||
runtime_initialized = false;
|
||||
}
|
||||
|
||||
#if defined(ANDROID_ENABLED)
|
||||
gdmono::android::support::cleanup();
|
||||
#endif
|
||||
|
||||
if (gdmono_log)
|
||||
memdelete(gdmono_log);
|
||||
|
||||
|
|
|
@ -144,8 +144,10 @@ private:
|
|||
|
||||
void _register_internal_calls();
|
||||
|
||||
#ifndef GD_MONO_SINGLE_APPDOMAIN
|
||||
Error _load_scripts_domain();
|
||||
Error _unload_scripts_domain();
|
||||
#endif
|
||||
|
||||
void _domain_assemblies_cleanup(uint32_t p_domain_id);
|
||||
|
||||
|
@ -209,7 +211,7 @@ public:
|
|||
|
||||
// Do not use these, unless you know what you're doing
|
||||
void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly);
|
||||
GDMonoAssembly **get_loaded_assembly(const String &p_name);
|
||||
GDMonoAssembly *get_loaded_assembly(const String &p_name);
|
||||
|
||||
_FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized && !mono_runtime_is_shutting_down() /* stays true after shutdown finished */; }
|
||||
|
||||
|
|
|
@ -42,9 +42,6 @@
|
|||
#include "gd_mono_cache.h"
|
||||
#include "gd_mono_class.h"
|
||||
|
||||
bool GDMonoAssembly::no_search = false;
|
||||
bool GDMonoAssembly::in_preload = false;
|
||||
|
||||
Vector<String> GDMonoAssembly::search_dirs;
|
||||
|
||||
void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) {
|
||||
|
@ -94,19 +91,30 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
|
|||
#endif
|
||||
}
|
||||
|
||||
// This is how these assembly loading hooks work:
|
||||
//
|
||||
// - The 'search' hook checks if the assembly has already been loaded, to avoid loading again.
|
||||
// - The 'preload' hook does the actual loading and is only called if the
|
||||
// 'search' hook didn't find the assembly in the list of loaded assemblies.
|
||||
// - The 'load' hook is called after the assembly has been loaded. Its job is to add the
|
||||
// assembly to the list of loaded assemblies so that the 'search' hook can look it up.
|
||||
|
||||
void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) {
|
||||
|
||||
if (no_search)
|
||||
return;
|
||||
String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
|
||||
|
||||
// If our search and preload hooks fail to load the assembly themselves, the mono runtime still might.
|
||||
// Just do Assembly.LoadFrom("/Full/Path/On/Disk.dll");
|
||||
// In this case, we wouldn't have the assembly known in GDMono, which causes crashes
|
||||
// if any class inside the assembly is looked up by Godot.
|
||||
// And causing a lookup like that is as easy as throwing an exception defined in it...
|
||||
// No, we can't make the assembly load hooks smart enough because they get passed a MonoAssemblyName* only,
|
||||
// not the disk path passed to say Assembly.LoadFrom().
|
||||
_wrap_mono_assembly(assembly);
|
||||
MonoImage *image = mono_assembly_get_image(assembly);
|
||||
|
||||
GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, image, assembly));
|
||||
|
||||
#ifdef GD_MONO_HOT_RELOAD
|
||||
const char *path = mono_image_get_filename(image);
|
||||
if (FileAccess::exists(path))
|
||||
gdassembly->modified_time = FileAccess::get_modified_time(path);
|
||||
#endif
|
||||
|
||||
MonoDomain *domain = mono_domain_get();
|
||||
GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly);
|
||||
}
|
||||
|
||||
MonoAssembly *GDMonoAssembly::assembly_search_hook(MonoAssemblyName *aname, void *user_data) {
|
||||
|
@ -132,71 +140,24 @@ MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_d
|
|||
String name = String::utf8(mono_assembly_name_get_name(aname));
|
||||
bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
|
||||
|
||||
if (no_search)
|
||||
return NULL;
|
||||
|
||||
GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
|
||||
GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
|
||||
if (loaded_asm)
|
||||
return (*loaded_asm)->get_assembly();
|
||||
return loaded_asm->get_assembly();
|
||||
|
||||
no_search = true; // Avoid the recursion madness
|
||||
|
||||
GDMonoAssembly *res = _load_assembly_search(name, search_dirs, refonly);
|
||||
|
||||
no_search = false;
|
||||
|
||||
return res ? res->get_assembly() : NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static _THREAD_LOCAL_(MonoImage *) image_corlib_loading = NULL;
|
||||
|
||||
MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) {
|
||||
|
||||
(void)user_data; // UNUSED
|
||||
|
||||
{
|
||||
// If we find the assembly here, we load it with 'mono_assembly_load_from_full',
|
||||
// which in turn invokes load hooks before returning the MonoAssembly to us.
|
||||
// One of the load hooks is 'load_aot_module'. This hook can end up calling preload hooks
|
||||
// again for the same assembly in certain in certain circumstances (the 'do_load_image' part).
|
||||
// If this is the case and we return NULL due to the no_search condition below,
|
||||
// it will result in an internal crash later on. Therefore we need to return the assembly we didn't
|
||||
// get yet from 'mono_assembly_load_from_full'. Luckily we have the image, which already got it.
|
||||
// This must be done here. If done in search hooks, it would cause 'mono_assembly_load_from_full'
|
||||
// to think another MonoAssembly for this assembly was already loaded, making it delete its own,
|
||||
// when in fact both pointers were the same... This hooks thing is confusing.
|
||||
if (image_corlib_loading) {
|
||||
return mono_image_get_assembly(image_corlib_loading);
|
||||
}
|
||||
}
|
||||
|
||||
if (no_search)
|
||||
return NULL;
|
||||
|
||||
no_search = true;
|
||||
in_preload = true;
|
||||
|
||||
String name = String::utf8(mono_assembly_name_get_name(aname));
|
||||
bool has_extension = name.ends_with(".dll");
|
||||
|
||||
GDMonoAssembly *res = NULL;
|
||||
if (has_extension ? name == "mscorlib.dll" : name == "mscorlib") {
|
||||
GDMonoAssembly **stored_assembly = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
|
||||
if (stored_assembly)
|
||||
return (*stored_assembly)->get_assembly();
|
||||
|
||||
res = _load_assembly_search("mscorlib.dll", search_dirs, refonly);
|
||||
}
|
||||
|
||||
no_search = false;
|
||||
in_preload = false;
|
||||
|
||||
return res ? res->get_assembly() : NULL;
|
||||
return _load_assembly_search(name, search_dirs, refonly);
|
||||
}
|
||||
|
||||
GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
|
||||
MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
|
||||
|
||||
GDMonoAssembly *res = NULL;
|
||||
MonoAssembly *res = NULL;
|
||||
String path;
|
||||
|
||||
bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe");
|
||||
|
@ -207,21 +168,21 @@ GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, cons
|
|||
if (has_extension) {
|
||||
path = search_dir.plus_file(p_name);
|
||||
if (FileAccess::exists(path)) {
|
||||
res = _load_assembly_from(p_name.get_basename(), path, p_refonly);
|
||||
res = _real_load_assembly_from(path, p_refonly);
|
||||
if (res != NULL)
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
path = search_dir.plus_file(p_name + ".dll");
|
||||
if (FileAccess::exists(path)) {
|
||||
res = _load_assembly_from(p_name, path, p_refonly);
|
||||
res = _real_load_assembly_from(path, p_refonly);
|
||||
if (res != NULL)
|
||||
return res;
|
||||
}
|
||||
|
||||
path = search_dir.plus_file(p_name + ".exe");
|
||||
if (FileAccess::exists(path)) {
|
||||
res = _load_assembly_from(p_name, path, p_refonly);
|
||||
res = _real_load_assembly_from(path, p_refonly);
|
||||
if (res != NULL)
|
||||
return res;
|
||||
}
|
||||
|
@ -258,40 +219,6 @@ String GDMonoAssembly::find_assembly(const String &p_name) {
|
|||
return String();
|
||||
}
|
||||
|
||||
GDMonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly) {
|
||||
|
||||
GDMonoAssembly *assembly = memnew(GDMonoAssembly(p_name, p_path));
|
||||
|
||||
Error err = assembly->load(p_refonly);
|
||||
|
||||
if (err != OK) {
|
||||
memdelete(assembly);
|
||||
ERR_FAIL_V(NULL);
|
||||
}
|
||||
|
||||
MonoDomain *domain = mono_domain_get();
|
||||
GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, assembly);
|
||||
|
||||
return assembly;
|
||||
}
|
||||
|
||||
void GDMonoAssembly::_wrap_mono_assembly(MonoAssembly *assembly) {
|
||||
String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
|
||||
|
||||
MonoImage *image = mono_assembly_get_image(assembly);
|
||||
|
||||
GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, mono_image_get_filename(image)));
|
||||
Error err = gdassembly->wrapper_for_image(image);
|
||||
|
||||
if (err != OK) {
|
||||
memdelete(gdassembly);
|
||||
ERR_FAIL();
|
||||
}
|
||||
|
||||
MonoDomain *domain = mono_domain_get();
|
||||
GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly);
|
||||
}
|
||||
|
||||
void GDMonoAssembly::initialize() {
|
||||
|
||||
fill_search_dirs(search_dirs);
|
||||
|
@ -303,46 +230,39 @@ void GDMonoAssembly::initialize() {
|
|||
mono_install_assembly_load_hook(&assembly_load_hook, NULL);
|
||||
}
|
||||
|
||||
Error GDMonoAssembly::load(bool p_refonly) {
|
||||
MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly) {
|
||||
|
||||
ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE);
|
||||
|
||||
refonly = p_refonly;
|
||||
|
||||
uint64_t last_modified_time = FileAccess::get_modified_time(path);
|
||||
|
||||
Vector<uint8_t> data = FileAccess::get_file_as_array(path);
|
||||
ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ);
|
||||
Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
|
||||
ERR_FAIL_COND_V_MSG(data.empty(), NULL, "Could read the assembly in the specified location");
|
||||
|
||||
String image_filename;
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
if (path.begins_with("res://")) {
|
||||
image_filename = path.substr(6, path.length());
|
||||
if (p_path.begins_with("res://")) {
|
||||
image_filename = p_path.substr(6, p_path.length());
|
||||
} else {
|
||||
image_filename = ProjectSettings::get_singleton()->globalize_path(path);
|
||||
image_filename = ProjectSettings::get_singleton()->globalize_path(p_path);
|
||||
}
|
||||
#else
|
||||
// FIXME: globalize_path does not work on exported games
|
||||
image_filename = ProjectSettings::get_singleton()->globalize_path(path);
|
||||
image_filename = ProjectSettings::get_singleton()->globalize_path(p_path);
|
||||
#endif
|
||||
|
||||
MonoImageOpenStatus status = MONO_IMAGE_OK;
|
||||
|
||||
image = mono_image_open_from_data_with_name(
|
||||
MonoImage *image = mono_image_open_from_data_with_name(
|
||||
(char *)&data[0], data.size(),
|
||||
true, &status, refonly,
|
||||
image_filename.utf8().get_data());
|
||||
true, &status, p_refonly,
|
||||
image_filename.utf8());
|
||||
|
||||
ERR_FAIL_COND_V(status != MONO_IMAGE_OK, ERR_FILE_CANT_OPEN);
|
||||
ERR_FAIL_NULL_V(image, ERR_FILE_CANT_OPEN);
|
||||
ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, NULL, "Failed to open assembly image from the loaded data");
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
Vector<uint8_t> pdb_data;
|
||||
String pdb_path(path + ".pdb");
|
||||
String pdb_path(p_path + ".pdb");
|
||||
|
||||
if (!FileAccess::exists(pdb_path)) {
|
||||
pdb_path = path.get_basename() + ".pdb"; // without .dll
|
||||
pdb_path = p_path.get_basename() + ".pdb"; // without .dll
|
||||
|
||||
if (!FileAccess::exists(pdb_path))
|
||||
goto no_pdb;
|
||||
|
@ -357,44 +277,21 @@ no_pdb:
|
|||
|
||||
#endif
|
||||
|
||||
bool is_corlib_preload = in_preload && name == "mscorlib";
|
||||
status = MONO_IMAGE_OK;
|
||||
|
||||
if (is_corlib_preload)
|
||||
image_corlib_loading = image;
|
||||
MonoAssembly *assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, p_refonly);
|
||||
|
||||
assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, refonly);
|
||||
|
||||
if (is_corlib_preload)
|
||||
image_corlib_loading = NULL;
|
||||
|
||||
ERR_FAIL_COND_V(status != MONO_IMAGE_OK || assembly == NULL, ERR_FILE_CANT_OPEN);
|
||||
ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, NULL, "Failed to load assembly for image");
|
||||
|
||||
// Decrement refcount which was previously incremented by mono_image_open_from_data_with_name
|
||||
mono_image_close(image);
|
||||
|
||||
loaded = true;
|
||||
modified_time = last_modified_time;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error GDMonoAssembly::wrapper_for_image(MonoImage *p_image) {
|
||||
|
||||
ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE);
|
||||
|
||||
assembly = mono_image_get_assembly(p_image);
|
||||
ERR_FAIL_NULL_V(assembly, FAILED);
|
||||
|
||||
image = p_image;
|
||||
|
||||
loaded = true;
|
||||
|
||||
return OK;
|
||||
return assembly;
|
||||
}
|
||||
|
||||
void GDMonoAssembly::unload() {
|
||||
|
||||
ERR_FAIL_COND(!loaded);
|
||||
ERR_FAIL_NULL(image); // Should not be called if already unloaded
|
||||
|
||||
for (Map<MonoClass *, GDMonoClass *>::Element *E = cached_raw.front(); E; E = E->next()) {
|
||||
memdelete(E->value());
|
||||
|
@ -405,12 +302,15 @@ void GDMonoAssembly::unload() {
|
|||
|
||||
assembly = NULL;
|
||||
image = NULL;
|
||||
loaded = false;
|
||||
}
|
||||
|
||||
String GDMonoAssembly::get_path() const {
|
||||
return String::utf8(mono_image_get_filename(image));
|
||||
}
|
||||
|
||||
GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) {
|
||||
|
||||
ERR_FAIL_COND_V(!loaded, NULL);
|
||||
ERR_FAIL_NULL_V(image, NULL);
|
||||
|
||||
ClassKey key(p_namespace, p_name);
|
||||
|
||||
|
@ -434,7 +334,7 @@ GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const Stri
|
|||
|
||||
GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) {
|
||||
|
||||
ERR_FAIL_COND_V(!loaded, NULL);
|
||||
ERR_FAIL_NULL_V(image, NULL);
|
||||
|
||||
Map<MonoClass *, GDMonoClass *>::Element *match = cached_raw.find(p_mono_class);
|
||||
|
||||
|
@ -514,32 +414,38 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class)
|
|||
|
||||
GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {
|
||||
|
||||
GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
|
||||
if (loaded_asm)
|
||||
return *loaded_asm;
|
||||
#ifdef DEBUG_ENABLED
|
||||
CRASH_COND(!FileAccess::exists(p_path));
|
||||
#endif
|
||||
no_search = true;
|
||||
GDMonoAssembly *res = _load_assembly_from(p_name, p_path, p_refonly);
|
||||
no_search = false;
|
||||
return res;
|
||||
if (p_name == "mscorlib" || p_name == "mscorlib.dll")
|
||||
return GDMono::get_singleton()->get_corlib_assembly();
|
||||
|
||||
// We need to manually call the search hook in this case, as it won't be called in the next step
|
||||
MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
|
||||
MonoAssembly *assembly = mono_assembly_invoke_search_hook(aname);
|
||||
mono_assembly_name_free(aname);
|
||||
mono_free(aname);
|
||||
|
||||
if (!assembly) {
|
||||
assembly = _real_load_assembly_from(p_path, p_refonly);
|
||||
ERR_FAIL_NULL_V(assembly, NULL);
|
||||
}
|
||||
|
||||
GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
|
||||
ERR_FAIL_NULL_V_MSG(loaded_asm, NULL, "Loaded assembly missing from table. Did we not receive the load hook?");
|
||||
|
||||
return loaded_asm;
|
||||
}
|
||||
|
||||
GDMonoAssembly::GDMonoAssembly(const String &p_name, const String &p_path) {
|
||||
|
||||
loaded = false;
|
||||
gdobject_class_cache_updated = false;
|
||||
name = p_name;
|
||||
path = p_path;
|
||||
refonly = false;
|
||||
modified_time = 0;
|
||||
assembly = NULL;
|
||||
image = NULL;
|
||||
GDMonoAssembly::GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly) :
|
||||
name(p_name),
|
||||
image(p_image),
|
||||
assembly(p_assembly),
|
||||
#ifdef GD_MONO_HOT_RELOAD
|
||||
modified_time(0),
|
||||
#endif
|
||||
gdobject_class_cache_updated(false) {
|
||||
}
|
||||
|
||||
GDMonoAssembly::~GDMonoAssembly() {
|
||||
|
||||
if (loaded)
|
||||
if (image)
|
||||
unload();
|
||||
}
|
||||
|
|
|
@ -68,24 +68,20 @@ class GDMonoAssembly {
|
|||
StringName class_name;
|
||||
};
|
||||
|
||||
MonoAssembly *assembly;
|
||||
MonoImage *image;
|
||||
|
||||
bool refonly;
|
||||
bool loaded;
|
||||
|
||||
String name;
|
||||
String path;
|
||||
uint64_t modified_time;
|
||||
MonoImage *image;
|
||||
MonoAssembly *assembly;
|
||||
|
||||
HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes;
|
||||
Map<MonoClass *, GDMonoClass *> cached_raw;
|
||||
#ifdef GD_MONO_HOT_RELOAD
|
||||
uint64_t modified_time;
|
||||
#endif
|
||||
|
||||
bool gdobject_class_cache_updated;
|
||||
Map<StringName, GDMonoClass *> gdobject_class_cache;
|
||||
|
||||
static bool no_search;
|
||||
static bool in_preload;
|
||||
HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes;
|
||||
Map<MonoClass *, GDMonoClass *> cached_raw;
|
||||
|
||||
static Vector<String> search_dirs;
|
||||
|
||||
static void assembly_load_hook(MonoAssembly *assembly, void *user_data);
|
||||
|
@ -97,25 +93,24 @@ class GDMonoAssembly {
|
|||
static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly);
|
||||
static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly);
|
||||
|
||||
static GDMonoAssembly *_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly);
|
||||
static GDMonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly);
|
||||
static void _wrap_mono_assembly(MonoAssembly *assembly);
|
||||
static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly);
|
||||
static MonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly);
|
||||
|
||||
friend class GDMono;
|
||||
static void initialize();
|
||||
|
||||
public:
|
||||
Error load(bool p_refonly);
|
||||
Error wrapper_for_image(MonoImage *p_image);
|
||||
void unload();
|
||||
|
||||
_FORCE_INLINE_ bool is_refonly() const { return refonly; }
|
||||
_FORCE_INLINE_ bool is_loaded() const { return loaded; }
|
||||
_FORCE_INLINE_ MonoImage *get_image() const { return image; }
|
||||
_FORCE_INLINE_ MonoAssembly *get_assembly() const { return assembly; }
|
||||
_FORCE_INLINE_ String get_name() const { return name; }
|
||||
_FORCE_INLINE_ String get_path() const { return path; }
|
||||
|
||||
#ifdef GD_MONO_HOT_RELOAD
|
||||
_FORCE_INLINE_ uint64_t get_modified_time() const { return modified_time; }
|
||||
#endif
|
||||
|
||||
String get_path() const;
|
||||
|
||||
GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name);
|
||||
GDMonoClass *get_class(MonoClass *p_mono_class);
|
||||
|
@ -128,7 +123,7 @@ public:
|
|||
|
||||
static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly);
|
||||
|
||||
GDMonoAssembly(const String &p_name, const String &p_path = String());
|
||||
GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly);
|
||||
~GDMonoAssembly();
|
||||
};
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ static CharString get_default_log_level() {
|
|||
|
||||
GDMonoLog *GDMonoLog::singleton = NULL;
|
||||
|
||||
#if !defined(JAVASCRIPT_ENABLED)
|
||||
#ifdef GD_MONO_LOG_ENABLED
|
||||
|
||||
static int get_log_level_id(const char *p_log_level) {
|
||||
|
||||
|
|
|
@ -35,13 +35,18 @@
|
|||
|
||||
#include "core/typedefs.h"
|
||||
|
||||
#if !defined(JAVASCRIPT_ENABLED)
|
||||
#if !defined(JAVASCRIPT_ENABLED) && !defined(IPHONE_ENABLED)
|
||||
// We have custom mono log callbacks for WASM and iOS
|
||||
#define GD_MONO_LOG_ENABLED
|
||||
#endif
|
||||
|
||||
#ifdef GD_MONO_LOG_ENABLED
|
||||
#include "core/os/file_access.h"
|
||||
#endif
|
||||
|
||||
class GDMonoLog {
|
||||
|
||||
#if !defined(JAVASCRIPT_ENABLED)
|
||||
#ifdef GD_MONO_LOG_ENABLED
|
||||
int log_level_id;
|
||||
|
||||
FileAccess *log_file;
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
#include "gd_mono_method.h"
|
||||
#include "gd_mono_utils.h"
|
||||
|
||||
#if !defined(JAVASCRIPT_ENABLED)
|
||||
#if !defined(JAVASCRIPT_ENABLED) && !defined(IPHONE_ENABLED)
|
||||
#define HAVE_METHOD_THUNKS
|
||||
#endif
|
||||
|
||||
|
|
|
@ -128,7 +128,12 @@ void set_main_thread(MonoThread *p_thread) {
|
|||
MonoThread *attach_current_thread() {
|
||||
ERR_FAIL_COND_V(!GDMono::get_singleton()->is_runtime_initialized(), NULL);
|
||||
MonoDomain *scripts_domain = GDMono::get_singleton()->get_scripts_domain();
|
||||
#ifndef GD_MONO_SINGLE_APPDOMAIN
|
||||
MonoThread *mono_thread = mono_thread_attach(scripts_domain ? scripts_domain : mono_get_root_domain());
|
||||
#else
|
||||
// The scripts domain is the root domain
|
||||
MonoThread *mono_thread = mono_thread_attach(scripts_domain);
|
||||
#endif
|
||||
ERR_FAIL_NULL_V(mono_thread, NULL);
|
||||
return mono_thread;
|
||||
}
|
||||
|
|
38
modules/mono/mono_gd/gd_mono_android.cpp → modules/mono/mono_gd/support/android_support.cpp
Normal file → Executable file
38
modules/mono/mono_gd/gd_mono_android.cpp → modules/mono/mono_gd/support/android_support.cpp
Normal file → Executable file
|
@ -1,5 +1,5 @@
|
|||
/*************************************************************************/
|
||||
/* gd_mono_android.cpp */
|
||||
/* android_support.cpp */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
|
@ -28,7 +28,7 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "gd_mono_android.h"
|
||||
#include "android_support.h"
|
||||
|
||||
#if defined(ANDROID_ENABLED)
|
||||
|
||||
|
@ -49,14 +49,16 @@
|
|||
#include "platform/android/os_android.h"
|
||||
#include "platform/android/thread_jandroid.h"
|
||||
|
||||
#include "../utils/path_utils.h"
|
||||
#include "../utils/string_utils.h"
|
||||
#include "gd_mono_cache.h"
|
||||
#include "gd_mono_marshal.h"
|
||||
#include "../../utils/path_utils.h"
|
||||
#include "../../utils/string_utils.h"
|
||||
#include "../gd_mono_cache.h"
|
||||
#include "../gd_mono_marshal.h"
|
||||
|
||||
// Warning: JNI boilerplate ahead... continue at your own risk
|
||||
|
||||
namespace GDMonoAndroid {
|
||||
namespace gdmono {
|
||||
namespace android {
|
||||
namespace support {
|
||||
|
||||
template <typename T>
|
||||
struct ScopedLocalRef {
|
||||
|
@ -150,11 +152,11 @@ int gd_mono_convert_dl_flags(int flags) {
|
|||
return lflags;
|
||||
}
|
||||
|
||||
#ifndef GD_MONO_ANDROID_SO_NAME
|
||||
#define GD_MONO_ANDROID_SO_NAME "libmonosgen-2.0.so"
|
||||
#ifndef GD_MONO_SO_NAME
|
||||
#define GD_MONO_SO_NAME "libmonosgen-2.0.so"
|
||||
#endif
|
||||
|
||||
const char *mono_so_name = GD_MONO_ANDROID_SO_NAME;
|
||||
const char *mono_so_name = GD_MONO_SO_NAME;
|
||||
const char *godot_so_name = "libgodot_android.so";
|
||||
|
||||
void *mono_dl_handle = NULL;
|
||||
|
@ -352,6 +354,11 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) {
|
|||
return encoded_ret;
|
||||
}
|
||||
|
||||
void register_internal_calls() {
|
||||
mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", (void *)_gd_mono_init_cert_store);
|
||||
mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", (void *)_gd_mono_android_cert_store_lookup);
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
// We need to set this environment variable to make the monodroid BCL use btls instead of legacy as the default provider
|
||||
OS::get_singleton()->set_environment("XA_TLS_PROVIDER", "btls");
|
||||
|
@ -364,11 +371,6 @@ void initialize() {
|
|||
godot_dl_handle = try_dlopen(so_path, gd_mono_convert_dl_flags(MONO_DL_LAZY));
|
||||
}
|
||||
|
||||
void register_internal_calls() {
|
||||
mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", (void *)_gd_mono_init_cert_store);
|
||||
mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", (void *)_gd_mono_android_cert_store_lookup);
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
// This is called after shutting down the Mono runtime
|
||||
|
||||
|
@ -386,9 +388,11 @@ void cleanup() {
|
|||
}
|
||||
}
|
||||
|
||||
} // namespace GDMonoAndroid
|
||||
} // namespace support
|
||||
} // namespace android
|
||||
} // namespace gdmono
|
||||
|
||||
using namespace GDMonoAndroid;
|
||||
using namespace gdmono::android::support;
|
||||
|
||||
// The following are P/Invoke functions required by the monodroid profile of the BCL.
|
||||
// These are P/Invoke functions and not internal calls, hence why they use
|
19
modules/mono/mono_gd/gd_mono_android.h → modules/mono/mono_gd/support/android_support.h
Normal file → Executable file
19
modules/mono/mono_gd/gd_mono_android.h → modules/mono/mono_gd/support/android_support.h
Normal file → Executable file
|
@ -1,5 +1,5 @@
|
|||
/*************************************************************************/
|
||||
/* gd_mono_android.h */
|
||||
/* android_support.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
|
@ -28,25 +28,28 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef GD_MONO_ANDROID_H
|
||||
#define GD_MONO_ANDROID_H
|
||||
#ifndef ANDROID_SUPPORT_H
|
||||
#define ANDROID_SUPPORT_H
|
||||
|
||||
#if defined(ANDROID_ENABLED)
|
||||
|
||||
#include "core/ustring.h"
|
||||
|
||||
namespace GDMonoAndroid {
|
||||
namespace gdmono {
|
||||
namespace android {
|
||||
namespace support {
|
||||
|
||||
String get_app_native_lib_dir();
|
||||
|
||||
void initialize();
|
||||
void cleanup();
|
||||
|
||||
void register_internal_calls();
|
||||
|
||||
void cleanup();
|
||||
|
||||
} // namespace GDMonoAndroid
|
||||
} // namespace support
|
||||
} // namespace android
|
||||
} // namespace gdmono
|
||||
|
||||
#endif // ANDROID_ENABLED
|
||||
|
||||
#endif // GD_MONO_ANDROID_H
|
||||
#endif // ANDROID_SUPPORT_H
|
|
@ -0,0 +1,51 @@
|
|||
/*************************************************************************/
|
||||
/* ios_support.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 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 IOS_SUPPORT_H
|
||||
#define IOS_SUPPORT_H
|
||||
|
||||
#if defined(IPHONE_ENABLED)
|
||||
|
||||
#include "core/ustring.h"
|
||||
|
||||
namespace gdmono {
|
||||
namespace ios {
|
||||
namespace support {
|
||||
|
||||
void initialize();
|
||||
void cleanup();
|
||||
|
||||
} // namespace support
|
||||
} // namespace ios
|
||||
} // namespace gdmono
|
||||
|
||||
#endif // IPHONE_ENABLED
|
||||
|
||||
#endif // IOS_SUPPORT_H
|
|
@ -0,0 +1,151 @@
|
|||
/*************************************************************************/
|
||||
/* ios_support.mm */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2020 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 "ios_support.h"
|
||||
|
||||
#if defined(IPHONE_ENABLED)
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <os/log.h>
|
||||
|
||||
#include "core/ustring.h"
|
||||
|
||||
#include "../gd_mono_marshal.h"
|
||||
|
||||
// Implemented mostly following: https://github.com/mono/mono/blob/master/sdks/ios/app/runtime.m
|
||||
|
||||
// Definition generated by the Godot exporter
|
||||
extern "C" void gd_mono_setup_aot();
|
||||
|
||||
namespace gdmono {
|
||||
namespace ios {
|
||||
namespace support {
|
||||
|
||||
void ios_mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) {
|
||||
os_log_info(OS_LOG_DEFAULT, "(%s %s) %s", log_domain, log_level, message);
|
||||
if (fatal) {
|
||||
os_log_info(OS_LOG_DEFAULT, "Exit code: %d.", 1);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
mono_dllmap_insert(NULL, "System.Native", NULL, "__Internal", NULL);
|
||||
mono_dllmap_insert(NULL, "System.IO.Compression.Native", NULL, "__Internal", NULL);
|
||||
mono_dllmap_insert(NULL, "System.Security.Cryptography.Native.Apple", NULL, "__Internal", NULL);
|
||||
|
||||
#ifdef IOS_DEVICE
|
||||
// This function is defined in an auto-generated source file
|
||||
gd_mono_setup_aot();
|
||||
#endif
|
||||
|
||||
mono_set_signal_chaining(true);
|
||||
mono_set_crash_chaining(true);
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
}
|
||||
|
||||
} // namespace support
|
||||
} // namespace ios
|
||||
} // namespace gdmono
|
||||
|
||||
// The following are P/Invoke functions required by the monotouch profile of the BCL.
|
||||
// These are P/Invoke functions and not internal calls, hence why they use
|
||||
// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'.
|
||||
|
||||
#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default")))
|
||||
|
||||
GD_PINVOKE_EXPORT const char *xamarin_get_locale_country_code() {
|
||||
NSLocale *locale = [NSLocale currentLocale];
|
||||
NSString *countryCode = [locale objectForKey:NSLocaleCountryCode];
|
||||
if (countryCode == NULL) {
|
||||
return strdup("US");
|
||||
}
|
||||
return strdup([countryCode UTF8String]);
|
||||
}
|
||||
|
||||
GD_PINVOKE_EXPORT void xamarin_log(const uint16_t *p_unicode_message) {
|
||||
int length = 0;
|
||||
const uint16_t *ptr = p_unicode_message;
|
||||
while (*ptr++)
|
||||
length += sizeof(uint16_t);
|
||||
NSString *msg = [[NSString alloc] initWithBytes:p_unicode_message length:length encoding:NSUTF16LittleEndianStringEncoding];
|
||||
|
||||
os_log_info(OS_LOG_DEFAULT, "%{public}@", msg);
|
||||
}
|
||||
|
||||
GD_PINVOKE_EXPORT const char *xamarin_GetFolderPath(int p_folder) {
|
||||
NSSearchPathDirectory dd = (NSSearchPathDirectory)p_folder;
|
||||
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:dd inDomains:NSUserDomainMask] lastObject];
|
||||
NSString *path = [url path];
|
||||
return strdup([path UTF8String]);
|
||||
}
|
||||
|
||||
GD_PINVOKE_EXPORT char *xamarin_timezone_get_local_name() {
|
||||
NSTimeZone *tz = nil;
|
||||
tz = [NSTimeZone localTimeZone];
|
||||
NSString *name = [tz name];
|
||||
return (name != nil) ? strdup([name UTF8String]) : strdup("Local");
|
||||
}
|
||||
|
||||
GD_PINVOKE_EXPORT char **xamarin_timezone_get_names(uint32_t *p_count) {
|
||||
NSArray *array = [NSTimeZone knownTimeZoneNames];
|
||||
*p_count = array.count;
|
||||
char **result = (char **)malloc(sizeof(char *) * (*p_count));
|
||||
for (uint32_t i = 0; i < *p_count; i++) {
|
||||
NSString *s = [array objectAtIndex:i];
|
||||
result[i] = strdup(s.UTF8String);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *p_size) { // FIXME: uint32_t since Dec 2019, unsigned long before
|
||||
NSTimeZone *tz = nil;
|
||||
if (p_name) {
|
||||
NSString *n = [[NSString alloc] initWithUTF8String:p_name];
|
||||
tz = [[[NSTimeZone alloc] initWithName:n] autorelease];
|
||||
[n release];
|
||||
} else {
|
||||
tz = [NSTimeZone localTimeZone];
|
||||
}
|
||||
NSData *data = [tz data];
|
||||
*p_size = [data length];
|
||||
void *result = malloc(*p_size);
|
||||
memcpy(result, data.bytes, *p_size);
|
||||
return result;
|
||||
}
|
||||
|
||||
GD_PINVOKE_EXPORT void xamarin_start_wwan(const char *p_uri) {
|
||||
// FIXME: What's this for? No idea how to implement.
|
||||
os_log_error(OS_LOG_DEFAULT, "Not implemented: 'xamarin_start_wwan'");
|
||||
}
|
||||
|
||||
#endif // IPHONE_ENABLED
|
|
@ -68,8 +68,8 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
|
|||
String modules_buildphase;
|
||||
String modules_buildgrp;
|
||||
};
|
||||
|
||||
struct ExportArchitecture {
|
||||
|
||||
String name;
|
||||
bool is_default;
|
||||
|
||||
|
@ -792,6 +792,13 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
|
|||
Vector<String> frameworks = export_plugins[i]->get_ios_frameworks();
|
||||
Error err = _export_additional_assets(p_out_dir, frameworks, true, r_exported_assets);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
|
||||
Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs();
|
||||
for (int j = 0; j < project_static_libs.size(); j++)
|
||||
project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project
|
||||
err = _export_additional_assets(p_out_dir, project_static_libs, true, r_exported_assets);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
|
||||
Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files();
|
||||
err = _export_additional_assets(p_out_dir, ios_bundle_files, false, r_exported_assets);
|
||||
ERR_FAIL_COND_V(err, err);
|
||||
|
@ -1069,6 +1076,22 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
|
|||
return ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Copy project static libs to the project
|
||||
Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
|
||||
for (int i = 0; i < export_plugins.size(); i++) {
|
||||
Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs();
|
||||
for (int j = 0; j < project_static_libs.size(); j++) {
|
||||
const String &static_lib_path = project_static_libs[j];
|
||||
String dest_lib_file_path = dest_dir + static_lib_path.get_file();
|
||||
Error lib_copy_err = tmp_app_path->copy(static_lib_path, dest_lib_file_path);
|
||||
if (lib_copy_err != OK) {
|
||||
ERR_PRINTS("Can't copy '" + static_lib_path + "'.");
|
||||
memdelete(tmp_app_path);
|
||||
return lib_copy_err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String iconset_dir = dest_dir + binary_name + "/Images.xcassets/AppIcon.appiconset/";
|
||||
err = OK;
|
||||
if (!tmp_app_path->dir_exists(iconset_dir)) {
|
||||
|
|
Loading…
Reference in New Issue