diff options
Diffstat (limited to 'modules/mono/editor')
14 files changed, 883 insertions, 427 deletions
diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs index e4c8759802..3cf495f025 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs @@ -205,9 +205,9 @@ namespace GodotTools if (what == EditorSettings.NotificationEditorSettingsChanged) { var editorBaseControl = editorInterface.GetBaseControl(); - panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("panel", editorBaseControl.GetThemeStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("tab_fg", editorBaseControl.GetThemeStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("tab_bg", editorBaseControl.GetThemeStylebox("DebuggerTabBG", "EditorStyles")); } } @@ -258,9 +258,9 @@ namespace GodotTools RectMinSize = new Vector2(0, 228) * EditorScale, SizeFlagsVertical = (int)SizeFlags.ExpandFill }; - panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); - panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("panel", editorBaseControl.GetThemeStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("tab_fg", editorBaseControl.GetThemeStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddThemeStyleboxOverride("tab_bg", editorBaseControl.GetThemeStylebox("DebuggerTabBG", "EditorStyles")); AddChild(panelTabs); { diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs index b2459b69ad..938c3d8be1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs @@ -46,12 +46,12 @@ namespace GodotTools get { if (!BuildExited) - return GetIcon("Stop", "EditorIcons"); + return GetThemeIcon("Stop", "EditorIcons"); if (BuildResult == BuildResults.Error) - return GetIcon("StatusError", "EditorIcons"); + return GetThemeIcon("StatusError", "EditorIcons"); - return GetIcon("StatusSuccess", "EditorIcons"); + return GetThemeIcon("StatusSuccess", "EditorIcons"); } } @@ -145,8 +145,8 @@ namespace GodotTools { issuesList.Clear(); - using (var warningIcon = GetIcon("Warning", "EditorIcons")) - using (var errorIcon = GetIcon("Error", "EditorIcons")) + using (var warningIcon = GetThemeIcon("Warning", "EditorIcons")) + using (var errorIcon = GetThemeIcon("Error", "EditorIcons")) { for (int i = 0; i < issues.Count; i++) { 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..c9d7dd26f8 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); @@ -119,7 +128,7 @@ namespace GodotTools { bool showOnStart = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start"); aboutDialogCheckBox.Pressed = showOnStart; - aboutDialog.PopupCenteredMinsize(); + aboutDialog.PopupCentered(); } private void _MenuOptionPressed(int id) @@ -157,10 +166,10 @@ namespace GodotTools bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start"); if (showInfoDialog) { - aboutDialog.PopupExclusive = true; + aboutDialog.Exclusive = true; _ShowAboutDialog(); // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on. - aboutDialog.PopupExclusive = false; + aboutDialog.Exclusive = false; } var fileSystemDock = GetEditorInterface().GetFileSystemDock(); @@ -203,9 +212,9 @@ namespace GodotTools public void ShowErrorDialog(string message, string title = "Error") { - errorDialog.WindowTitle = title; + errorDialog.Title = title; errorDialog.DialogText = message; - errorDialog.PopupCenteredMinsize(); + errorDialog.PopupCentered(); } private static string _vsCodePath = string.Empty; @@ -374,7 +383,6 @@ namespace GodotTools menuPopup = new PopupMenu(); menuPopup.Hide(); - menuPopup.SetAsToplevel(true); AddToolSubmenuItem("Mono", menuPopup); @@ -383,7 +391,7 @@ namespace GodotTools menuPopup.AddItem("About C# support".TTR(), (int)MenuOptions.AboutCSharp); aboutDialog = new AcceptDialog(); editorBaseControl.AddChild(aboutDialog); - aboutDialog.WindowTitle = "Important: C# support is not feature-complete"; + aboutDialog.Title = "Important: C# support is not feature-complete"; // We don't use DialogText as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox // we'll add. Instead we add containers and a new autowrapped Label inside. @@ -397,7 +405,7 @@ namespace GodotTools aboutVBox.AddChild(aboutHBox); var aboutIcon = new TextureRect(); - aboutIcon.Texture = aboutIcon.GetIcon("NodeWarning", "EditorIcons"); + aboutIcon.Texture = aboutIcon.GetThemeIcon("NodeWarning", "EditorIcons"); aboutHBox.AddChild(aboutIcon); var aboutLabel = new Label(); 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/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs index 1d19fab706..ae05710f4f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -10,7 +10,7 @@ namespace GodotTools public override void _Notification(int what) { - if (what == MainLoop.NotificationWmFocusIn) + if (what == Node.NotificationWmFocusIn) { RestartTimer(); 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/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 9a5de6db16..71bb8ff851 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -411,7 +411,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append("\"/>"); } else { // Try to find as global enum constant - const EnumInterface *target_ienum = NULL; + const EnumInterface *target_ienum = nullptr; for (const List<EnumInterface>::Element *E = global_enums.front(); E; E = E->next()) { target_ienum = &E->get(); @@ -449,7 +449,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append("\"/>"); } else { // Try to find as enum constant in the current class - const EnumInterface *target_ienum = NULL; + const EnumInterface *target_ienum = nullptr; for (const List<EnumInterface>::Element *E = target_itype->enums.front(); E; E = E->next()) { target_ienum = &E->get(); @@ -782,7 +782,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { const ConstantInterface &iconstant = E->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { - String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL); + String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr); Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { @@ -843,7 +843,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { const ConstantInterface &iconstant = F->get(); if (iconstant.const_doc && iconstant.const_doc->description.size()) { - String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL); + String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr); Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { @@ -1406,7 +1406,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte const TypeInterface *current_type = &p_itype; while (!setter && current_type->base_name != StringName()) { OrderedHashMap<StringName, TypeInterface>::Element base_match = obj_types.find(current_type->base_name); - ERR_FAIL_COND_V(!base_match, ERR_BUG); + ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'."); current_type = &base_match.get(); setter = current_type->find_method_by_name(p_iprop.setter); } @@ -1417,7 +1417,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte current_type = &p_itype; while (!getter && current_type->base_name != StringName()) { OrderedHashMap<StringName, TypeInterface>::Element base_match = obj_types.find(current_type->base_name); - ERR_FAIL_COND_V(!base_match, ERR_BUG); + ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'."); current_type = &base_match.get(); getter = current_type->find_method_by_name(p_iprop.getter); } @@ -1494,7 +1494,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); - CRASH_COND(idx_arg_type == NULL); + CRASH_COND(idx_arg_type == nullptr); p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index)); } else { p_output.append(itos(p_iprop.index)); @@ -1522,7 +1522,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte if (idx_arg.type.cname != name_cache.type_int) { // Assume the index parameter is an enum const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type); - CRASH_COND(idx_arg_type == NULL); + CRASH_COND(idx_arg_type == nullptr); p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", "); } else { p_output.append(itos(p_iprop.index) + ", "); @@ -1631,7 +1631,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Generate method { if (!p_imethod.is_virtual && !p_imethod.requires_object_call) { - p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr "); + p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static readonly IntPtr "); p_output.append(method_bind_field); p_output.append(" = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); p_output.append(p_imethod.name); @@ -2121,7 +2121,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte if (return_type->is_object_type) { ptrcall_return_type = return_type->is_reference ? "Ref<Reference>" : return_type->c_type; - initialization = return_type->is_reference ? "" : " = NULL"; + initialization = return_type->is_reference ? "" : " = nullptr"; } else { ptrcall_return_type = return_type->c_type; } @@ -2130,10 +2130,10 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte p_output.append(" " C_LOCAL_RET); p_output.append(initialization + ";\n"); - String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "NULL" : return_type->c_type_out + "()"; + String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "nullptr" : return_type->c_type_out + "()"; if (return_type->ret_as_byref_arg) { - p_output.append("\tif (" CS_PARAM_INSTANCE " == NULL) { *arg_ret = "); + p_output.append("\tif (" CS_PARAM_INSTANCE " == nullptr) { *arg_ret = "); p_output.append(fail_ret); p_output.append("; ERR_FAIL_MSG(\"Parameter ' arg_ret ' is null.\"); }\n"); } else { @@ -2187,7 +2187,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte } p_output.append(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", "); - p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL"); + p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "nullptr"); p_output.append(", total_length, vcall_error);\n"); if (!ret_void) { @@ -2198,8 +2198,8 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte } } else { p_output.append("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", "); - p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, "); - p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n"); + p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "nullptr, "); + p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "nullptr);\n"); } if (!ret_void) { @@ -2241,11 +2241,11 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(con // Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead. const Map<StringName, TypeInterface>::Element *int_match = builtin_types.find(name_cache.type_int); - ERR_FAIL_NULL_V(int_match, NULL); + ERR_FAIL_NULL_V(int_match, nullptr); return &int_match->get(); } - return NULL; + return nullptr; } const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placeholder(const TypeReference &p_typeref) { @@ -2412,7 +2412,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash... - iprop.prop_doc = NULL; + iprop.prop_doc = nullptr; for (int i = 0; i < itype.class_doc->properties.size(); i++) { const DocData::PropertyDoc &prop_doc = itype.class_doc->properties[i]; @@ -2457,7 +2457,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { PropertyInfo return_info = method_info.return_val; - MethodBind *m = imethod.is_virtual ? NULL : ClassDB::get_method(type_cname, method_info.name); + MethodBind *m = imethod.is_virtual ? nullptr : ClassDB::get_method(type_cname, method_info.name); imethod.is_vararg = m && m->is_vararg(); @@ -2603,7 +2603,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { // Populate signals const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map; - const StringName *k = NULL; + const StringName *k = nullptr; while ((k = signal_map.next(k))) { SignalInterface isignal; @@ -2685,7 +2685,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ClassDB::get_integer_constant_list(type_cname, &constants, true); const HashMap<StringName, List<StringName>> &enum_map = class_info->enum_map; - k = NULL; + k = nullptr; while ((k = enum_map.next(k))) { StringName enum_proxy_cname = *k; @@ -2707,7 +2707,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value); - iconstant.const_doc = NULL; + iconstant.const_doc = nullptr; for (int i = 0; i < itype.class_doc->constants.size(); i++) { const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i]; @@ -2742,7 +2742,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value); - iconstant.const_doc = NULL; + iconstant.const_doc = nullptr; for (int i = 0; i < itype.class_doc->constants.size(); i++) { const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i]; @@ -3262,7 +3262,7 @@ void BindingsGenerator::_populate_global_constants() { String constant_name = GlobalConstants::get_global_constant_name(i); - const DocData::ConstantDoc *const_doc = NULL; + const DocData::ConstantDoc *const_doc = nullptr; for (int j = 0; j < global_scope_doc.constants.size(); j++) { const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[j]; diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index bebe489d02..7c87c688db 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -176,7 +176,7 @@ class BindingsGenerator { is_virtual = false; requires_object_call = false; is_internal = false; - method_doc = NULL; + method_doc = nullptr; is_deprecated = false; } }; @@ -202,7 +202,7 @@ class BindingsGenerator { } SignalInterface() { - method_doc = NULL; + method_doc = nullptr; is_deprecated = false; } }; @@ -376,7 +376,7 @@ class BindingsGenerator { return &E->get(); } - return NULL; + return nullptr; } const PropertyInterface *find_property_by_name(const StringName &p_cname) const { @@ -385,7 +385,7 @@ class BindingsGenerator { return &E->get(); } - return NULL; + return nullptr; } const PropertyInterface *find_property_by_proxy_name(const String &p_proxy_name) const { @@ -394,7 +394,7 @@ class BindingsGenerator { return &E->get(); } - return NULL; + return nullptr; } const MethodInterface *find_method_by_proxy_name(const String &p_proxy_name) const { @@ -403,7 +403,7 @@ class BindingsGenerator { return &E->get(); } - return NULL; + return nullptr; } private: @@ -498,7 +498,7 @@ class BindingsGenerator { c_arg_in = "%s"; - class_doc = NULL; + class_doc = nullptr; } }; @@ -622,7 +622,7 @@ class BindingsGenerator { if (it->get().name == p_name) return it; it = it->next(); } - return NULL; + return nullptr; } const ConstantInterface *find_constant_by_name(const String &p_name, const List<ConstantInterface> &p_constants) const { @@ -631,7 +631,7 @@ class BindingsGenerator { return &E->get(); } - return NULL; + return nullptr; } inline String get_unique_sig(const TypeInterface &p_type) { diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index 872f45ba91..e5c2d023d3 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -57,8 +57,8 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str Variant item_type = p_item_type; Variant include = p_include; const Variant *args[3] = { &project_path, &item_type, &include }; - MonoException *exc = NULL; - klass->get_method("AddItemToProjectChecked", 3)->invoke(NULL, args, &exc); + MonoException *exc = nullptr; + klass->get_method("AddItemToProjectChecked", 3)->invoke(nullptr, args, &exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 31996a03d0..283d4beb8e 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -95,7 +95,7 @@ MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir()); #else - return NULL; + return nullptr; #endif } @@ -103,7 +103,7 @@ MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir()); #else - return NULL; + return nullptr; #endif } @@ -111,7 +111,7 @@ MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path()); #else - return NULL; + return nullptr; #endif } @@ -119,7 +119,7 @@ MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path()); #else - return NULL; + return nullptr; #endif } @@ -127,7 +127,7 @@ MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir()); #else - return NULL; + return nullptr; #endif } @@ -135,7 +135,7 @@ MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() { #ifdef TOOLS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir()); #else - return NULL; + return nullptr; #endif } @@ -151,7 +151,7 @@ MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() { #ifdef WINDOWS_ENABLED return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir()); #else - return NULL; + return nullptr; #endif } @@ -202,7 +202,7 @@ uint32_t godot_icall_BindingsGenerator_CsGlueVersion() { } int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes, MonoString **r_error_str) { - *r_error_str = NULL; + *r_error_str = nullptr; String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath); @@ -335,7 +335,7 @@ MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir; return GDMonoMarshal::mono_string_from_godot(install_root_dir); #else - return NULL; + return nullptr; #endif } diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index ce0b6ad0e6..324013e5e2 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -59,7 +59,7 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> if (r_dependencies.has(ref_name)) continue; - GDMonoAssembly *ref_assembly = NULL; + GDMonoAssembly *ref_assembly = nullptr; String path; bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe"); @@ -70,21 +70,21 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> path = search_dir.plus_file(ref_name); if (FileAccess::exists(path)) { GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true); - if (ref_assembly != NULL) + if (ref_assembly != nullptr) break; } } else { path = search_dir.plus_file(ref_name + ".dll"); if (FileAccess::exists(path)) { GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); - if (ref_assembly != NULL) + if (ref_assembly != nullptr) break; } path = search_dir.plus_file(ref_name + ".exe"); if (FileAccess::exists(path)) { GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true); - if (ref_assembly != NULL) + if (ref_assembly != nullptr) break; } } @@ -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 + "'."); @@ -116,7 +117,7 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencie String assembly_name = *key; String assembly_path = p_initial_dependencies[*key]; - GDMonoAssembly *assembly = NULL; + GDMonoAssembly *assembly = nullptr; bool load_success = GDMono::get_singleton()->load_assembly_from(assembly_name, assembly_path, &assembly, /* refonly: */ true); ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'."); |