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 void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags) { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { return; } string platform = DeterminePlatformFromFeatures(features); if (platform == null) { throw new NotSupportedException("Target platform not supported"); } string outputDir = new FileInfo(path).Directory?.FullName ?? throw new FileNotFoundException("Base directory not found"); string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); AddFile(scriptsMetadataPath, scriptsMetadataPath); // Turn export features into defines var godotDefines = features; if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines)) { throw new Exception("Failed to build project"); } // Add dependency assemblies var dependencies = new Godot.Collections.Dictionary <string, string>(); var projectDllName = (string)ProjectSettings.GetSetting("application/config/name"); if (projectDllName.Empty()) { projectDllName = "UnnamedProject"; } string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig); string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll"); dependencies[projectDllName] = projectDllSrcPath; if (platform == OS.Platforms.Android) { string godotAndroidExtProfileDir = GetBclProfileDir("godot_android_ext"); string monoAndroidAssemblyPath = Path.Combine(godotAndroidExtProfileDir, "Mono.Android.dll"); if (!File.Exists(monoAndroidAssemblyPath)) { throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath); } dependencies["Mono.Android"] = monoAndroidAssemblyPath; } var initialDependencies = dependencies.Duplicate(); internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, DeterminePlatformBclDir(platform), dependencies); AddI18NAssemblies(dependencies, platform); string outputDataDir = null; if (PlatformHasTemplateDir(platform)) { outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir); } string apiConfig = isDebug ? "Debug" : "Release"; string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig); bool assembliesInsidePck = (bool)ProjectSettings.GetSetting("mono/export/export_assemblies_inside_pck") || outputDataDir == null; if (!assembliesInsidePck) { string outputDataGameAssembliesDir = Path.Combine(outputDataDir, "Assemblies"); if (!Directory.Exists(outputDataGameAssembliesDir)) { Directory.CreateDirectory(outputDataGameAssembliesDir); } } foreach (var dependency in dependencies) { string dependSrcPath = dependency.Value; if (assembliesInsidePck) { string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile()); AddFile(dependSrcPath, dependDstPath); } else { string dependDstPath = Path.Combine(outputDataDir, "Assemblies", dependSrcPath.GetFile()); File.Copy(dependSrcPath, dependDstPath); } } // AOT if ((bool)ProjectSettings.GetSetting("mono/export/aot/enabled")) { AotCompileDependencies(features, platform, isDebug, outputDir, outputDataDir, dependencies); } }
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); } } } }
public static void GenerateScriptsMetadata(string projectPath, string outputPath) { if (File.Exists(outputPath)) { File.Delete(outputPath); } var oldDict = Internal.GetScriptsMetadataOrNothing(); var newDict = new Godot.Collections.Dictionary <string, object>(); foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile")) { string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath(); ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp(); if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar)) { var oldFileDict = (Dictionary)oldFileVar; if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime)) { if (storedModifiedTime == modifiedTime) { // No changes so no need to parse again newDict[projectIncludeFile] = oldFileDict; continue; } } } ScriptClassParser.ParseFileOrThrow(projectIncludeFile, out var classes); string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile); var classDict = new Dictionary(); foreach (var classDecl in classes) { if (classDecl.BaseCount == 0) { continue; // Does not inherit nor implement anything, so it can't be a script class } string classCmp = classDecl.Nested ? classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) : classDecl.Name; if (classCmp != searchName) { continue; } classDict["namespace"] = classDecl.Namespace; classDict["class_name"] = classDecl.Name; classDict["nested"] = classDecl.Nested; break; } if (classDict.Count == 0) { continue; // Not found } newDict[projectIncludeFile] = new Dictionary { ["modified_time"] = $"{modifiedTime}", ["class"] = classDict }; } if (newDict.Count > 0) { string json = JSON.Print(newDict); string baseDir = outputPath.GetBaseDir(); if (!Directory.Exists(baseDir)) { Directory.CreateDirectory(baseDir); } File.WriteAllText(outputPath, json); } }