Example #1
0
        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);
        }
Example #2
0
        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);
            }
        }
Example #3
0
        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);
        }
Example #4
0
 private void CleanSolution()
 {
     if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
     {
         return; // No solution to build
     }
     BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Clean" });
 }
Example #5
0
 private void CleanSolution()
 {
     if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
     {
         return; // No solution to build
     }
     _ = BuildManager.CleanProjectBlocking("Debug");
 }
Example #6
0
        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)));
            }
        }
Example #7
0
        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);
        }
Example #8
0
        private static void RemoveOldIssuesFile(BuildInfo buildInfo)
        {
            string issuesFile = GetIssuesFilePath(buildInfo);

            if (!File.Exists(issuesFile))
            {
                return;
            }

            File.Delete(issuesFile);
        }
Example #9
0
        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);
        }
Example #10
0
        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"));
        }
Example #11
0
        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);
            }
        }
Example #12
0
        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);
        }
Example #13
0
        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);
        }
Example #14
0
        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);
        }
Example #15
0
        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();
                }
            }
        }
Example #16
0
        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;
        }
Example #17
0
        // 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);
            }
        }
Example #18
0
        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);
        }
Example #19
0
        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"));
        }
Example #20
0
        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);
            }
        }
Example #21
0
 private void AddFile(string srcPath, string dstPath, bool remap = false)
 {
     AddFile(dstPath, File.ReadAllBytes(srcPath), remap);
 }
Example #22
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 ? "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);
            }
        }
Example #23
0
 private void AddFile(string srcPath, string dstPath, bool remap = false)
 {
     // Add file to the PCK
     AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap);
 }
Example #24
0
        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)));
            }
        }
Example #25
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);
            }
        }
Example #26
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 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);
            }
        }
Example #27
0
        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[]
Example #28
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 #29
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);
            }
        }