summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/mono/SCsub6
-rw-r--r--modules/mono/build_scripts/mono_configure.py73
-rw-r--r--modules/mono/config.py26
-rw-r--r--modules/mono/csharp_script.cpp2
-rwxr-xr-xmodules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs618
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs400
-rwxr-xr-xmodules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs93
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs15
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs42
-rw-r--r--modules/mono/editor/godotsharp_export.cpp3
-rw-r--r--modules/mono/godotsharp_dirs.cpp6
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp118
-rw-r--r--modules/mono/mono_gd/gd_mono.h4
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.cpp258
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.h35
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono_log.h9
-rw-r--r--modules/mono/mono_gd/gd_mono_method_thunk.h2
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.cpp5
-rwxr-xr-x[-rw-r--r--]modules/mono/mono_gd/support/android_support.cpp (renamed from modules/mono/mono_gd/gd_mono_android.cpp)38
-rwxr-xr-x[-rw-r--r--]modules/mono/mono_gd/support/android_support.h (renamed from modules/mono/mono_gd/gd_mono_android.h)19
-rwxr-xr-xmodules/mono/mono_gd/support/ios_support.h51
-rwxr-xr-xmodules/mono/mono_gd/support/ios_support.mm151
24 files changed, 1344 insertions, 634 deletions
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index af77913b91..c723b210cb 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -47,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")
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index c5616c1dc3..23f01b3cca 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -48,16 +48,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):
@@ -65,11 +68,11 @@ def is_desktop(platform):
def is_unix_like(platform):
- return platform in ["osx", "linuxbsd", "server", "android", "haiku"]
+ return platform in ["osx", "linuxbsd", "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):
@@ -87,6 +90,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"]
@@ -111,17 +116,32 @@ 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
@@ -193,6 +213,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"
@@ -200,12 +221,12 @@ 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:
@@ -223,6 +244,9 @@ def configure(env, env_mono):
+ "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)
@@ -244,7 +268,26 @@ 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"])
@@ -281,10 +324,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:
@@ -344,6 +389,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:
diff --git a/modules/mono/config.py b/modules/mono/config.py
index 2ea8a5247d..106ca6e028 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -1,9 +1,14 @@
+supported_platforms = ["windows", "osx", "linuxbsd", "server", "android", "haiku", "javascript", "iphone"]
+
+
def can_build(env, platform):
return True
def configure(env):
- if env["platform"] not in ["windows", "osx", "linuxbsd", "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,6 +16,9 @@ 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(
@@ -20,7 +28,7 @@ def configure(env):
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(
@@ -28,12 +36,20 @@ def configure(env):
)
)
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])
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 28bacbd0f0..bb3d0ef46f 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -763,7 +763,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))
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
new file mode 100755
index 0000000000..f1765f7e19
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
@@ -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}");
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 022005ad0b..d782d4e61b 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -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;
}
+ string bclDir = DeterminePlatformBclDir(platform);
+
var initialDependencies = dependencies.Duplicate();
- internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, DeterminePlatformBclDir(platform), dependencies);
+ internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, bclDir, dependencies);
- AddI18NAssemblies(dependencies, platform);
+ 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)]
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs
new file mode 100755
index 0000000000..219b7a698a
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs
@@ -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}");
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index e6d5dd9895..afe7670165 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -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);
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index 379dfd9f7d..ac9379adf8 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -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" />
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index 11a4109d97..b057ac12c6 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.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;
+ }
+ }
}
}
diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp
index ce0b6ad0e6..19b3bea5cf 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -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 + "'.");
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 828ab73c82..fe8b925257 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -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");
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index eb4c263745..3390aaae88 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -58,7 +58,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:
@@ -178,7 +189,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
@@ -320,8 +338,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();
@@ -330,12 +356,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
@@ -371,15 +391,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();
@@ -491,11 +515,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) {
@@ -549,14 +577,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());
@@ -894,8 +914,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.
@@ -916,8 +936,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
@@ -991,6 +1011,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);
@@ -1010,7 +1031,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);
@@ -1043,6 +1064,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);
@@ -1054,6 +1077,7 @@ Error GDMono::_unload_scripts_domain() {
return OK;
}
+#endif
#ifdef GD_MONO_HOT_RELOAD
Error GDMono::reload_scripts_domain() {
@@ -1092,6 +1116,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);
@@ -1123,6 +1148,7 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
return OK;
}
+#endif
GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) {
@@ -1150,13 +1176,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;
}
@@ -1223,12 +1253,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))) {
@@ -1245,15 +1307,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);
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 9528c64f8d..f7c2426734 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -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 */; }
diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp
index 6da1db249c..a6a44fe8eb 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.cpp
+++ b/modules/mono/mono_gd/gd_mono_assembly.cpp
@@ -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;
-
- // 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);
+ 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, 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();
-
- no_search = true; // Avoid the recursion madness
-
- GDMonoAssembly *res = _load_assembly_search(name, search_dirs, refonly);
+ return loaded_asm->get_assembly();
- 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) {
-
- ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE);
-
- refonly = p_refonly;
+MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool 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";
-
- if (is_corlib_preload)
- image_corlib_loading = image;
+ status = MONO_IMAGE_OK;
- assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, refonly);
+ MonoAssembly *assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, p_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();
-GDMonoAssembly::GDMonoAssembly(const String &p_name, const String &p_path) {
+ // 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);
- loaded = false;
- gdobject_class_cache_updated = false;
- name = p_name;
- path = p_path;
- refonly = false;
- modified_time = 0;
- assembly = NULL;
- image = NULL;
+ 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, 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();
}
diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h
index 4740e10339..43c8225b74 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.h
+++ b/modules/mono/mono_gd/gd_mono_assembly.h
@@ -68,24 +68,20 @@ class GDMonoAssembly {
StringName class_name;
};
- MonoAssembly *assembly;
+ String name;
MonoImage *image;
+ MonoAssembly *assembly;
- bool refonly;
- bool loaded;
-
- String name;
- String path;
+#ifdef GD_MONO_HOT_RELOAD
uint64_t modified_time;
-
- HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes;
- Map<MonoClass *, GDMonoClass *> cached_raw;
+#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();
};
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index 76828a66e0..4189934570 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -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) {
diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h
index ecf4c78b1a..1fc21f7df5 100644
--- a/modules/mono/mono_gd/gd_mono_log.h
+++ b/modules/mono/mono_gd/gd_mono_log.h
@@ -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;
diff --git a/modules/mono/mono_gd/gd_mono_method_thunk.h b/modules/mono/mono_gd/gd_mono_method_thunk.h
index d8c9a5eb02..614ae30947 100644
--- a/modules/mono/mono_gd/gd_mono_method_thunk.h
+++ b/modules/mono/mono_gd/gd_mono_method_thunk.h
@@ -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
diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp
index cdb26ae61b..5a2bdffe54 100644
--- a/modules/mono/mono_gd/gd_mono_utils.cpp
+++ b/modules/mono/mono_gd/gd_mono_utils.cpp
@@ -129,7 +129,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;
}
diff --git a/modules/mono/mono_gd/gd_mono_android.cpp b/modules/mono/mono_gd/support/android_support.cpp
index 761368878f..222b50730d 100644..100755
--- a/modules/mono/mono_gd/gd_mono_android.cpp
+++ b/modules/mono/mono_gd/support/android_support.cpp
@@ -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
diff --git a/modules/mono/mono_gd/gd_mono_android.h b/modules/mono/mono_gd/support/android_support.h
index 0e04847924..dc2e6c95ed 100644..100755
--- a/modules/mono/mono_gd/gd_mono_android.h
+++ b/modules/mono/mono_gd/support/android_support.h
@@ -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
diff --git a/modules/mono/mono_gd/support/ios_support.h b/modules/mono/mono_gd/support/ios_support.h
new file mode 100755
index 0000000000..e28af120e3
--- /dev/null
+++ b/modules/mono/mono_gd/support/ios_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
diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm
new file mode 100755
index 0000000000..e3d1a647fd
--- /dev/null
+++ b/modules/mono/mono_gd/support/ios_support.mm
@@ -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