public void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags) { // TODO Right now there is no way to stop the export process with an error if (File.Exists(GodotSharpDirs.ProjectSlnPath)) { string buildConfig = isDebug ? "Debug" : "Release"; string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); AddFile(scriptsMetadataPath, scriptsMetadataPath); // Turn export features into defines var godotDefines = features; if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines)) { GD.PushError("Failed to build project"); return; } // 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; { string templatesDir = Internal.FullTemplatesDir; string androidBclDir = Path.Combine(templatesDir, "android-bcl"); string customLibDir = features.Contains("Android") && Directory.Exists(androidBclDir) ? androidBclDir : string.Empty; GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies); } string apiConfig = isDebug ? "Debug" : "Release"; string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig); foreach (var dependency in dependencies) { string dependSrcPath = dependency.Value; string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile()); AddFile(dependSrcPath, dependDstPath); } } // Mono specific export template extras (data dir) ExportDataDirectory(features, isDebug, path); }
private void RebuildSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { return; // No solution to build } try { // Make sure our packages are added to the fallback folder NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); } catch (Exception e) { GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Rebuild" })) { return; // Build failed } // Notify running game for hot-reload Internal.EditorDebuggerNodeReloadScripts(); // Hot-reload in the editor GodotSharpEditor.Instance.GetNode <HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); if (Internal.IsAssembliesReloadingNeeded()) { Internal.ReloadAssemblies(softReload: false); } }
private static bool BuildProjectBlocking(BuildInfo buildInfo) { if (!File.Exists(buildInfo.Solution)) { return(true); // No solution to build } // Make sure the API assemblies are up to date before building the project. // We may not have had the chance to update the release API assemblies, and the debug ones // may have been deleted by the user at some point after they were loaded by the Godot editor. string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(buildInfo.Configuration == "ExportRelease" ? "Release" : "Debug"); if (!string.IsNullOrEmpty(apiAssembliesUpdateError)) { ShowBuildErrorDialog("Failed to update the Godot API assemblies"); return(false); } using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) { pr.Step("Building project solution", 0); if (!Build(buildInfo)) { ShowBuildErrorDialog("Failed to build project solution"); return(false); } } return(true); }
private void CleanSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { return; // No solution to build } BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Clean" }); }
private void CleanSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { return; // No solution to build } _ = BuildManager.CleanProjectBlocking("Debug"); }
private static void ExportDataDirectory(IEnumerable <string> features, bool debug, string path) { var featureSet = new HashSet <string>(features); if (!PlatformHasTemplateDir(featureSet)) { return; } string templateDirName = "data.mono"; if (featureSet.Contains("Windows")) { templateDirName += ".windows"; templateDirName += featureSet.Contains("64") ? ".64" : ".32"; } else if (featureSet.Contains("X11")) { templateDirName += ".x11"; templateDirName += featureSet.Contains("64") ? ".64" : ".32"; } else { throw new NotSupportedException("Target platform not supported"); } templateDirName += debug ? ".release_debug" : ".release"; string templateDirPath = Path.Combine(Internal.FullTemplatesDir, templateDirName); if (!Directory.Exists(templateDirPath)) { throw new FileNotFoundException("Data template directory not found"); } string outputDir = new FileInfo(path).Directory?.FullName ?? throw new FileNotFoundException("Base directory not found"); string outputDataDir = Path.Combine(outputDir, DataDirName); if (Directory.Exists(outputDataDir)) { Directory.Delete(outputDataDir, recursive: true); // Clean first } Directory.CreateDirectory(outputDataDir); foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories)) { Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1))); } foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories)) { File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1))); } }
private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir) { string target = isDebug ? "release_debug" : "release"; // 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}"; string templateDirPath = Path.Combine(Internal.FullTemplatesDir, TemplateDirName()); bool validTemplatePathFound = true; if (!Directory.Exists(templateDirPath)) { validTemplatePathFound = false; if (isDebug) { target = "debug"; // Support both 'release_debug' and 'debug' for the template data directory name templateDirPath = Path.Combine(Internal.FullTemplatesDir, TemplateDirName()); validTemplatePathFound = true; if (!Directory.Exists(templateDirPath)) { validTemplatePathFound = false; } } } if (!validTemplatePathFound) { throw new FileNotFoundException("Data template directory not found", templateDirPath); } 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(templateDirPath, "*", SearchOption.AllDirectories)) { Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1))); } foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories)) { File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1))); } return(outputDataDir); }
private static void RemoveOldIssuesFile(BuildInfo buildInfo) { string issuesFile = GetIssuesFilePath(buildInfo); if (!File.Exists(issuesFile)) { return; } File.Delete(issuesFile); }
private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir) { 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. string bits = features.Contains("64") ? "64" : "32"; string TemplateDirName() => $"data.mono.{platform}.{bits}.{target}"; string templateDirPath = Path.Combine(Internal.FullTemplatesDir, TemplateDirName()); if (!Directory.Exists(templateDirPath)) { templateDirPath = null; if (isDebug) { target = "debug"; // Support both 'release_debug' and 'debug' for the template data directory name templateDirPath = Path.Combine(Internal.FullTemplatesDir, TemplateDirName()); if (!Directory.Exists(templateDirPath)) { templateDirPath = null; } } } if (templateDirPath == null) { throw new FileNotFoundException("Data template directory not found"); } string outputDataDir = Path.Combine(outputDir, DataDirName); if (Directory.Exists(outputDataDir)) { Directory.Delete(outputDataDir, recursive: true); // Clean first } Directory.CreateDirectory(outputDataDir); foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories)) { Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1))); } foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories)) { File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1))); } return(outputDataDir); }
public static bool EditorBuildCallback() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { return(true); // No solution to build } GenerateEditorScriptMetadata(); if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) { return(true); // Requested play from an external editor/IDE which already built the project } return(BuildProjectBlocking("Debug")); }
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("Debug", godotDefines); if (!buildSuccess) { return; } // Notify running game for hot-reload Internal.EditorDebuggerNodeReloadScripts(); // 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); }
private static bool CleanProjectBlocking(BuildInfo buildInfo) { if (!File.Exists(buildInfo.Solution)) { return(true); // No solution to clean } using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1); pr.Step("Cleaning project solution", 0); if (!Build(buildInfo)) { ShowBuildErrorDialog("Failed to clean project solution"); return(false); } return(true); }
private static string DeterminePlatformBclDir(string platform) { string templatesDir = Internal.FullTemplatesDir; string platformBclDir = Path.Combine(templatesDir, "bcl", platform); if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) { string profile = DeterminePlatformBclProfile(platform); platformBclDir = Path.Combine(templatesDir, "bcl", profile); if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) { platformBclDir = null; // Use the one we're running on } } return(platformBclDir); }
private void _IssueActivated(int idx) { if (idx < 0 || idx >= issuesList.GetItemCount()) { throw new IndexOutOfRangeException("Item list index out of range"); } // Get correct issue idx from issue list int issueIndex = (int)issuesList.GetItemMetadata(idx); if (idx < 0 || idx >= issues.Count) { throw new IndexOutOfRangeException("Issue index out of range"); } BuildIssue issue = issues[issueIndex]; if (issue.ProjectFile.Empty() && issue.File.Empty()) { return; } string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir(); string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); if (!File.Exists(file)) { return; } file = ProjectSettings.LocalizePath(file); if (file.StartsWith("res://")) { var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType); if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column)) { Internal.EditorNodeShowScriptScreen(); } } }
private static string DeterminePlatformBclDir(string platform) { string templatesDir = Internal.FullTemplatesDir; string platformBclDir = Path.Combine(templatesDir, "bcl", platform); if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) { string profile = DeterminePlatformBclProfile(platform); platformBclDir = Path.Combine(templatesDir, "bcl", profile); if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) { if (PlatformRequiresCustomBcl(platform)) throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}"); platformBclDir = typeof(object).Assembly.Location.GetBaseDir(); // Use the one we're running on } } return platformBclDir; }
// 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); } }
public static void GenerateScriptsMetadata(string projectPath, string outputPath) { var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate(); bool IsUpToDate(string includeFile, ulong modifiedTime) { return(metadataDict.TryGetValue(includeFile, out var oldFileVar) && ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string, out ulong storedModifiedTime) && storedModifiedTime == modifiedTime); } var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile") .Select(path => ("res://" + path).SimplifyGodotPath()) .ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp()) .Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value)) .ToArray(); foreach (var pair in outdatedFiles) { metadataDict.Remove(pair.Key); string includeFile = pair.Key; if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata)) { metadataDict[includeFile] = fileMetadata; } } string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict); string baseDir = outputPath.GetBaseDir(); if (!Directory.Exists(baseDir)) { Directory.CreateDirectory(baseDir); } File.WriteAllText(outputPath, json); }
public static bool EditorBuildCallback() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { return(true); // No solution to build } try { // Make sure our packages are added to the fallback folder NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); } catch (Exception e) { GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) { return(true); // Requested play from an external editor/IDE which already built the project } return(BuildProjectBlocking("Debug")); }
private void RebuildSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { return; // No solution to build } BuildManager.GenerateEditorScriptMetadata(); if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Rebuild" })) { return; // Build failed } // 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 void AddFile(string srcPath, string dstPath, bool remap = false) { AddFile(dstPath, File.ReadAllBytes(srcPath), remap); }
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 ? "Debug" : "Release"; 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; { string platformBclDir = DeterminePlatformBclDir(platform); internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, platformBclDir, dependencies); } string apiConfig = isDebug ? "Debug" : "Release"; string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig); foreach (var dependency in dependencies) { string dependSrcPath = dependency.Value; string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile()); AddFile(dependSrcPath, dependDstPath); } // Mono specific export template extras (data dir) string outputDataDir = null; if (PlatformHasTemplateDir(platform)) { outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir); } // AOT if ((bool)ProjectSettings.GetSetting("mono/export/aot/enabled")) { AotCompileDependencies(features, platform, isDebug, outputDir, outputDataDir, dependencies); } }
private void AddFile(string srcPath, string dstPath, bool remap = false) { // Add file to the PCK AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap); }
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))); } }
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); } }
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 _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"); } string outputDir = new FileInfo(path).Directory?.FullName ?? throw new FileNotFoundException("Base directory not found"); string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform)) { 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; string bclDir = DeterminePlatformBclDir(platform); 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; } else if (platform == OS.Platforms.HTML5) { // Ideally these would be added automatically since they're referenced by the wasm BCL assemblies. // However, at least in the case of 'WebAssembly.Net.Http' for some reason the BCL assemblies // reference a different version even though the assembly is the same, for some weird reason. var wasmFrameworkAssemblies = new[] { "WebAssembly.Bindings", "WebAssembly.Net.WebSockets" }; foreach (string thisWasmFrameworkAssemblyName in wasmFrameworkAssemblies) { string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName + ".dll"); if (!File.Exists(thisWasmFrameworkAssemblyPath)) { throw new FileNotFoundException($"Assembly not found: '{thisWasmFrameworkAssemblyName}'", thisWasmFrameworkAssemblyPath); } assemblies[thisWasmFrameworkAssemblyName] = thisWasmFrameworkAssemblyPath; } // Assemblies that can have a different name in a newer version. Newer version must come first and it has priority. (string newName, string oldName)[] wasmFrameworkAssembliesOneOf = new[]
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); } }