public void BuildProjectPressed() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { return; // No solution to build } string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); if (File.Exists(editorScriptsMetadataPath)) { try { File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); } catch (IOException e) { GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}"); return; } } var godotDefines = new[] { OS.GetName(), Internal.GodotIs32Bits() ? "32" : "64" }; bool buildSuccess = BuildManager.BuildProjectBlocking("Tools", godotDefines); if (!buildSuccess) { return; } // Notify running game for hot-reload Internal.ScriptEditorDebuggerReloadScripts(); // Hot-reload in the editor GodotSharpEditor.Instance.GetNode <HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); if (Internal.IsAssembliesReloadingNeeded()) { Internal.ReloadAssemblies(softReload: false); } }
private static void AddPackageToFallbackFolder(string fallbackFolder, string nupkgPath, string packageId, string packageVersion) { // dotnet CLI provides no command for this, but we can do it manually. // // - The expected structure is as follows: // fallback_folder/ // <package.name>/<version>/ // <package.name>.<version>.nupkg // <package.name>.<version>.nupkg.sha512 // <package.name>.nuspec // ... extracted nupkg files (check code for excluded files) ... // // - <package.name> and <version> must be in lower case. // - The sha512 of the nupkg is base64 encoded. // - We can get the nuspec from the nupkg which is a Zip file. string packageIdLower = packageId.ToLower(); string packageVersionLower = packageVersion.ToLower(); string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower); string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg"); string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512"); if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath)) { return; // Already added (for speed we don't check if every file is properly extracted) } Directory.CreateDirectory(destDir); // Generate .nupkg.sha512 file using (var alg = SHA512.Create()) { alg.ComputeHash(File.ReadAllBytes(nupkgPath)); string base64Hash = Convert.ToBase64String(alg.Hash); File.WriteAllText(nupkgSha512DestPath, base64Hash); } // Extract nupkg ExtractNupkg(destDir, nupkgPath, packageId, packageVersion); // Copy .nupkg File.Copy(nupkgPath, nupkgDestPath); }
// NOTE: This will be replaced with C# source generators in 4.0 public static void GenerateEditorScriptMetadata() { string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); if (!File.Exists(editorScriptsMetadataPath)) { return; } try { File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); } catch (IOException e) { throw new IOException("Failed to copy scripts metadata file.", innerException: e); } }
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 assemblies = new Godot.Collections.Dictionary <string, string>(); string projectDllName = GodotSharpEditor.ProjectAssemblyName; string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig); string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll"); assemblies[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); } assemblies["Mono.Android"] = monoAndroidAssemblyPath; } string bclDir = DeterminePlatformBclDir(platform); var initialAssemblies = assemblies.Duplicate(); internal_GetExportedAssemblyDependencies(initialAssemblies, buildConfig, bclDir, assemblies); AddI18NAssemblies(assemblies, bclDir); 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 assembly in assemblies) { void AddToAssembliesDir(string fileSrcPath) { if (assembliesInsidePck) { string fileDstPath = Path.Combine(resAssembliesDir, fileSrcPath.GetFile()); AddFile(fileSrcPath, fileDstPath); } else { Debug.Assert(outputDataDir != null); string fileDstPath = Path.Combine(outputDataDir, "Assemblies", fileSrcPath.GetFile()); File.Copy(fileSrcPath, fileDstPath); } } string assemblySrcPath = assembly.Value; string assemblyPathWithoutExtension = Path.ChangeExtension(assemblySrcPath, null); string pdbSrcPath = assemblyPathWithoutExtension + ".pdb"; AddToAssembliesDir(assemblySrcPath); if (File.Exists(pdbSrcPath)) { AddToAssembliesDir(pdbSrcPath); } } // AOT compilation bool aotEnabled = platform == OS.Platforms.iOS || (bool)ProjectSettings.GetSetting("mono/export/aot/enabled"); if (aotEnabled) { 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, assemblies); } }
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 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 _ExportBeginImpl(string[] features, bool isDebug, string path, int flags) { _ = flags; // Unused if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { return; } if (!DeterminePlatformFromFeatures(features, out string platform)) { throw new NotSupportedException("Target platform not supported"); } if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS } .Contains(platform)) { throw new NotImplementedException("Target platform not yet implemented"); } string outputDir = new FileInfo(path).Directory?.FullName ?? throw new FileNotFoundException("Output base directory not found"); string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; // TODO: This works for now, as we only implemented support for x86 family desktop so far, but it needs to be fixed string arch = features.Contains("64") ? "x86_64" : "x86"; string ridOS = DetermineRuntimeIdentifierOS(platform); string ridArch = DetermineRuntimeIdentifierArch(arch); string runtimeIdentifier = $"{ridOS}-{ridArch}"; // Create temporary publish output directory string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet", $"{Process.GetCurrentProcess().Id}-{buildConfig}-{runtimeIdentifier}"); if (!Directory.Exists(publishOutputTempDir)) { Directory.CreateDirectory(publishOutputTempDir); } // Execute dotnet publish if (!BuildManager.PublishProjectBlocking(buildConfig, platform, runtimeIdentifier, publishOutputTempDir)) { throw new Exception("Failed to build project"); } string soExt = ridOS switch { OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll", OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib", _ => "so" }; if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll")) // NativeAOT shared library output && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}"))) { throw new NotSupportedException( "Publish succeeded but project assembly not found in the output directory"); } // Copy all files from the dotnet publish output directory to // a data directory next to the Godot output executable. string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject()); if (Directory.Exists(outputDataDir)) { Directory.Delete(outputDataDir, recursive: true); // Clean first } Directory.CreateDirectory(outputDataDir); foreach (string dir in Directory.GetDirectories(publishOutputTempDir, "*", SearchOption.AllDirectories)) { Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(publishOutputTempDir.Length + 1))); } foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories)) { File.Copy(file, Path.Combine(outputDataDir, file.Substring(publishOutputTempDir.Length + 1))); } }