summaryrefslogtreecommitdiff
path: root/modules/mono/editor
diff options
context:
space:
mode:
authorIgnacio Etcheverry <ignalfonsore@gmail.com>2020-03-18 17:40:04 +0100
committerIgnacio Etcheverry <ignalfonsore@gmail.com>2020-03-31 09:37:16 +0200
commit77dd06134504ce68cc79b879c4a28d76a2c975f8 (patch)
treea4551344d3914ce93cfc25590f3c7261a27d4ab5 /modules/mono/editor
parentfa08437694d09f0d5ee594cb4fbe1ff72b074742 (diff)
Mono/C#: Add iOS support
Right now, games only work on devices when exported with FullAOT+Interpreter. There are some issues left that need to addressed for FullAOT alone. Right now, it's giving issues with the Godot.NativeCalls static constructor.
Diffstat (limited to 'modules/mono/editor')
-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
7 files changed, 815 insertions, 358 deletions
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 + "'.");