Example #1
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("Tools", godotDefines);

            if (!buildSuccess)
            {
                return;
            }

            // Notify running game for hot-reload
            Internal.ScriptEditorDebuggerReloadScripts();

            // Hot-reload in the editor
            GodotSharpEditor.Instance.GetNode <HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();

            if (Internal.IsAssembliesReloadingNeeded())
            {
                Internal.ReloadAssemblies(softReload: false);
            }
        }
Example #2
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 #3
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 #4
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 #5
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 #6
0
        private void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags)
        {
            if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
            {
                return;
            }

            string platform = DeterminePlatformFromFeatures(features);

            if (platform == null)
            {
                throw new NotSupportedException("Target platform not supported");
            }

            string outputDir = new FileInfo(path).Directory?.FullName ??
                               throw new FileNotFoundException("Base directory not found");

            string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";

            string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");

            CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);

            AddFile(scriptsMetadataPath, scriptsMetadataPath);

            // Turn export features into defines
            var godotDefines = features;

            if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
            {
                throw new Exception("Failed to build project");
            }

            // Add dependency assemblies

            var dependencies = new Godot.Collections.Dictionary <string, string>();

            var projectDllName = (string)ProjectSettings.GetSetting("application/config/name");

            if (projectDllName.Empty())
            {
                projectDllName = "UnnamedProject";
            }

            string projectDllSrcDir  = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
            string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");

            dependencies[projectDllName] = projectDllSrcPath;

            if (platform == OS.Platforms.Android)
            {
                string godotAndroidExtProfileDir = GetBclProfileDir("godot_android_ext");
                string monoAndroidAssemblyPath   = Path.Combine(godotAndroidExtProfileDir, "Mono.Android.dll");

                if (!File.Exists(monoAndroidAssemblyPath))
                {
                    throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath);
                }

                dependencies["Mono.Android"] = monoAndroidAssemblyPath;
            }

            var initialDependencies = dependencies.Duplicate();

            internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, DeterminePlatformBclDir(platform), dependencies);

            AddI18NAssemblies(dependencies, platform);

            string outputDataDir = null;

            if (PlatformHasTemplateDir(platform))
            {
                outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir);
            }

            string apiConfig        = isDebug ? "Debug" : "Release";
            string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig);

            bool assembliesInsidePck = (bool)ProjectSettings.GetSetting("mono/export/export_assemblies_inside_pck") || outputDataDir == null;

            if (!assembliesInsidePck)
            {
                string outputDataGameAssembliesDir = Path.Combine(outputDataDir, "Assemblies");
                if (!Directory.Exists(outputDataGameAssembliesDir))
                {
                    Directory.CreateDirectory(outputDataGameAssembliesDir);
                }
            }

            foreach (var dependency in dependencies)
            {
                string dependSrcPath = dependency.Value;

                if (assembliesInsidePck)
                {
                    string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile());
                    AddFile(dependSrcPath, dependDstPath);
                }
                else
                {
                    string dependDstPath = Path.Combine(outputDataDir, "Assemblies", dependSrcPath.GetFile());
                    File.Copy(dependSrcPath, dependDstPath);
                }
            }

            // AOT

            if ((bool)ProjectSettings.GetSetting("mono/export/aot/enabled"))
            {
                AotCompileDependencies(features, platform, isDebug, outputDir, outputDataDir, dependencies);
            }
        }
Example #7
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)));
            }
        }