Example #1
0
        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");
                }
            }
        }
Example #2
0
        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);
            }
        }
Example #3
0
        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);
                    }
                }
            }
        }
Example #4
0
        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);
            }
        }