diff options
Diffstat (limited to 'modules/mono/editor')
64 files changed, 4704 insertions, 3675 deletions
diff --git a/modules/mono/editor/GodotSharpTools/.gitignore b/modules/mono/editor/GodotSharpTools/.gitignore deleted file mode 100644 index 296ad48834..0000000000 --- a/modules/mono/editor/GodotSharpTools/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# nuget packages -packages
\ No newline at end of file diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs deleted file mode 100644 index e5044feb75..0000000000 --- a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs +++ /dev/null @@ -1,425 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security; -using Microsoft.Build.Framework; - -namespace GodotSharpTools.Build -{ - public class BuildInstance : IDisposable - { - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode); - - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static string godot_icall_BuildInstance_get_MSBuildPath(); - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static string godot_icall_BuildInstance_get_MonoWindowsBinDir(); - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static bool godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows(); - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static bool godot_icall_BuildInstance_get_PrintBuildOutput(); - - private static string GetMSBuildPath() - { - string msbuildPath = godot_icall_BuildInstance_get_MSBuildPath(); - - if (msbuildPath == null) - throw new FileNotFoundException("Cannot find the MSBuild executable."); - - return msbuildPath; - } - - private static string MonoWindowsBinDir - { - get - { - string monoWinBinDir = godot_icall_BuildInstance_get_MonoWindowsBinDir(); - - if (monoWinBinDir == null) - throw new FileNotFoundException("Cannot find the Windows Mono binaries directory."); - - return monoWinBinDir; - } - } - - private static bool UsingMonoMSBuildOnWindows - { - get - { - return godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows(); - } - } - - private static bool PrintBuildOutput - { - get - { - return godot_icall_BuildInstance_get_PrintBuildOutput(); - } - } - - private string solution; - private string config; - - private Process process; - - private int exitCode; - public int ExitCode { get { return exitCode; } } - - public bool IsRunning { get { return process != null && !process.HasExited; } } - - public BuildInstance(string solution, string config) - { - this.solution = solution; - this.config = config; - } - - public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) - { - List<string> customPropertiesList = new List<string>(); - - if (customProperties != null) - customPropertiesList.AddRange(customProperties); - - string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList); - - ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); - - bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput; - - if (!redirectOutput) // TODO: or if stdout verbose - Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); - - startInfo.RedirectStandardOutput = redirectOutput; - startInfo.RedirectStandardError = redirectOutput; - startInfo.UseShellExecute = false; - - if (UsingMonoMSBuildOnWindows) - { - // These environment variables are required for Mono's MSBuild to find the compilers. - // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. - string monoWinBinDir = MonoWindowsBinDir; - startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); - startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); - startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); - } - - // Needed when running from Developer Command Prompt for VS - RemovePlatformVariable(startInfo.EnvironmentVariables); - - using (Process process = new Process()) - { - process.StartInfo = startInfo; - - process.Start(); - - if (redirectOutput) - { - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } - - process.WaitForExit(); - - exitCode = process.ExitCode; - } - - return true; - } - - public bool BuildAsync(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) - { - if (process != null) - throw new InvalidOperationException("Already in use"); - - List<string> customPropertiesList = new List<string>(); - - if (customProperties != null) - customPropertiesList.AddRange(customProperties); - - string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList); - - ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs); - - bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput; - - if (!redirectOutput) // TODO: or if stdout verbose - Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); - - startInfo.RedirectStandardOutput = redirectOutput; - startInfo.RedirectStandardError = redirectOutput; - startInfo.UseShellExecute = false; - - if (UsingMonoMSBuildOnWindows) - { - // These environment variables are required for Mono's MSBuild to find the compilers. - // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. - string monoWinBinDir = MonoWindowsBinDir; - startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); - startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); - startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); - } - - // Needed when running from Developer Command Prompt for VS - RemovePlatformVariable(startInfo.EnvironmentVariables); - - process = new Process(); - process.StartInfo = startInfo; - process.EnableRaisingEvents = true; - process.Exited += new EventHandler(BuildProcess_Exited); - - process.Start(); - - if (redirectOutput) - { - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } - - return true; - } - - private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, List<string> customProperties) - { - string arguments = string.Format(@"""{0}"" /v:normal /t:Rebuild ""/p:{1}"" ""/l:{2},{3};{4}""", - solution, - "Configuration=" + config, - typeof(GodotBuildLogger).FullName, - loggerAssemblyPath, - loggerOutputDir - ); - - foreach (string customProperty in customProperties) - { - arguments += " /p:" + customProperty; - } - - return arguments; - } - - private void RemovePlatformVariable(StringDictionary environmentVariables) - { - // EnvironmentVariables is case sensitive? Seriously? - - List<string> platformEnvironmentVariables = new List<string>(); - - foreach (string env in environmentVariables.Keys) - { - if (env.ToUpper() == "PLATFORM") - platformEnvironmentVariables.Add(env); - } - - foreach (string env in platformEnvironmentVariables) - environmentVariables.Remove(env); - } - - private void BuildProcess_Exited(object sender, System.EventArgs e) - { - exitCode = process.ExitCode; - - godot_icall_BuildInstance_ExitCallback(solution, config, exitCode); - - Dispose(); - } - - private static bool IsDebugMSBuildRequested() - { - return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1"; - } - - public void Dispose() - { - if (process != null) - { - process.Dispose(); - process = null; - } - } - } - - public class GodotBuildLogger : ILogger - { - public string Parameters { get; set; } - public LoggerVerbosity Verbosity { get; set; } - - public void Initialize(IEventSource eventSource) - { - if (null == Parameters) - throw new LoggerException("Log directory was not set."); - - string[] parameters = Parameters.Split(new[] { ';' }); - - string logDir = parameters[0]; - - if (String.IsNullOrEmpty(logDir)) - throw new LoggerException("Log directory was not set."); - - if (parameters.Length > 1) - throw new LoggerException("Too many parameters passed."); - - string logFile = Path.Combine(logDir, "msbuild_log.txt"); - string issuesFile = Path.Combine(logDir, "msbuild_issues.csv"); - - try - { - if (!Directory.Exists(logDir)) - Directory.CreateDirectory(logDir); - - this.logStreamWriter = new StreamWriter(logFile); - this.issuesStreamWriter = new StreamWriter(issuesFile); - } - catch (Exception ex) - { - if - ( - ex is UnauthorizedAccessException - || ex is ArgumentNullException - || ex is PathTooLongException - || ex is DirectoryNotFoundException - || ex is NotSupportedException - || ex is ArgumentException - || ex is SecurityException - || ex is IOException - ) - { - throw new LoggerException("Failed to create log file: " + ex.Message); - } - else - { - // Unexpected failure - throw; - } - } - - eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted); - eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted); - eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised); - eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised); - eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised); - eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished); - } - - void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) - { - string line = String.Format("{0}({1},{2}): error {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message); - - if (e.ProjectFile.Length > 0) - line += string.Format(" [{0}]", e.ProjectFile); - - WriteLine(line); - - string errorLine = String.Format(@"error,{0},{1},{2},{3},{4},{5}", - e.File.CsvEscape(), e.LineNumber, e.ColumnNumber, - e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile.CsvEscape()); - issuesStreamWriter.WriteLine(errorLine); - } - - void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) - { - string line = String.Format("{0}({1},{2}): warning {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile); - - if (e.ProjectFile != null && e.ProjectFile.Length > 0) - line += string.Format(" [{0}]", e.ProjectFile); - - WriteLine(line); - - string warningLine = String.Format(@"warning,{0},{1},{2},{3},{4},{5}", - e.File.CsvEscape(), e.LineNumber, e.ColumnNumber, - e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty); - issuesStreamWriter.WriteLine(warningLine); - } - - void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) - { - // BuildMessageEventArgs adds Importance to BuildEventArgs - // Let's take account of the verbosity setting we've been passed in deciding whether to log the message - if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal)) - || (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal)) - || (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed)) - ) - { - WriteLineWithSenderAndMessage(String.Empty, e); - } - } - - void eventSource_TaskStarted(object sender, TaskStartedEventArgs e) - { - // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName - // To keep this log clean, this logger will ignore these events. - } - - void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) - { - WriteLine(e.Message); - indent++; - } - - void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) - { - indent--; - WriteLine(e.Message); - } - - /// <summary> - /// Write a line to the log, adding the SenderName - /// </summary> - private void WriteLineWithSender(string line, BuildEventArgs e) - { - if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/)) - { - // Well, if the sender name is MSBuild, let's leave it out for prettiness - WriteLine(line); - } - else - { - WriteLine(e.SenderName + ": " + line); - } - } - - /// <summary> - /// Write a line to the log, adding the SenderName and Message - /// (these parameters are on all MSBuild event argument objects) - /// </summary> - private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e) - { - if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/)) - { - // Well, if the sender name is MSBuild, let's leave it out for prettiness - WriteLine(line + e.Message); - } - else - { - WriteLine(e.SenderName + ": " + line + e.Message); - } - } - - private void WriteLine(string line) - { - for (int i = indent; i > 0; i--) - { - logStreamWriter.Write("\t"); - } - logStreamWriter.WriteLine(line); - } - - public void Shutdown() - { - logStreamWriter.Close(); - issuesStreamWriter.Close(); - } - - public bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) - { - return this.Verbosity >= checkVerbosity; - } - - private StreamWriter logStreamWriter; - private StreamWriter issuesStreamWriter; - private int indent; - } -} diff --git a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs b/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs deleted file mode 100644 index 44a43f0ddd..0000000000 --- a/modules/mono/editor/GodotSharpTools/Editor/GodotSharpExport.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace GodotSharpTools.Editor -{ - public static class GodotSharpExport - { - public static void _ExportBegin(string[] features, bool debug, string path, int flags) - { - var featureSet = new HashSet<string>(features); - - if (PlatformHasTemplateDir(featureSet)) - { - 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(GetTemplatesDir(), templateDirName); - - if (!Directory.Exists(templateDirPath)) - throw new FileNotFoundException("Data template directory not found"); - - string outputDir = new FileInfo(path).Directory.FullName; - - string outputDataDir = Path.Combine(outputDir, GetDataDirName()); - - 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))); - } - } - } - - public static bool PlatformHasTemplateDir(HashSet<string> featureSet) - { - // OSX export templates are contained in a zip, so we place - // our custom template inside it and let Godot do the rest. - return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f)); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - extern static string GetTemplatesDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - extern static string GetDataDirName(); - } -} diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln b/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln deleted file mode 100644 index 5f7d0e8a39..0000000000 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln +++ /dev/null @@ -1,17 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpTools", "GodotSharpTools.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/modules/mono/editor/GodotSharpTools/Utils/OS.cs b/modules/mono/editor/GodotSharpTools/Utils/OS.cs deleted file mode 100644 index 148e954e77..0000000000 --- a/modules/mono/editor/GodotSharpTools/Utils/OS.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.CompilerServices; - -namespace GodotSharpTools.Utils -{ - public static class OS - { - [MethodImpl(MethodImplOptions.InternalCall)] - extern static string GetPlatformName(); - - const string HaikuName = "Haiku"; - const string OSXName = "OSX"; - const string ServerName = "Server"; - const string UWPName = "UWP"; - const string WindowsName = "Windows"; - const string X11Name = "X11"; - - public static bool IsHaiku() - { - return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsOSX() - { - return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsServer() - { - return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsUWP() - { - return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsWindows() - { - return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - public static bool IsX11() - { - return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); - } - - static bool? IsUnixCache = null; - static readonly string[] UnixPlatforms = new string[] { HaikuName, OSXName, ServerName, X11Name }; - - public static bool IsUnix() - { - if (IsUnixCache.HasValue) - return IsUnixCache.Value; - - string osName = GetPlatformName(); - IsUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase)); - return IsUnixCache.Value; - } - } -} diff --git a/modules/mono/editor/GodotTools/.gitignore b/modules/mono/editor/GodotTools/.gitignore new file mode 100644 index 0000000000..48e2f914d8 --- /dev/null +++ b/modules/mono/editor/GodotTools/.gitignore @@ -0,0 +1,356 @@ +# Rider +.idea/ + +# Visual Studio Code +.vscode/ + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs new file mode 100644 index 0000000000..a0f6f1ff32 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -0,0 +1,186 @@ +using System; +using System.IO; +using System.Security; +using Microsoft.Build.Framework; +using GodotTools.Core; + +namespace GodotTools.BuildLogger +{ + public class GodotBuildLogger : ILogger + { + public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location); + + public string Parameters { get; set; } + public LoggerVerbosity Verbosity { get; set; } + + public void Initialize(IEventSource eventSource) + { + if (null == Parameters) + throw new LoggerException("Log directory was not set."); + + var parameters = Parameters.Split(new[] {';'}); + + string logDir = parameters[0]; + + if (string.IsNullOrEmpty(logDir)) + throw new LoggerException("Log directory was not set."); + + if (parameters.Length > 1) + throw new LoggerException("Too many parameters passed."); + + string logFile = Path.Combine(logDir, "msbuild_log.txt"); + string issuesFile = Path.Combine(logDir, "msbuild_issues.csv"); + + try + { + if (!Directory.Exists(logDir)) + Directory.CreateDirectory(logDir); + + logStreamWriter = new StreamWriter(logFile); + issuesStreamWriter = new StreamWriter(issuesFile); + } + catch (Exception ex) + { + if (ex is UnauthorizedAccessException + || ex is ArgumentNullException + || ex is PathTooLongException + || ex is DirectoryNotFoundException + || ex is NotSupportedException + || ex is ArgumentException + || ex is SecurityException + || ex is IOException) + { + throw new LoggerException("Failed to create log file: " + ex.Message); + } + else + { + // Unexpected failure + throw; + } + } + + eventSource.ProjectStarted += eventSource_ProjectStarted; + eventSource.TaskStarted += eventSource_TaskStarted; + eventSource.MessageRaised += eventSource_MessageRaised; + eventSource.WarningRaised += eventSource_WarningRaised; + eventSource.ErrorRaised += eventSource_ErrorRaised; + eventSource.ProjectFinished += eventSource_ProjectFinished; + } + + void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) + { + string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}"; + + if (e.ProjectFile.Length > 0) + line += $" [{e.ProjectFile}]"; + + WriteLine(line); + + string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + $@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}"; + issuesStreamWriter.WriteLine(errorLine); + } + + void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) + { + string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}"; + + if (!string.IsNullOrEmpty(e.ProjectFile)) + line += $" [{e.ProjectFile}]"; + + WriteLine(line); + + string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," + + $@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}"; + issuesStreamWriter.WriteLine(warningLine); + } + + private void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) + { + // BuildMessageEventArgs adds Importance to BuildEventArgs + // Let's take account of the verbosity setting we've been passed in deciding whether to log the message + if (e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal) + || e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal) + || e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed)) + { + WriteLineWithSenderAndMessage(string.Empty, e); + } + } + + private void eventSource_TaskStarted(object sender, TaskStartedEventArgs e) + { + // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName + // To keep this log clean, this logger will ignore these events. + } + + private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) + { + WriteLine(e.Message); + indent++; + } + + private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) + { + indent--; + WriteLine(e.Message); + } + + /// <summary> + /// Write a line to the log, adding the SenderName + /// </summary> + private void WriteLineWithSender(string line, BuildEventArgs e) + { + if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase)) + { + // Well, if the sender name is MSBuild, let's leave it out for prettiness + WriteLine(line); + } + else + { + WriteLine(e.SenderName + ": " + line); + } + } + + /// <summary> + /// Write a line to the log, adding the SenderName and Message + /// (these parameters are on all MSBuild event argument objects) + /// </summary> + private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e) + { + if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase)) + { + // Well, if the sender name is MSBuild, let's leave it out for prettiness + WriteLine(line + e.Message); + } + else + { + WriteLine(e.SenderName + ": " + line + e.Message); + } + } + + private void WriteLine(string line) + { + for (int i = indent; i > 0; i--) + { + logStreamWriter.Write("\t"); + } + + logStreamWriter.WriteLine(line); + } + + public void Shutdown() + { + logStreamWriter.Close(); + issuesStreamWriter.Close(); + } + + private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) + { + return Verbosity >= checkVerbosity; + } + + private StreamWriter logStreamWriter; + private StreamWriter issuesStreamWriter; + private int indent; + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj new file mode 100644 index 0000000000..f3ac353c0f --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>GodotTools.BuildLogger</RootNamespace> + <AssemblyName>GodotTools.BuildLogger</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugSymbols>true</DebugSymbols> + <DebugType>portable</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <PlatformTarget>AnyCPU</PlatformTarget> + <DebugType>portable</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="Microsoft.Build.Framework" /> + <Reference Include="System" /> + <Reference Include="System.Core" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="GodotBuildLogger.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> + <Project>{639e48bd-44e5-4091-8edd-22d36dc0768d}</Project> + <Name>GodotTools.Core</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- To modify your build process, add your task inside one of the targets below and uncomment it. + Other similar extension points exist, see Microsoft.Common.targets. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..8717c4901e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("GodotTools.BuildLogger")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6CE9A984-37B1-4F8A-8FE9-609F05F071B3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj new file mode 100644 index 0000000000..f36b40f87c --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>GodotTools.Core</RootNamespace> + <AssemblyName>GodotTools.Core</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + </ItemGroup> + <ItemGroup> + <Compile Include="ProcessExtensions.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="StringExtensions.cs" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs new file mode 100644 index 0000000000..43d40f2ad9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace GodotTools.Core +{ + public static class ProcessExtensions + { + public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken)) + { + var tcs = new TaskCompletionSource<bool>(); + + void ProcessExited(object sender, EventArgs e) + { + tcs.TrySetResult(true); + } + + process.EnableRaisingEvents = true; + process.Exited += ProcessExited; + + try + { + if (process.HasExited) + return; + + using (cancellationToken.Register(() => tcs.TrySetCanceled())) + { + await tcs.Task; + } + } + finally + { + process.Exited -= ProcessExited; + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..699ae6e741 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("GodotTools.Core")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/modules/mono/editor/GodotSharpTools/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index b0436d2f18..8cd7e76303 100644 --- a/modules/mono/editor/GodotSharpTools/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -1,7 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; -namespace GodotSharpTools +namespace GodotTools.Core { public static class StringExtensions { @@ -25,7 +26,7 @@ namespace GodotSharpTools path = path.Replace('\\', '/'); - string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + string[] parts = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim(); @@ -37,18 +38,40 @@ namespace GodotSharpTools public static bool IsAbsolutePath(this string path) { return path.StartsWith("/", StringComparison.Ordinal) || - path.StartsWith("\\", StringComparison.Ordinal) || - path.StartsWith(driveRoot, StringComparison.Ordinal); + path.StartsWith("\\", StringComparison.Ordinal) || + path.StartsWith(driveRoot, StringComparison.Ordinal); } public static string CsvEscape(this string value, char delimiter = ',') { - bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1; + bool hasSpecialChar = value.IndexOfAny(new char[] {'\"', '\n', '\r', delimiter}) != -1; if (hasSpecialChar) return "\"" + value.Replace("\"", "\"\"") + "\""; return value; } + + public static string ToSafeDirName(this string dirName, bool allowDirSeparator) + { + var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"}; + + if (allowDirSeparator) + { + // Directory separators are allowed, but disallow ".." to avoid going up the filesystem + invalidChars.Add(".."); + } + else + { + invalidChars.Add("/"); + } + + string safeDirName = dirName.Replace("\\", "/").Trim(); + + foreach (string invalidChar in invalidChars) + safeDirName = safeDirName.Replace(invalidChar, "-"); + + return safeDirName; + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs new file mode 100644 index 0000000000..345a472185 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiAssembliesInfo.cs @@ -0,0 +1,15 @@ +namespace GodotTools +{ + public static class ApiAssemblyNames + { + public const string SolutionName = "GodotSharp"; + public const string Core = "GodotSharp"; + public const string Editor = "GodotSharpEditor"; + } + + public enum ApiAssemblyType + { + Core, + Editor + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs new file mode 100644 index 0000000000..bfae2afc13 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ApiSolutionGenerator.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.IO; + +namespace GodotTools.ProjectEditor +{ + public static class ApiSolutionGenerator + { + public static void GenerateApiSolution(string solutionDir, + string coreProjDir, IEnumerable<string> coreCompileItems, + string editorProjDir, IEnumerable<string> editorCompileItems) + { + var solution = new DotNetSolution(ApiAssemblyNames.SolutionName); + + solution.DirectoryPath = solutionDir; + + // GodotSharp project + + const string coreApiAssemblyName = ApiAssemblyNames.Core; + + string coreGuid = ProjectGenerator.GenCoreApiProject(coreProjDir, coreCompileItems); + + var coreProjInfo = new DotNetSolution.ProjectInfo + { + Guid = coreGuid, + PathRelativeToSolution = Path.Combine(coreApiAssemblyName, $"{coreApiAssemblyName}.csproj") + }; + coreProjInfo.Configs.Add("Debug"); + coreProjInfo.Configs.Add("Release"); + + solution.AddNewProject(coreApiAssemblyName, coreProjInfo); + + // GodotSharpEditor project + + const string editorApiAssemblyName = ApiAssemblyNames.Editor; + + string editorGuid = ProjectGenerator.GenEditorApiProject(editorProjDir, + $"../{coreApiAssemblyName}/{coreApiAssemblyName}.csproj", editorCompileItems); + + var editorProjInfo = new DotNetSolution.ProjectInfo(); + editorProjInfo.Guid = editorGuid; + editorProjInfo.PathRelativeToSolution = Path.Combine(editorApiAssemblyName, $"{editorApiAssemblyName}.csproj"); + editorProjInfo.Configs.Add("Debug"); + editorProjInfo.Configs.Add("Release"); + + solution.AddNewProject(editorApiAssemblyName, editorProjInfo); + + // Save solution + + solution.Save(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs new file mode 100644 index 0000000000..76cb249acf --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs @@ -0,0 +1,122 @@ +using GodotTools.Core; +using System.Collections.Generic; +using System.IO; + +namespace GodotTools.ProjectEditor +{ + public class DotNetSolution + { + private string directoryPath; + private readonly Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>(); + + public string Name { get; } + + public string DirectoryPath + { + get => directoryPath; + set => directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value); + } + + public class ProjectInfo + { + public string Guid; + public string PathRelativeToSolution; + public List<string> Configs = new List<string>(); + } + + public void AddNewProject(string name, ProjectInfo projectInfo) + { + projects[name] = projectInfo; + } + + public bool HasProject(string name) + { + return projects.ContainsKey(name); + } + + public ProjectInfo GetProjectInfo(string name) + { + return projects[name]; + } + + public bool RemoveProject(string name) + { + return projects.Remove(name); + } + + public void Save() + { + if (!Directory.Exists(DirectoryPath)) + throw new FileNotFoundException("The solution directory does not exist."); + + string projectsDecl = string.Empty; + string slnPlatformsCfg = string.Empty; + string projPlatformsCfg = string.Empty; + + bool isFirstProject = true; + + foreach (var pair in projects) + { + string name = pair.Key; + ProjectInfo projectInfo = pair.Value; + + if (!isFirstProject) + projectsDecl += "\n"; + + projectsDecl += string.Format(ProjectDeclaration, + name, projectInfo.PathRelativeToSolution.Replace("/", "\\"), projectInfo.Guid); + + for (int i = 0; i < projectInfo.Configs.Count; i++) + { + string config = projectInfo.Configs[i]; + + if (i != 0 || !isFirstProject) + { + slnPlatformsCfg += "\n"; + projPlatformsCfg += "\n"; + } + + slnPlatformsCfg += string.Format(SolutionPlatformsConfig, config); + projPlatformsCfg += string.Format(ProjectPlatformsConfig, projectInfo.Guid, config); + } + + isFirstProject = false; + } + + string solutionPath = Path.Combine(DirectoryPath, Name + ".sln"); + string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg); + + File.WriteAllText(solutionPath, content); + } + + public DotNetSolution(string name) + { + Name = name; + } + + const string SolutionTemplate = +@"Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +{0} +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution +{1} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution +{2} + EndGlobalSection +EndGlobal +"; + + const string ProjectDeclaration = +@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}"" +EndProject"; + + const string SolutionPlatformsConfig = +@" {0}|Any CPU = {0}|Any CPU"; + + const string ProjectPlatformsConfig = +@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU + {{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU"; + } +} diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 2871c041f5..08b8ba3946 100644 --- a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -1,12 +1,12 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid> <OutputType>Library</OutputType> - <RootNamespace>GodotSharpTools</RootNamespace> - <AssemblyName>GodotSharpTools</AssemblyName> + <RootNamespace>GodotTools.ProjectEditor</RootNamespace> + <AssemblyName>GodotTools.ProjectEditor</AssemblyName> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath> </PropertyGroup> @@ -21,7 +21,6 @@ <ConsolePause>false</ConsolePause> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> - <DebugType>portable</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release</OutputPath> <ErrorReport>prompt</ErrorReport> @@ -31,25 +30,35 @@ <ItemGroup> <Reference Include="System" /> <Reference Include="Microsoft.Build" /> - <Reference Include="Microsoft.Build.Framework" /> <Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL"> - <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <!-- + When building Godot with 'mono_glue=no' SCons will build this project alone instead of the + entire solution. $(SolutionDir) is not defined in that case, so we need to workaround that. + We make SCons restore the NuGet packages in the project directory instead in this case. + --> + <HintPath Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <HintPath>$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> + <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> <!-- Are you happy CI? --> </Reference> </ItemGroup> <ItemGroup> - <Compile Include="StringExtensions.cs" /> - <Compile Include="Build\BuildSystem.cs" /> - <Compile Include="Editor\MonoDevelopInstance.cs" /> - <Compile Include="Project\ProjectExtensions.cs" /> - <Compile Include="Project\IdentifierUtils.cs" /> - <Compile Include="Project\ProjectGenerator.cs" /> - <Compile Include="Project\ProjectUtils.cs" /> + <Compile Include="ApiAssembliesInfo.cs" /> + <Compile Include="ApiSolutionGenerator.cs" /> + <Compile Include="DotNetSolution.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Utils\OS.cs" /> - <Compile Include="Editor\GodotSharpExport.cs" /> + <Compile Include="IdentifierUtils.cs" /> + <Compile Include="ProjectExtensions.cs" /> + <Compile Include="ProjectGenerator.cs" /> + <Compile Include="ProjectUtils.cs" /> </ItemGroup> <ItemGroup> <None Include="packages.config" /> </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> + <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project> + <Name>GodotTools.Core</Name> + </ProjectReference> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> -</Project>
\ No newline at end of file +</Project> diff --git a/modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs index 83e2d2cf8d..f93eb9a1fa 100644 --- a/modules/mono/editor/GodotSharpTools/Project/IdentifierUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class IdentifierUtils { @@ -12,7 +12,7 @@ namespace GodotSharpTools.Project if (string.IsNullOrEmpty(qualifiedIdentifier)) throw new ArgumentException($"{nameof(qualifiedIdentifier)} cannot be empty", nameof(qualifiedIdentifier)); - string[] identifiers = qualifiedIdentifier.Split(new[] { '.' }); + string[] identifiers = qualifiedIdentifier.Split('.'); for (int i = 0; i < identifiers.Length; i++) { @@ -66,8 +66,6 @@ namespace GodotSharpTools.Project if (identifierBuilder.Length > startIndex || @char == '_') identifierBuilder.Append(@char); break; - default: - break; } } @@ -97,14 +95,14 @@ namespace GodotSharpTools.Project } else { - if (_doubleUnderscoreKeywords.Contains(value)) + if (DoubleUnderscoreKeywords.Contains(value)) return true; } - return _keywords.Contains(value); + return Keywords.Contains(value); } - static HashSet<string> _doubleUnderscoreKeywords = new HashSet<string> + private static readonly HashSet<string> DoubleUnderscoreKeywords = new HashSet<string> { "__arglist", "__makeref", @@ -112,7 +110,7 @@ namespace GodotSharpTools.Project "__refvalue", }; - static HashSet<string> _keywords = new HashSet<string> + private static readonly HashSet<string> Keywords = new HashSet<string> { "as", "do", diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs index 647d9ac81d..36961eb45e 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs @@ -1,8 +1,9 @@ +using GodotTools.Core; using System; using DotNet.Globbing; using Microsoft.Build.Construction; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class ProjectExtensions { diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index f4ab11a222..7cf58b6755 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -1,17 +1,19 @@ +using GodotTools.Core; using System; +using System.Collections.Generic; using System.IO; using Microsoft.Build.Construction; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class ProjectGenerator { - public const string CoreApiProjectName = "GodotSharp"; - public const string EditorApiProjectName = "GodotSharpEditor"; - const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"; - const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}"; + private const string CoreApiProjectName = "GodotSharp"; + private const string EditorApiProjectName = "GodotSharpEditor"; + private const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"; + private const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}"; - public static string GenCoreApiProject(string dir, string[] compileItems) + public static string GenCoreApiProject(string dir, IEnumerable<string> compileItems) { string path = Path.Combine(dir, CoreApiProjectName + ".csproj"); @@ -24,8 +26,8 @@ namespace GodotSharpTools.Project mainGroup.SetProperty("BaseIntermediateOutputPath", "obj"); GenAssemblyInfoFile(root, dir, CoreApiProjectName, - new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]" }, - new string[] { "System.Runtime.CompilerServices" }); + new[] {"[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]"}, + new[] {"System.Runtime.CompilerServices"}); foreach (var item in compileItems) { @@ -37,7 +39,7 @@ namespace GodotSharpTools.Project return CoreApiProjectGuid; } - public static string GenEditorApiProject(string dir, string coreApiProjPath, string[] compileItems) + public static string GenEditorApiProject(string dir, string coreApiProjPath, IEnumerable<string> compileItems) { string path = Path.Combine(dir, EditorApiProjectName + ".csproj"); @@ -64,7 +66,7 @@ namespace GodotSharpTools.Project return EditorApiProjectGuid; } - public static string GenGameProject(string dir, string name, string[] compileItems) + public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems) { string path = Path.Combine(dir, name + ".csproj"); @@ -74,6 +76,8 @@ namespace GodotSharpTools.Project mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)")); mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj")); mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)")); + mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'Release' "; + mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'Release' "; var toolsGroup = root.AddPropertyGroup(); toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' "; @@ -86,11 +90,13 @@ namespace GodotSharpTools.Project toolsGroup.AddProperty("ConsolePause", "false"); var coreApiRef = root.AddItem("Reference", CoreApiProjectName); + coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll")); coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProjectName + ".dll")); coreApiRef.AddMetadata("Private", "False"); var editorApiRef = root.AddItem("Reference", EditorApiProjectName); editorApiRef.Condition = " '$(Configuration)' == 'Tools' "; + editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll")); editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProjectName + ".dll")); editorApiRef.AddMetadata("Private", "False"); @@ -108,7 +114,6 @@ namespace GodotSharpTools.Project public static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null) { - string propertiesDir = Path.Combine(dir, "Properties"); if (!Directory.Exists(propertiesDir)) Directory.CreateDirectory(propertiesDir); @@ -124,12 +129,9 @@ namespace GodotSharpTools.Project string assemblyLinesText = string.Empty; if (assemblyLines != null) - { - foreach (var assemblyLine in assemblyLines) - assemblyLinesText += string.Join("\n", assemblyLines) + "\n"; - } + assemblyLinesText += string.Join("\n", assemblyLines) + "\n"; - string content = string.Format(assemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText); + string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText); string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs"); @@ -194,8 +196,8 @@ namespace GodotSharpTools.Project } } - private const string assemblyInfoTemplate = -@"using System.Reflection;{0} + private const string AssemblyInfoTemplate = + @"using System.Reflection;{0} // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index a13f4fd6ef..22cf89695d 100644 --- a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -1,9 +1,10 @@ +using GodotTools.Core; using System.Collections.Generic; using System.IO; using DotNet.Globbing; using Microsoft.Build.Construction; -namespace GodotSharpTools.Project +namespace GodotTools.ProjectEditor { public static class ProjectUtils { @@ -53,7 +54,7 @@ namespace GodotSharpTools.Project var glob = Glob.Parse(normalizedInclude, globOptions); - // TODO Check somehow if path has no blog to avoid the following loop... + // TODO Check somehow if path has no blob to avoid the following loop... foreach (var existingFile in existingFiles) { diff --git a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs index 7115d8fc71..09333850fc 100644 --- a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs @@ -4,12 +4,12 @@ using System.Runtime.CompilerServices; // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. -[assembly: AssemblyTitle("GodotSharpTools")] +[assembly: AssemblyTitle("GodotTools.ProjectEditor")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("ignacio")] +[assembly: AssemblyCopyright("Godot Engine contributors")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/modules/mono/editor/GodotSharpTools/packages.config b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config index 2c7cb0bd4b..13915000e4 100644 --- a/modules/mono/editor/GodotSharpTools/packages.config +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <packages> <package id="DotNet.Glob" version="2.1.1" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs new file mode 100644 index 0000000000..f849356919 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -0,0 +1,172 @@ +using GodotTools.Core; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using GodotTools.BuildLogger; +using GodotTools.Internals; +using GodotTools.Utils; +using Directory = System.IO.Directory; + +namespace GodotTools.Build +{ + public static class BuildSystem + { + private static string GetMsBuildPath() + { + string msbuildPath = MsBuildFinder.FindMsBuild(); + + if (msbuildPath == null) + throw new FileNotFoundException("Cannot find the MSBuild executable."); + + return msbuildPath; + } + + private static string MonoWindowsBinDir + { + get + { + string monoWinBinDir = Path.Combine(Internal.MonoWindowsInstallRoot, "bin"); + + if (!Directory.Exists(monoWinBinDir)) + throw new FileNotFoundException("Cannot find the Windows Mono install bin directory."); + + return monoWinBinDir; + } + } + + private static Godot.EditorSettings EditorSettings => + GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + + private static bool UsingMonoMsBuildOnWindows + { + get + { + if (OS.IsWindows()) + { + return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool") + == GodotSharpBuilds.BuildTool.MsBuildMono; + } + + return false; + } + } + + private static bool PrintBuildOutput => + (bool) EditorSettings.GetSetting("mono/builds/print_build_output"); + + private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + { + var customPropertiesList = new List<string>(); + + if (customProperties != null) + customPropertiesList.AddRange(customProperties); + + string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList); + + var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs); + + bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput; + + if (!redirectOutput || Godot.OS.IsStdoutVerbose()) + Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); + + startInfo.RedirectStandardOutput = redirectOutput; + startInfo.RedirectStandardError = redirectOutput; + startInfo.UseShellExecute = false; + + if (UsingMonoMsBuildOnWindows) + { + // These environment variables are required for Mono's MSBuild to find the compilers. + // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. + string monoWinBinDir = MonoWindowsBinDir; + startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); + startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); + startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); + } + + // Needed when running from Developer Command Prompt for VS + RemovePlatformVariable(startInfo.EnvironmentVariables); + + var process = new Process {StartInfo = startInfo}; + + process.Start(); + + if (redirectOutput) + { + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + + return process; + } + + public static int Build(MonoBuildInfo monoBuildInfo) + { + return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration, + monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + } + + public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo) + { + return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration, + monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties); + } + + public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + { + using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties)) + { + process.WaitForExit(); + + return process.ExitCode; + } + } + + public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null) + { + using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties)) + { + await process.WaitForExitAsync(); + + return process.ExitCode; + } + } + + private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties) + { + string arguments = $@"""{solution}"" /v:normal /t:Rebuild ""/p:{"Configuration=" + config}"" " + + $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}"""; + + foreach (string customProperty in customProperties) + { + arguments += " /p:" + customProperty; + } + + return arguments; + } + + private static void RemovePlatformVariable(StringDictionary environmentVariables) + { + // EnvironmentVariables is case sensitive? Seriously? + + var platformEnvironmentVariables = new List<string>(); + + foreach (string env in environmentVariables.Keys) + { + if (env.ToUpper() == "PLATFORM") + platformEnvironmentVariables.Add(env); + } + + foreach (string env in platformEnvironmentVariables) + environmentVariables.Remove(env); + } + + private static bool IsDebugMsBuildRequested() + { + return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1"; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs new file mode 100644 index 0000000000..a0d14c43c9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Godot; +using GodotTools.Internals; +using Directory = System.IO.Directory; +using Environment = System.Environment; +using File = System.IO.File; +using Path = System.IO.Path; +using OS = GodotTools.Utils.OS; + +namespace GodotTools.Build +{ + public static class MsBuildFinder + { + private static string _msbuildToolsPath = string.Empty; + private static string _msbuildUnixPath = string.Empty; + private static string _xbuildUnixPath = string.Empty; + + public static string FindMsBuild() + { + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool"); + + if (OS.IsWindows()) + { + switch (buildTool) + { + case GodotSharpBuilds.BuildTool.MsBuildVs: + { + if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _msbuildToolsPath = FindMsBuildToolsPathOnWindows(); + + if (_msbuildToolsPath.Empty()) + { + throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}"); + } + } + + if (!_msbuildToolsPath.EndsWith("\\")) + _msbuildToolsPath += "\\"; + + return Path.Combine(_msbuildToolsPath, "MSBuild.exe"); + } + + case GodotSharpBuilds.BuildTool.MsBuildMono: + { + string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat"); + + if (!File.Exists(msbuildPath)) + { + throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}"); + } + + return msbuildPath; + } + + case GodotSharpBuilds.BuildTool.XBuild: + { + string xbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "xbuild.bat"); + + if (!File.Exists(xbuildPath)) + { + throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameXbuild}'. Tried with path: {xbuildPath}"); + } + + return xbuildPath; + } + + default: + throw new IndexOutOfRangeException("Invalid build tool in editor settings"); + } + } + + if (OS.IsUnix()) + { + if (buildTool == GodotSharpBuilds.BuildTool.XBuild) + { + if (_xbuildUnixPath.Empty() || !File.Exists(_xbuildUnixPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _xbuildUnixPath = FindBuildEngineOnUnix("msbuild"); + } + + if (_xbuildUnixPath.Empty()) + { + throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameXbuild}'"); + } + } + else + { + if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _msbuildUnixPath = FindBuildEngineOnUnix("msbuild"); + } + + if (_msbuildUnixPath.Empty()) + { + throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'"); + } + } + + return buildTool != GodotSharpBuilds.BuildTool.XBuild ? _msbuildUnixPath : _xbuildUnixPath; + } + + throw new PlatformNotSupportedException(); + } + + private static IEnumerable<string> MsBuildHintDirs + { + get + { + var result = new List<string>(); + + if (OS.IsOSX()) + { + result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); + result.Add("/usr/local/var/homebrew/linked/mono/bin/"); + } + + result.Add("/opt/novell/mono/bin/"); + + return result; + } + } + + private static string FindBuildEngineOnUnix(string name) + { + string ret = OS.PathWhich(name); + + if (!ret.Empty()) + return ret; + + string retFallback = OS.PathWhich($"{name}.exe"); + + if (!retFallback.Empty()) + return retFallback; + + foreach (string hintDir in MsBuildHintDirs) + { + string hintPath = Path.Combine(hintDir, name); + + if (File.Exists(hintPath)) + return hintPath; + } + + return string.Empty; + } + + private static string FindMsBuildToolsPathOnWindows() + { + if (!OS.IsWindows()) + throw new PlatformNotSupportedException(); + + // Try to find 15.0 with vswhere + + string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)"); + vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; + + var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"}; + + var outputArray = new Godot.Collections.Array<string>(); + int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs, + blocking: true, output: (Godot.Collections.Array) outputArray); + + if (exitCode == 0) + return string.Empty; + + if (outputArray.Count == 0) + return string.Empty; + + var lines = outputArray[1].Split('\n'); + + foreach (string line in lines) + { + int sepIdx = line.IndexOf(':'); + + if (sepIdx <= 0) + continue; + + string key = line.Substring(0, sepIdx); // No need to trim + + if (key != "installationPath") + continue; + + string value = line.Substring(sepIdx + 1).StripEdges(); + + if (value.Empty()) + throw new FormatException("installationPath value is empty"); + + if (!value.EndsWith("\\")) + value += "\\"; + + // Since VS2019, the directory is simply named "Current" + string msbuildDir = Path.Combine(value, "MSBuild\\Current\\Bin"); + + if (Directory.Exists(msbuildDir)) + return msbuildDir; + + // Directory name "15.0" is used in VS 2017 + return Path.Combine(value, "MSBuild\\15.0\\Bin"); + } + + return string.Empty; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs new file mode 100644 index 0000000000..0426f0ac5a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/CSharpProject.cs @@ -0,0 +1,115 @@ +using Godot; +using System; +using System.Collections.Generic; +using Godot.Collections; +using GodotTools.Internals; +using GodotTools.ProjectEditor; +using File = GodotTools.Utils.File; +using Directory = GodotTools.Utils.Directory; + +namespace GodotTools +{ + public static class CSharpProject + { + public static string GenerateGameProject(string dir, string name, IEnumerable<string> files = null) + { + try + { + return ProjectGenerator.GenGameProject(dir, name, files); + } + catch (Exception e) + { + GD.PushError(e.ToString()); + return string.Empty; + } + } + + public static void AddItem(string projectPath, string itemType, string include) + { + if (!(bool) Internal.GlobalDef("mono/project/auto_update_project", true)) + return; + + ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include); + } + + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private static ulong ConvertToTimestamp(this DateTime value) + { + TimeSpan elapsedTime = value - Epoch; + return (ulong) elapsedTime.TotalSeconds; + } + + 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((string) oldFileDict["modified_time"], 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); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs new file mode 100644 index 0000000000..433a931941 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs @@ -0,0 +1,396 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using GodotTools.Build; +using GodotTools.Internals; +using GodotTools.Utils; +using Error = Godot.Error; +using File = GodotTools.Utils.File; +using Directory = GodotTools.Utils.Directory; + +namespace GodotTools +{ + public static class GodotSharpBuilds + { + private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>(); + + public const string PropNameMsbuildMono = "MSBuild (Mono)"; + public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)"; + public const string PropNameXbuild = "xbuild (Deprecated)"; + + public const string MsBuildIssuesFileName = "msbuild_issues.csv"; + public const string MsBuildLogFileName = "msbuild_log.txt"; + + public enum BuildTool + { + MsBuildMono, + MsBuildVs, + XBuild // Deprecated + } + + private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo) + { + var issuesFile = GetIssuesFilePath(buildInfo); + + if (!File.Exists(issuesFile)) + return; + + File.Delete(issuesFile); + } + + private static string _ApiFolderName(ApiAssemblyType apiType) + { + ulong apiHash = apiType == ApiAssemblyType.Core ? + Internal.GetCoreApiHash() : + Internal.GetEditorApiHash(); + return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}"; + } + + private static void ShowBuildErrorDialog(string message) + { + GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error"); + GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab(); + } + + public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); + public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException(); + + private static string GetLogFilePath(MonoBuildInfo buildInfo) + { + return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName); + } + + private static string GetIssuesFilePath(MonoBuildInfo buildInfo) + { + return Path.Combine(Godot.ProjectSettings.LocalizePath(buildInfo.LogsDirPath), MsBuildIssuesFileName); + } + + private static void PrintVerbose(string text) + { + if (Godot.OS.IsStdoutVerbose()) + Godot.GD.Print(text); + } + + public static bool Build(MonoBuildInfo buildInfo) + { + if (BuildsInProgress.Contains(buildInfo)) + throw new InvalidOperationException("A build is already in progress"); + + BuildsInProgress.Add(buildInfo); + + try + { + MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + buildTab.OnBuildStart(); + + // Required in order to update the build tasks list + Internal.GodotMainIteration(); + + try + { + RemoveOldIssuesFile(buildInfo); + } + catch (IOException e) + { + buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + Console.Error.WriteLine(e); + } + + try + { + int exitCode = BuildSystem.Build(buildInfo); + + if (exitCode != 0) + PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); + + buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + + return exitCode == 0; + } + catch (Exception e) + { + buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + Console.Error.WriteLine(e); + return false; + } + } + finally + { + BuildsInProgress.Remove(buildInfo); + } + } + + public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo) + { + if (BuildsInProgress.Contains(buildInfo)) + throw new InvalidOperationException("A build is already in progress"); + + BuildsInProgress.Add(buildInfo); + + try + { + MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo); + + try + { + RemoveOldIssuesFile(buildInfo); + } + catch (IOException e) + { + buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + Console.Error.WriteLine(e); + } + + try + { + int exitCode = await BuildSystem.BuildAsync(buildInfo); + + if (exitCode != 0) + PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); + + buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error); + + return exitCode == 0; + } + catch (Exception e) + { + buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + Console.Error.WriteLine(e); + return false; + } + } + finally + { + BuildsInProgress.Remove(buildInfo); + } + } + + public static bool BuildApiSolution(string apiSlnDir, string config) + { + string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln"); + + string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config); + string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll"); + + string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config); + string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll"); + + if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile)) + return true; // The assemblies are in the output folder; assume the solution is already built + + var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config); + + // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, + // once we start to actively document manually maintained C# classes + apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings + + if (Build(apiBuildInfo)) + return true; + + ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution."); + return false; + } + + private static bool CopyApiAssembly(string srcDir, string dstDir, string assemblyName, ApiAssemblyType apiType) + { + // Create destination directory if needed + if (!Directory.Exists(dstDir)) + { + try + { + Directory.CreateDirectory(dstDir); + } + catch (IOException e) + { + ShowBuildErrorDialog($"Failed to create destination directory for the API assemblies. Exception message: {e.Message}"); + return false; + } + } + + string assemblyFile = assemblyName + ".dll"; + string assemblySrc = Path.Combine(srcDir, assemblyFile); + string assemblyDst = Path.Combine(dstDir, assemblyFile); + + if (!File.Exists(assemblyDst) || File.GetLastWriteTime(assemblySrc) > File.GetLastWriteTime(assemblyDst) || + Internal.MetadataIsApiAssemblyInvalidated(apiType)) + { + string xmlFile = $"{assemblyName}.xml"; + string pdbFile = $"{assemblyName}.pdb"; + + try + { + File.Copy(Path.Combine(srcDir, xmlFile), Path.Combine(dstDir, xmlFile)); + } + catch (IOException e) + { + Godot.GD.PushWarning(e.ToString()); + } + + try + { + File.Copy(Path.Combine(srcDir, pdbFile), Path.Combine(dstDir, pdbFile)); + } + catch (IOException e) + { + Godot.GD.PushWarning(e.ToString()); + } + + try + { + File.Copy(assemblySrc, assemblyDst); + } + catch (IOException e) + { + ShowBuildErrorDialog($"Failed to copy {assemblyFile}. Exception message: {e.Message}"); + return false; + } + + Internal.MetadataSetApiAssemblyInvalidated(apiType, false); + } + + return true; + } + + public static bool MakeApiAssembly(ApiAssemblyType apiType, string config) + { + string apiName = apiType == ApiAssemblyType.Core ? ApiAssemblyNames.Core : ApiAssemblyNames.Editor; + + string editorPrebuiltApiDir = Path.Combine(GodotSharpDirs.DataEditorPrebuiltApiDir, config); + string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, config); + + if (File.Exists(Path.Combine(editorPrebuiltApiDir, $"{apiName}.dll"))) + { + using (var copyProgress = new EditorProgress("mono_copy_prebuilt_api_assembly", $"Copying prebuilt {apiName} assembly...", 1)) + { + copyProgress.Step($"Copying {apiName} assembly", 0); + return CopyApiAssembly(editorPrebuiltApiDir, resAssembliesDir, apiName, apiType); + } + } + + const string apiSolutionName = ApiAssemblyNames.SolutionName; + + using (var pr = new EditorProgress($"mono_build_release_{apiSolutionName}", $"Building {apiSolutionName} solution...", 3)) + { + pr.Step($"Generating {apiSolutionName} solution", 0); + + string apiSlnDir = Path.Combine(GodotSharpDirs.MonoSolutionsDir, _ApiFolderName(ApiAssemblyType.Core)); + string apiSlnFile = Path.Combine(apiSlnDir, $"{apiSolutionName}.sln"); + + if (!Directory.Exists(apiSlnDir) || !File.Exists(apiSlnFile)) + { + var bindingsGenerator = new BindingsGenerator(); + + if (!Godot.OS.IsStdoutVerbose()) + bindingsGenerator.LogPrintEnabled = false; + + Error err = bindingsGenerator.GenerateCsApi(apiSlnDir); + if (err != Error.Ok) + { + ShowBuildErrorDialog($"Failed to generate {apiSolutionName} solution. Error: {err}"); + return false; + } + } + + pr.Step($"Building {apiSolutionName} solution", 1); + + if (!BuildApiSolution(apiSlnDir, config)) + return false; + + pr.Step($"Copying {apiName} assembly", 2); + + // Copy the built assembly to the assemblies directory + string apiAssemblyDir = Path.Combine(apiSlnDir, apiName, "bin", config); + if (!CopyApiAssembly(apiAssemblyDir, resAssembliesDir, apiName, apiType)) + return false; + } + + return true; + } + + public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines) + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return true; // No solution to build + + string apiConfig = config == "Release" ? "Release" : "Debug"; + + if (!MakeApiAssembly(ApiAssemblyType.Core, apiConfig)) + return false; + + if (!MakeApiAssembly(ApiAssemblyType.Editor, apiConfig)) + return false; + + using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) + { + pr.Step("Building project solution", 0); + + var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config); + + // Add Godot defines + string constants = OS.IsWindows() ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; + + foreach (var godotDefine in godotDefines) + constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};"; + + if (Internal.GodotIsRealTDouble()) + constants += "GODOT_REAL_T_IS_DOUBLE;"; + + constants += OS.IsWindows() ? "\"" : "\\\""; + + buildInfo.CustomProperties.Add(constants); + + if (!Build(buildInfo)) + { + ShowBuildErrorDialog("Failed to build project solution"); + return false; + } + } + + return true; + } + + public static bool EditorBuildCallback() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return true; // No solution to build + + string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); + string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); + + CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); + + if (File.Exists(editorScriptsMetadataPath)) + File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + + var godotDefines = new[] + { + Godot.OS.GetName(), + Internal.GodotIs32Bits() ? "32" : "64" + }; + + return BuildProjectBlocking("Tools", godotDefines); + } + + public static void Initialize() + { + // Build tool settings + + Internal.EditorDef("mono/builds/build_tool", OS.IsWindows() ? BuildTool.MsBuildVs : BuildTool.MsBuildMono); + + var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + + editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = Godot.Variant.Type.Int, + ["name"] = "mono/builds/build_tool", + ["hint"] = Godot.PropertyHint.Enum, + ["hint_string"] = OS.IsWindows() ? + $"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameXbuild}" : + $"{PropNameMsbuildMono},{PropNameXbuild}" + }); + + Internal.EditorDef("mono/builds/print_build_output", false); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs new file mode 100644 index 0000000000..955574d5fe --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -0,0 +1,538 @@ +using Godot; +using GodotTools.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using GodotTools.Internals; +using GodotTools.ProjectEditor; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; +using OS = GodotTools.Utils.OS; + +namespace GodotTools +{ + public class GodotSharpEditor : EditorPlugin, ISerializationListener + { + private EditorSettings editorSettings; + + private PopupMenu menuPopup; + + private AcceptDialog errorDialog; + private AcceptDialog aboutDialog; + private CheckBox aboutDialogCheckBox; + + private ToolButton bottomPanelBtn; + + private MonoDevelopInstance monoDevelopInstance; + private MonoDevelopInstance visualStudioForMacInstance; + + public MonoBottomPanel MonoBottomPanel { get; private set; } + + private bool CreateProjectSolution() + { + using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...", 2)) // TTR("Generating solution...") + { + pr.Step("Generating C# project..."); // TTR("Generating C# project...") + + string resourceDir = ProjectSettings.GlobalizePath("res://"); + + string path = resourceDir; + string name = (string) ProjectSettings.GetSetting("application/config/name"); + if (name.Empty()) + name = "UnnamedProject"; + + string guid = CSharpProject.GenerateGameProject(path, name); + + if (guid.Length > 0) + { + var solution = new DotNetSolution(name) + { + DirectoryPath = path + }; + + var projectInfo = new DotNetSolution.ProjectInfo + { + Guid = guid, + PathRelativeToSolution = name + ".csproj", + Configs = new List<string> {"Debug", "Release", "Tools"} + }; + + solution.AddNewProject(name, projectInfo); + + try + { + solution.Save(); + } + catch (IOException e) + { + ShowErrorDialog($"Failed to save solution. Exception message: {e.Message}"); // TTR + return false; + } + + string apiConfig = "Debug"; + + if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, apiConfig)) + return false; + + if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, apiConfig)) + return false; + + pr.Step("Done"); // TTR("Done") + + // Here, after all calls to progress_task_step + CallDeferred(nameof(_RemoveCreateSlnMenuOption)); + } + else + { + ShowErrorDialog("Failed to create C# project."); // TTR + } + + return true; + } + } + + private static int _makeApiSolutionsAttempts = 100; + private static bool _makeApiSolutionsRecursionGuard = false; + + private void _MakeApiSolutionsIfNeeded() + { + // I'm sick entirely of ProgressDialog + + if (Internal.IsMessageQueueFlushing() || Engine.GetMainLoop() == null) + { + if (_makeApiSolutionsAttempts == 0) // This better never happen or I swear... + throw new TimeoutException(); + + if (Engine.GetMainLoop() != null) + { + if (!Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded))) + Engine.GetMainLoop().Connect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)); + } + else + { + CallDeferred(nameof(_MakeApiSolutionsIfNeededImpl)); + } + + _makeApiSolutionsAttempts--; + return; + } + + // Recursion guard needed because signals don't play well with ProgressDialog either, but unlike + // the message queue, with signals the collateral damage should be minimal in the worst case. + if (!_makeApiSolutionsRecursionGuard) + { + _makeApiSolutionsRecursionGuard = true; + + // Oneshot signals don't play well with ProgressDialog either, so we do it this way instead + if (Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded))) + Engine.GetMainLoop().Disconnect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)); + + _MakeApiSolutionsIfNeededImpl(); + + _makeApiSolutionsRecursionGuard = false; + } + } + + private void _MakeApiSolutionsIfNeededImpl() + { + // If the project has a solution and C# project make sure the API assemblies are present and up to date + + string api_config = "Debug"; + string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, api_config); + + if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Core}.dll")) || + Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Core)) + { + if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, api_config)) + return; + } + + if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Editor}.dll")) || + Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Editor)) + { + if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, api_config)) + return; // Redundant? I don't think so! + } + } + + private void _RemoveCreateSlnMenuOption() + { + menuPopup.RemoveItem(menuPopup.GetItemIndex((int) MenuOptions.CreateSln)); + bottomPanelBtn.Show(); + } + + private void _ShowAboutDialog() + { + bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start"); + aboutDialogCheckBox.Pressed = showOnStart; + aboutDialog.PopupCenteredMinsize(); + } + + private void _ToggleAboutDialogOnStart(bool enabled) + { + bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start"); + if (showOnStart != enabled) + editorSettings.SetSetting("mono/editor/show_info_on_start", enabled); + } + + private void _MenuOptionPressed(MenuOptions id) + { + switch (id) + { + case MenuOptions.CreateSln: + CreateProjectSolution(); + break; + case MenuOptions.AboutCSharp: + _ShowAboutDialog(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option"); + } + } + + private void _BuildSolutionPressed() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + { + if (!CreateProjectSolution()) + return; // Failed to create solution + } + + Instance.MonoBottomPanel.BuildProjectPressed(); + } + + public override void _Notification(int what) + { + base._Notification(what); + + if (what == NotificationReady) + { + bool showInfoDialog = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start"); + if (showInfoDialog) + { + aboutDialog.PopupExclusive = true; + _ShowAboutDialog(); + // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on. + aboutDialog.PopupExclusive = false; + } + } + } + + public enum MenuOptions + { + CreateSln, + AboutCSharp, + } + + public enum ExternalEditor + { + None, + VisualStudio, // TODO (Windows-only) + VisualStudioForMac, // Mac-only + MonoDevelop, + VsCode + } + + public void ShowErrorDialog(string message, string title = "Error") + { + errorDialog.WindowTitle = title; + errorDialog.DialogText = message; + errorDialog.PopupCenteredMinsize(); + } + + private static string _vsCodePath = string.Empty; + + private static readonly string[] VsCodeNames = + { + "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss" + }; + + public Error OpenInExternalEditor(Script script, int line, int col) + { + var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor"); + + switch (editor) + { + case ExternalEditor.VsCode: + { + if (_vsCodePath.Empty() || !File.Exists(_vsCodePath)) + { + // Try to search it again if it wasn't found last time or if it was removed from its location + _vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty); + } + + var args = new List<string>(); + + bool osxAppBundleInstalled = false; + + if (OS.IsOSX()) + { + // The package path is '/Applications/Visual Studio Code.app' + const string vscodeBundleId = "com.microsoft.VSCode"; + + osxAppBundleInstalled = Internal.IsOsxAppBundleInstalled(vscodeBundleId); + + if (osxAppBundleInstalled) + { + args.Add("-b"); + args.Add(vscodeBundleId); + + // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is + // editing our folder. It's better to ask for a new window and let VSCode do the window management. + args.Add("-n"); + + // The open process must wait until the application finishes (which is instant in VSCode's case) + args.Add("--wait-apps"); + + args.Add("--args"); + } + } + + var resourcePath = ProjectSettings.GlobalizePath("res://"); + args.Add(resourcePath); + + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + if (line >= 0) + { + args.Add("-g"); + args.Add($"{scriptPath}:{line + 1}:{col}"); + } + else + { + args.Add(scriptPath); + } + + string command; + + if (OS.IsOSX()) + { + if (!osxAppBundleInstalled && _vsCodePath.Empty()) + { + GD.PushError("Cannot find code editor: VSCode"); + return Error.FileNotFound; + } + + command = osxAppBundleInstalled ? "/usr/bin/open" : _vsCodePath; + } + else + { + if (_vsCodePath.Empty()) + { + GD.PushError("Cannot find code editor: VSCode"); + return Error.FileNotFound; + } + + command = _vsCodePath; + } + + try + { + OS.RunProcess(command, args); + } + catch (Exception e) + { + GD.PushError($"Error when trying to run code editor: VSCode. Exception message: '{e.Message}'"); + } + + break; + } + + case ExternalEditor.VisualStudioForMac: + goto case ExternalEditor.MonoDevelop; + case ExternalEditor.MonoDevelop: + { + MonoDevelopInstance GetMonoDevelopInstance(string solutionPath) + { + if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac) + { + if (visualStudioForMacInstance == null) + visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac); + + return visualStudioForMacInstance; + } + + if (monoDevelopInstance == null) + monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop); + + return monoDevelopInstance; + } + + string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath); + + if (line >= 0) + scriptPath += $";{line + 1};{col}"; + + GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath); + + break; + } + + case ExternalEditor.None: + return Error.Unavailable; + default: + throw new ArgumentOutOfRangeException(); + } + + return Error.Ok; + } + + public bool OverridesExternalEditor() + { + return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None; + } + + public override bool Build() + { + return GodotSharpBuilds.EditorBuildCallback(); + } + + public override void EnablePlugin() + { + base.EnablePlugin(); + + if (Instance != null) + throw new InvalidOperationException(); + Instance = this; + + var editorInterface = GetEditorInterface(); + var editorBaseControl = editorInterface.GetBaseControl(); + + editorSettings = editorInterface.GetEditorSettings(); + + errorDialog = new AcceptDialog(); + editorBaseControl.AddChild(errorDialog); + + MonoBottomPanel = new MonoBottomPanel(); + + bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono"); // TTR("Mono") + + AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); + + menuPopup = new PopupMenu(); + menuPopup.Hide(); + menuPopup.SetAsToplevel(true); + + AddToolSubmenuItem("Mono", menuPopup); + + // TODO: Remove or edit this info dialog once Mono support is no longer in alpha + { + menuPopup.AddItem("About C# support", (int) MenuOptions.AboutCSharp); // TTR("About C# support") + aboutDialog = new AcceptDialog(); + editorBaseControl.AddChild(aboutDialog); + aboutDialog.WindowTitle = "Important: C# support is not feature-complete"; + + // We don't use DialogText as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox + // we'll add. Instead we add containers and a new autowrapped Label inside. + + // Main VBoxContainer (icon + label on top, checkbox at bottom) + var aboutVBox = new VBoxContainer(); + aboutDialog.AddChild(aboutVBox); + + // HBoxContainer for icon + label + var aboutHBox = new HBoxContainer(); + aboutVBox.AddChild(aboutHBox); + + var aboutIcon = new TextureRect(); + aboutIcon.Texture = aboutIcon.GetIcon("NodeWarning", "EditorIcons"); + aboutHBox.AddChild(aboutIcon); + + var aboutLabel = new Label(); + aboutHBox.AddChild(aboutLabel); + aboutLabel.RectMinSize = new Vector2(600, 150) * Internal.EditorScale; + aboutLabel.SizeFlagsVertical = (int) Control.SizeFlags.ExpandFill; + aboutLabel.Autowrap = true; + aboutLabel.Text = + "C# support in Godot Engine is in late alpha stage and, while already usable, " + + "it is not meant for use in production.\n\n" + + "Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " + + "Bugs and usability issues will be addressed gradually over future releases, " + + "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + + "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" + + " https://github.com/godotengine/godot/issues\n\n" + + "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!"; + + Internal.EditorDef("mono/editor/show_info_on_start", true); + + // CheckBox in main container + aboutDialogCheckBox = new CheckBox {Text = "Show this warning when starting the editor"}; + aboutDialogCheckBox.Connect("toggled", this, nameof(_ToggleAboutDialogOnStart)); + aboutVBox.AddChild(aboutDialogCheckBox); + } + + if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) + { + // Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized. + CallDeferred(nameof(_MakeApiSolutionsIfNeeded)); + } + else + { + bottomPanelBtn.Hide(); + menuPopup.AddItem("Create C# solution", (int) MenuOptions.CreateSln); // TTR("Create C# solution") + } + + menuPopup.Connect("id_pressed", this, nameof(_MenuOptionPressed)); + + var buildButton = new ToolButton + { + Text = "Build", + HintTooltip = "Build solution", + FocusMode = Control.FocusModeEnum.None + }; + buildButton.Connect("pressed", this, nameof(_BuildSolutionPressed)); + AddControlToContainer(CustomControlContainer.Toolbar, buildButton); + + // External editor settings + Internal.EditorDef("mono/editor/external_editor", ExternalEditor.None); + + string settingsHintStr = "Disabled"; + + if (OS.IsWindows()) + { + settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + } + else if (OS.IsOSX()) + { + settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" + + $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + } + else if (OS.IsUnix()) + { + settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" + + $",Visual Studio Code:{(int) ExternalEditor.VsCode}"; + } + + editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary + { + ["type"] = Variant.Type.Int, + ["name"] = "mono/editor/external_editor", + ["hint"] = PropertyHint.Enum, + ["hint_string"] = settingsHintStr + }); + + // Export plugin + AddExportPlugin(new GodotSharpExport()); + + GodotSharpBuilds.Initialize(); + } + + public void OnBeforeSerialize() + { + } + + public void OnAfterDeserialize() + { + Instance = this; + } + + // Singleton + + public static GodotSharpEditor Instance { get; private set; } + + private GodotSharpEditor() + { + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs new file mode 100644 index 0000000000..b80fe1fab7 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpExport.cs @@ -0,0 +1,197 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using GodotTools.Core; +using GodotTools.Internals; +using Directory = GodotTools.Utils.Directory; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; + +namespace GodotTools +{ + public class GodotSharpExport : EditorExportPlugin + { + private void AddFile(string srcPath, string dstPath, bool remap = false) + { + AddFile(dstPath, File.ReadAllBytes(srcPath), remap); + } + + public override void _ExportFile(string path, string type, string[] features) + { + base._ExportFile(path, type, features); + + if (type != Internal.CSharpLanguageType) + return; + + if (Path.GetExtension(path) != $".{Internal.CSharpLanguageExtension}") + throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path)); + + // TODO What if the source file is not part of the game's C# project + + bool includeScriptsContent = (bool) ProjectSettings.GetSetting("mono/export/include_scripts_content"); + + if (!includeScriptsContent) + { + // We don't want to include the source code on exported games + AddFile(path, new byte[] { }, remap: false); + Skip(); + } + } + + public override void _ExportBegin(string[] features, bool isDebug, string path, int flags) + { + base._ExportBegin(features, isDebug, path, flags); + + try + { + _ExportBeginImpl(features, isDebug, path, flags); + } + catch (Exception e) + { + GD.PushError($"Failed to export project. Exception message: {e.Message}"); + Console.Error.WriteLine(e); + } + } + + 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 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 bool PlatformHasTemplateDir(IEnumerable<string> featureSet) + { + // OSX export templates are contained in a zip, so we place + // our custom template inside it and let Godot do the rest. + return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f)); + } + + private static string DataDirName + { + get + { + var appName = (string) ProjectSettings.GetSetting("application/config/name"); + string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false); + return $"data_{appNameSafe}"; + } + } + + private static void GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath, + string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies) => + internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath, + string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj new file mode 100644 index 0000000000..a0ff8a0df1 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>GodotTools</RootNamespace> + <AssemblyName>GodotTools</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> + <GodotApiConfiguration>Debug</GodotApiConfiguration> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>portable</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="GodotSharp"> + <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath> + </Reference> + <Reference Include="GodotSharpEditor"> + <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharpEditor.dll</HintPath> + </Reference> + </ItemGroup> + <ItemGroup> + <Compile Include="Build\MsBuildFinder.cs" /> + <Compile Include="Internals\BindingsGenerator.cs" /> + <Compile Include="Internals\EditorProgress.cs" /> + <Compile Include="Internals\GodotSharpDirs.cs" /> + <Compile Include="Internals\Internal.cs" /> + <Compile Include="Internals\ScriptClassParser.cs" /> + <Compile Include="MonoDevelopInstance.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Build\BuildSystem.cs" /> + <Compile Include="Utils\Directory.cs" /> + <Compile Include="Utils\File.cs" /> + <Compile Include="Utils\OS.cs" /> + <Compile Include="GodotSharpEditor.cs" /> + <Compile Include="GodotSharpBuilds.cs" /> + <Compile Include="HotReloadAssemblyWatcher.cs" /> + <Compile Include="MonoBuildInfo.cs" /> + <Compile Include="MonoBuildTab.cs" /> + <Compile Include="MonoBottomPanel.cs" /> + <Compile Include="GodotSharpExport.cs" /> + <Compile Include="CSharpProject.cs" /> + <Compile Include="Utils\CollectionExtensions.cs" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj"> + <Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project> + <Name>GodotTools.BuildLogger</Name> + </ProjectReference> + <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj"> + <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project> + <Name>GodotTools.ProjectEditor</Name> + </ProjectReference> + <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj"> + <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project> + <Name>GodotTools.Core</Name> + </ProjectReference> + </ItemGroup> + <ItemGroup> + <Folder Include="Editor" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs new file mode 100644 index 0000000000..aa52079cf4 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -0,0 +1,47 @@ +using Godot; +using GodotTools.Internals; + +namespace GodotTools +{ + public class HotReloadAssemblyWatcher : Node + { + private Timer watchTimer; + + public override void _Notification(int what) + { + if (what == MainLoop.NotificationWmFocusIn) + { + RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + } + + private void TimerTimeout() + { + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + public void RestartTimer() + { + watchTimer.Stop(); + watchTimer.Start(); + } + + public override void _Ready() + { + base._Ready(); + + watchTimer = new Timer + { + OneShot = false, + WaitTime = (float) Internal.EditorDef("mono/assembly_watch_interval_sec", 0.5) + }; + watchTimer.Connect("timeout", this, nameof(TimerTimeout)); + AddChild(watchTimer); + watchTimer.Start(); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs new file mode 100644 index 0000000000..1daa5e138e --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/BindingsGenerator.cs @@ -0,0 +1,87 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace GodotTools.Internals +{ + public class BindingsGenerator : IDisposable + { + class BindingsGeneratorSafeHandle : SafeHandle + { + public BindingsGeneratorSafeHandle(IntPtr handle) : base(IntPtr.Zero, true) + { + this.handle = handle; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + internal_Dtor(handle); + return true; + } + } + + private BindingsGeneratorSafeHandle safeHandle; + private bool disposed = false; + + public bool LogPrintEnabled + { + get => internal_LogPrintEnabled(GetPtr()); + set => internal_SetLogPrintEnabled(GetPtr(), value); + } + + public static uint Version => internal_Version(); + public static uint CsGlueVersion => internal_CsGlueVersion(); + + public Godot.Error GenerateCsApi(string outputDir) => internal_GenerateCsApi(GetPtr(), outputDir); + + internal IntPtr GetPtr() + { + if (disposed) + throw new ObjectDisposedException(GetType().FullName); + + return safeHandle.DangerousGetHandle(); + } + + public void Dispose() + { + if (disposed) + return; + + if (safeHandle != null && !safeHandle.IsInvalid) + { + safeHandle.Dispose(); + safeHandle = null; + } + + disposed = true; + } + + public BindingsGenerator() + { + safeHandle = new BindingsGeneratorSafeHandle(internal_Ctor()); + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern IntPtr internal_Ctor(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_Dtor(IntPtr handle); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_LogPrintEnabled(IntPtr handle); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_SetLogPrintEnabled(IntPtr handle, bool enabled); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Godot.Error internal_GenerateCsApi(IntPtr handle, string outputDir); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern uint internal_Version(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern uint internal_CsGlueVersion(); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs new file mode 100644 index 0000000000..70ba7c733a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.CompilerServices; +using Godot; + +namespace GodotTools.Internals +{ + public class EditorProgress : IDisposable + { + public string Task { get; } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_Create(string task, string label, int amount, bool canCancel); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_Dispose(string task); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_Step(string task, string state, int step, bool forceRefresh); + + public EditorProgress(string task, string label, int amount, bool canCancel = false) + { + Task = task; + internal_Create(task, label, amount, canCancel); + } + + ~EditorProgress() + { + // Should never rely on the GC to dispose EditorProgress. + // It should be disposed immediately when the task finishes. + GD.PushError("EditorProgress disposed by the Garbage Collector"); + Dispose(); + } + + public void Dispose() + { + internal_Dispose(Task); + GC.SuppressFinalize(this); + } + + public void Step(string state, int step = -1, bool forceRefresh = true) + { + internal_Step(Task, state, step, forceRefresh); + } + + public bool TryStep(string state, int step = -1, bool forceRefresh = true) + { + return internal_Step(Task, state, step, forceRefresh); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs new file mode 100644 index 0000000000..ddf3b829b5 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -0,0 +1,91 @@ +using System.Runtime.CompilerServices; + +namespace GodotTools.Internals +{ + public static class GodotSharpDirs + { + public static string ResDataDir => internal_ResDataDir(); + public static string ResMetadataDir => internal_ResMetadataDir(); + public static string ResAssembliesBaseDir => internal_ResAssembliesBaseDir(); + public static string ResAssembliesDir => internal_ResAssembliesDir(); + public static string ResConfigDir => internal_ResConfigDir(); + public static string ResTempDir => internal_ResTempDir(); + public static string ResTempAssembliesBaseDir => internal_ResTempAssembliesBaseDir(); + public static string ResTempAssembliesDir => internal_ResTempAssembliesDir(); + + public static string MonoUserDir => internal_MonoUserDir(); + public static string MonoLogsDir => internal_MonoLogsDir(); + + #region Tools-only + public static string MonoSolutionsDir => internal_MonoSolutionsDir(); + public static string BuildLogsDirs => internal_BuildLogsDirs(); + + public static string ProjectSlnPath => internal_ProjectSlnPath(); + public static string ProjectCsProjPath => internal_ProjectCsProjPath(); + + public static string DataEditorToolsDir => internal_DataEditorToolsDir(); + public static string DataEditorPrebuiltApiDir => internal_DataEditorPrebuiltApiDir(); + #endregion + + public static string DataMonoEtcDir => internal_DataMonoEtcDir(); + public static string DataMonoLibDir => internal_DataMonoLibDir(); + + #region Windows-only + public static string DataMonoBinDir => internal_DataMonoBinDir(); + #endregion + + + #region Internal + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResDataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResMetadataDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResAssembliesDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResConfigDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResTempDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResTempAssembliesBaseDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ResTempAssembliesDir(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoUserDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoLogsDir(); + + #region Tools-only + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoSolutionsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_BuildLogsDirs(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ProjectSlnPath(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_ProjectCsProjPath(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataEditorToolsDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataEditorPrebuiltApiDir(); + #endregion + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataMonoEtcDir(); + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataMonoLibDir(); + + #region Windows-only + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_DataMonoBinDir(); + #endregion + + #endregion + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs new file mode 100644 index 0000000000..5c7ce832cd --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.CompilerServices; +using Godot; +using Godot.Collections; + +namespace GodotTools.Internals +{ + public static class Internal + { + public const string CSharpLanguageType = "CSharpScript"; + public const string CSharpLanguageExtension = "cs"; + + public static float EditorScale => internal_EditorScale(); + + public static object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) => + internal_GlobalDef(setting, defaultValue, restartIfChanged); + + public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) => + internal_EditorDef(setting, defaultValue, restartIfChanged); + + public static string FullTemplatesDir => + internal_FullTemplatesDir(); + + public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path); + + public static bool IsOsxAppBundleInstalled(string bundleId) => internal_IsOsxAppBundleInstalled(bundleId); + + public static bool MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType) => + internal_MetadataIsApiAssemblyInvalidated(apiType); + + public static void MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated) => + internal_MetadataSetApiAssemblyInvalidated(apiType, invalidated); + + public static bool IsMessageQueueFlushing() => internal_IsMessageQueueFlushing(); + + public static bool GodotIs32Bits() => internal_GodotIs32Bits(); + + public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble(); + + public static void GodotMainIteration() => internal_GodotMainIteration(); + + public static ulong GetCoreApiHash() => internal_GetCoreApiHash(); + + public static ulong GetEditorApiHash() => internal_GetEditorApiHash(); + + public static bool IsAssembliesReloadingNeeded() => internal_IsAssembliesReloadingNeeded(); + + public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload); + + public static void ScriptEditorDebuggerReloadScripts() => internal_ScriptEditorDebuggerReloadScripts(); + + public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) => + internal_ScriptEditorEdit(resource, line, col, grabFocus); + + public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen(); + + public static Dictionary<string, object> GetScriptsMetadataOrNothing() => + internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>)); + + public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); + + // Internal Calls + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern float internal_EditorScale(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern object internal_GlobalDef(string setting, object defaultValue, bool restartIfChanged); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_FullTemplatesDir(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_SimplifyGodotPath(this string path); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_IsOsxAppBundleInstalled(string bundleId); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_IsMessageQueueFlushing(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_GodotIs32Bits(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_GodotIsRealTDouble(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_GodotMainIteration(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern ulong internal_GetCoreApiHash(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern ulong internal_GetEditorApiHash(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_IsAssembliesReloadingNeeded(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_ReloadAssemblies(bool softReload); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_ScriptEditorDebuggerReloadScripts(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern void internal_EditorNodeShowScriptScreen(); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType); + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_MonoWindowsInstallRoot(); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs new file mode 100644 index 0000000000..2497d276a9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Godot; +using Godot.Collections; + +namespace GodotTools.Internals +{ + public static class ScriptClassParser + { + public class ClassDecl + { + public string Name { get; } + public string Namespace { get; } + public bool Nested { get; } + public int BaseCount { get; } + + public ClassDecl(string name, string @namespace, bool nested, int baseCount) + { + Name = name; + Namespace = @namespace; + Nested = nested; + BaseCount = baseCount; + } + } + + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes); + + public static void ParseFileOrThrow(string filePath, out IEnumerable<ClassDecl> classes) + { + var classesArray = new Array<Dictionary>(); + var error = internal_ParseFile(filePath, classesArray); + if (error != Error.Ok) + throw new Exception($"Failed to determine namespace and class for script: {filePath}. Parse error: {error}"); + + var classesList = new List<ClassDecl>(); + + foreach (var classDeclDict in classesArray) + { + classesList.Add(new ClassDecl( + (string) classDeclDict["name"], + (string) classDeclDict["namespace"], + (bool) classDeclDict["nested"], + (int) classDeclDict["base_count"] + )); + } + + classes = classesList; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs new file mode 100644 index 0000000000..300cf7fcb9 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/MonoBottomPanel.cs @@ -0,0 +1,342 @@ +using Godot; +using System; +using System.IO; +using Godot.Collections; +using GodotTools.Internals; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; + +namespace GodotTools +{ + public class MonoBottomPanel : VBoxContainer + { + private EditorInterface editorInterface; + + private TabContainer panelTabs; + + private VBoxContainer panelBuildsTab; + + private ItemList buildTabsList; + private TabContainer buildTabs; + + private ToolButton warningsBtn; + private ToolButton errorsBtn; + private Button viewLogBtn; + + private void _UpdateBuildTabsList() + { + buildTabsList.Clear(); + + int currentTab = buildTabs.CurrentTab; + + bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount(); + + for (int i = 0; i < buildTabs.GetChildCount(); i++) + { + var tab = (MonoBuildTab) buildTabs.GetChild(i); + + if (tab == null) + continue; + + string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution); + itemName += " [" + tab.BuildInfo.Configuration + "]"; + + buildTabsList.AddItem(itemName, tab.IconTexture); + + string itemTooltip = "Solution: " + tab.BuildInfo.Solution; + itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration; + itemTooltip += "\nStatus: "; + + if (tab.BuildExited) + itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored"; + else + itemTooltip += "Running"; + + if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error) + itemTooltip += $"\nErrors: {tab.ErrorCount}"; + + itemTooltip += $"\nWarnings: {tab.WarningCount}"; + + buildTabsList.SetItemTooltip(i, itemTooltip); + + if (noCurrentTab || currentTab == i) + { + buildTabsList.Select(i); + _BuildTabsItemSelected(i); + } + } + } + + public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo) + { + foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren())) + { + if (buildTab.BuildInfo.Equals(buildInfo)) + return buildTab; + } + + var newBuildTab = new MonoBuildTab(buildInfo); + AddBuildTab(newBuildTab); + + return newBuildTab; + } + + private void _BuildTabsItemSelected(int idx) + { + if (idx < 0 || idx >= buildTabs.GetTabCount()) + throw new IndexOutOfRangeException(); + + buildTabs.CurrentTab = idx; + if (!buildTabs.Visible) + buildTabs.Visible = true; + + warningsBtn.Visible = true; + errorsBtn.Visible = true; + viewLogBtn.Visible = true; + } + + private void _BuildTabsNothingSelected() + { + if (buildTabs.GetTabCount() != 0) + { + // just in case + buildTabs.Visible = false; + + // This callback is called when clicking on the empty space of the list. + // ItemList won't deselect the items automatically, so we must do it ourselves. + buildTabsList.UnselectAll(); + } + + warningsBtn.Visible = false; + errorsBtn.Visible = false; + viewLogBtn.Visible = false; + } + + private void _WarningsToggled(bool pressed) + { + int currentTab = buildTabs.CurrentTab; + + if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) + throw new InvalidOperationException("No tab selected"); + + var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + buildTab.WarningsVisible = pressed; + buildTab.UpdateIssuesList(); + } + + private void _ErrorsToggled(bool pressed) + { + int currentTab = buildTabs.CurrentTab; + + if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) + throw new InvalidOperationException("No tab selected"); + + var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab); + buildTab.ErrorsVisible = pressed; + buildTab.UpdateIssuesList(); + } + + 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"); + + CSharpProject.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 = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines); + + if (!buildSuccess) + return; + + // Notify running game for hot-reload + Internal.ScriptEditorDebuggerReloadScripts(); + + // Hot-reload in the editor + GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + private void _ViewLogPressed() + { + if (!buildTabsList.IsAnythingSelected()) + return; + + var selectedItems = buildTabsList.GetSelectedItems(); + + if (selectedItems.Length != 1) + throw new InvalidOperationException($"Expected 1 selected item, got {selectedItems.Length}"); + + int selectedItem = selectedItems[0]; + + var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem); + + OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName)); + } + + public override void _Notification(int what) + { + base._Notification(what); + + if (what == EditorSettings.NotificationEditorSettingsChanged) + { + var editorBaseControl = editorInterface.GetBaseControl(); + panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); + } + } + + public void AddBuildTab(MonoBuildTab buildTab) + { + buildTabs.AddChild(buildTab); + RaiseBuildTab(buildTab); + } + + public void RaiseBuildTab(MonoBuildTab buildTab) + { + if (buildTab.GetParent() != buildTabs) + throw new InvalidOperationException("Build tab is not in the tabs list"); + + buildTabs.MoveChild(buildTab, 0); + _UpdateBuildTabsList(); + } + + public void ShowBuildTab() + { + for (int i = 0; i < panelTabs.GetTabCount(); i++) + { + if (panelTabs.GetTabControl(i) == panelBuildsTab) + { + panelTabs.CurrentTab = i; + GodotSharpEditor.Instance.MakeBottomPanelItemVisible(this); + return; + } + } + + GD.PushError("Builds tab not found"); + } + + public override void _Ready() + { + base._Ready(); + + editorInterface = GodotSharpEditor.Instance.GetEditorInterface(); + + var editorBaseControl = editorInterface.GetBaseControl(); + + SizeFlagsVertical = (int) SizeFlags.ExpandFill; + SetAnchorsAndMarginsPreset(LayoutPreset.Wide); + + panelTabs = new TabContainer + { + TabAlign = TabContainer.TabAlignEnum.Left, + RectMinSize = new Vector2(0, 228) * Internal.EditorScale, + SizeFlagsVertical = (int) SizeFlags.ExpandFill + }; + panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles")); + panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles")); + AddChild(panelTabs); + + { + // Builds tab + panelBuildsTab = new VBoxContainer + { + Name = "Builds", // TTR + SizeFlagsHorizontal = (int) SizeFlags.ExpandFill + }; + panelTabs.AddChild(panelBuildsTab); + + var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill}; + panelBuildsTab.AddChild(toolBarHBox); + + var buildProjectBtn = new Button + { + Text = "Build Project", // TTR + FocusMode = FocusModeEnum.None + }; + buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed)); + toolBarHBox.AddChild(buildProjectBtn); + + toolBarHBox.AddSpacer(begin: false); + + warningsBtn = new ToolButton + { + Text = "Warnings", // TTR + ToggleMode = true, + Pressed = true, + Visible = false, + FocusMode = FocusModeEnum.None + }; + warningsBtn.Connect("toggled", this, nameof(_WarningsToggled)); + toolBarHBox.AddChild(warningsBtn); + + errorsBtn = new ToolButton + { + Text = "Errors", // TTR + ToggleMode = true, + Pressed = true, + Visible = false, + FocusMode = FocusModeEnum.None + }; + errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled)); + toolBarHBox.AddChild(errorsBtn); + + toolBarHBox.AddSpacer(begin: false); + + viewLogBtn = new Button + { + Text = "View log", // TTR + FocusMode = FocusModeEnum.None, + Visible = false + }; + viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed)); + toolBarHBox.AddChild(viewLogBtn); + + var hsc = new HSplitContainer + { + SizeFlagsHorizontal = (int) SizeFlags.ExpandFill, + SizeFlagsVertical = (int) SizeFlags.ExpandFill + }; + panelBuildsTab.AddChild(hsc); + + buildTabsList = new ItemList {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill}; + buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected)); + buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected)); + hsc.AddChild(buildTabsList); + + buildTabs = new TabContainer + { + TabAlign = TabContainer.TabAlignEnum.Left, + SizeFlagsHorizontal = (int) SizeFlags.ExpandFill, + TabsVisible = false + }; + hsc.AddChild(buildTabs); + } + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs new file mode 100644 index 0000000000..858e852392 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/MonoBuildInfo.cs @@ -0,0 +1,47 @@ +using System; +using Godot; +using Godot.Collections; +using GodotTools.Internals; +using Path = System.IO.Path; + +namespace GodotTools +{ + [Serializable] + public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization + { + public string Solution { get; } + public string Configuration { get; } + public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization + + public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); + + public override bool Equals(object obj) + { + if (obj is MonoBuildInfo other) + return other.Solution == Solution && other.Configuration == Configuration; + + return false; + } + + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = hash * 29 + Solution.GetHashCode(); + hash = hash * 29 + Configuration.GetHashCode(); + return hash; + } + } + + private MonoBuildInfo() + { + } + + public MonoBuildInfo(string solution, string configuration) + { + Solution = solution; + Configuration = configuration; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs new file mode 100644 index 0000000000..75fdacc0da --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/MonoBuildTab.cs @@ -0,0 +1,260 @@ +using Godot; +using System; +using Godot.Collections; +using GodotTools.Internals; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; + +namespace GodotTools +{ + public class MonoBuildTab : VBoxContainer + { + public enum BuildResults + { + Error, + Success + } + + [Serializable] + private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization + { + public bool Warning { get; set; } + public string File { get; set; } + public int Line { get; set; } + public int Column { get; set; } + public string Code { get; set; } + public string Message { get; set; } + public string ProjectFile { get; set; } + } + + private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization + private ItemList issuesList; + + public bool BuildExited { get; private set; } = false; + + public BuildResults? BuildResult { get; private set; } = null; + + public int ErrorCount { get; private set; } = 0; + + public int WarningCount { get; private set; } = 0; + + public bool ErrorsVisible { get; set; } = true; + public bool WarningsVisible { get; set; } = true; + + public Texture IconTexture + { + get + { + if (!BuildExited) + return GetIcon("Stop", "EditorIcons"); + + if (BuildResult == BuildResults.Error) + return GetIcon("StatusError", "EditorIcons"); + + return GetIcon("StatusSuccess", "EditorIcons"); + } + } + + public MonoBuildInfo BuildInfo { get; private set; } + + private void _LoadIssuesFromFile(string csvFile) + { + using (var file = new Godot.File()) + { + Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read); + + if (openError != Error.Ok) + return; + + while (!file.EofReached()) + { + string[] csvColumns = file.GetCsvLine(); + + if (csvColumns.Length == 1 && csvColumns[0].Empty()) + return; + + if (csvColumns.Length != 7) + { + GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); + continue; + } + + var issue = new BuildIssue + { + Warning = csvColumns[0] == "warning", + File = csvColumns[1], + Line = int.Parse(csvColumns[2]), + Column = int.Parse(csvColumns[3]), + Code = csvColumns[4], + Message = csvColumns[5], + ProjectFile = csvColumns[6] + }; + + if (issue.Warning) + WarningCount += 1; + else + ErrorCount += 1; + + issues.Add(issue); + } + } + } + + 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(); + } + } + + public void UpdateIssuesList() + { + issuesList.Clear(); + + using (var warningIcon = GetIcon("Warning", "EditorIcons")) + using (var errorIcon = GetIcon("Error", "EditorIcons")) + { + for (int i = 0; i < issues.Count; i++) + { + BuildIssue issue = issues[i]; + + if (!(issue.Warning ? WarningsVisible : ErrorsVisible)) + continue; + + string tooltip = string.Empty; + tooltip += $"Message: {issue.Message}"; + + if (!issue.Code.Empty()) + tooltip += $"\nCode: {issue.Code}"; + + tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}"; + + string text = string.Empty; + + if (!issue.File.Empty()) + { + text += $"{issue.File}({issue.Line},{issue.Column}): "; + + tooltip += $"\nFile: {issue.File}"; + tooltip += $"\nLine: {issue.Line}"; + tooltip += $"\nColumn: {issue.Column}"; + } + + if (!issue.ProjectFile.Empty()) + tooltip += $"\nProject: {issue.ProjectFile}"; + + text += issue.Message; + + int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal); + string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx); + issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon); + + int index = issuesList.GetItemCount() - 1; + issuesList.SetItemTooltip(index, tooltip); + issuesList.SetItemMetadata(index, i); + } + } + } + + public void OnBuildStart() + { + BuildExited = false; + + issues.Clear(); + WarningCount = 0; + ErrorCount = 0; + UpdateIssuesList(); + + GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + } + + public void OnBuildExit(BuildResults result) + { + BuildExited = true; + BuildResult = result; + + _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName)); + UpdateIssuesList(); + + GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + } + + public void OnBuildExecFailed(string cause) + { + BuildExited = true; + BuildResult = BuildResults.Error; + + issuesList.Clear(); + + var issue = new BuildIssue {Message = cause, Warning = false}; + + ErrorCount += 1; + issues.Add(issue); + + UpdateIssuesList(); + + GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this); + } + + public void RestartBuild() + { + if (!BuildExited) + throw new InvalidOperationException("Build already started"); + + GodotSharpBuilds.RestartBuild(this); + } + + public void StopBuild() + { + if (!BuildExited) + throw new InvalidOperationException("Build is not in progress"); + + GodotSharpBuilds.StopBuild(this); + } + + public override void _Ready() + { + base._Ready(); + + issuesList = new ItemList {SizeFlagsVertical = (int) SizeFlags.ExpandFill}; + issuesList.Connect("item_activated", this, nameof(_IssueActivated)); + AddChild(issuesList); + } + + private MonoBuildTab() + { + } + + public MonoBuildTab(MonoBuildInfo buildInfo) + { + BuildInfo = buildInfo; + } + } +} diff --git a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs index fba4a8f65c..0c8d86e799 100644 --- a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/MonoDevelopInstance.cs @@ -1,11 +1,11 @@ +using GodotTools.Core; using System; using System.IO; using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; +using GodotTools.Internals; -namespace GodotSharpTools.Editor +namespace GodotTools { public class MonoDevelopInstance { @@ -15,24 +15,24 @@ namespace GodotSharpTools.Editor VisualStudioForMac = 1 } - readonly string solutionFile; - readonly EditorId editorId; + private readonly string solutionFile; + private readonly EditorId editorId; - Process process; + private Process process; - public void Execute(string[] files) + public void Execute(params string[] files) { bool newWindow = process == null || process.HasExited; - List<string> args = new List<string>(); + var args = new List<string>(); string command; if (Utils.OS.IsOSX()) { - string bundleId = codeEditorBundleIds[editorId]; + string bundleId = CodeEditorBundleIds[editorId]; - if (IsApplicationBundleInstalled(bundleId)) + if (Internal.IsOsxAppBundleInstalled(bundleId)) { command = "open"; @@ -47,12 +47,12 @@ namespace GodotSharpTools.Editor } else { - command = codeEditorPaths[editorId]; + command = CodeEditorPaths[editorId]; } } else { - command = codeEditorPaths[editorId]; + command = CodeEditorPaths[editorId]; } args.Add("--ipc-tcp"); @@ -72,7 +72,7 @@ namespace GodotSharpTools.Editor if (newWindow) { - process = Process.Start(new ProcessStartInfo() + process = Process.Start(new ProcessStartInfo { FileName = command, Arguments = string.Join(" ", args), @@ -81,12 +81,12 @@ namespace GodotSharpTools.Editor } else { - Process.Start(new ProcessStartInfo() + Process.Start(new ProcessStartInfo { FileName = command, Arguments = string.Join(" ", args), UseShellExecute = false - }); + })?.Dispose(); } } @@ -99,45 +99,42 @@ namespace GodotSharpTools.Editor this.editorId = editorId; } - [MethodImpl(MethodImplOptions.InternalCall)] - private extern static bool IsApplicationBundleInstalled(string bundleId); - - static readonly IReadOnlyDictionary<EditorId, string> codeEditorPaths; - static readonly IReadOnlyDictionary<EditorId, string> codeEditorBundleIds; + private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorPaths; + private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorBundleIds; static MonoDevelopInstance() { if (Utils.OS.IsOSX()) { - codeEditorPaths = new Dictionary<EditorId, string> + CodeEditorPaths = new Dictionary<EditorId, string> { // Rely on PATH - { EditorId.MonoDevelop, "monodevelop" }, - { EditorId.VisualStudioForMac, "VisualStudio" } + {EditorId.MonoDevelop, "monodevelop"}, + {EditorId.VisualStudioForMac, "VisualStudio"} }; - codeEditorBundleIds = new Dictionary<EditorId, string> + CodeEditorBundleIds = new Dictionary<EditorId, string> { // TODO EditorId.MonoDevelop - { EditorId.VisualStudioForMac, "com.microsoft.visual-studio" } + {EditorId.VisualStudioForMac, "com.microsoft.visual-studio"} }; } else if (Utils.OS.IsWindows()) { - codeEditorPaths = new Dictionary<EditorId, string> + CodeEditorPaths = new Dictionary<EditorId, string> { // XamarinStudio is no longer a thing, and the latest version is quite old // MonoDevelop is available from source only on Windows. The recommendation // is to use Visual Studio instead. Since there are no official builds, we // will rely on custom MonoDevelop builds being added to PATH. - { EditorId.MonoDevelop, "MonoDevelop.exe" } + {EditorId.MonoDevelop, "MonoDevelop.exe"} }; } else if (Utils.OS.IsUnix()) { - codeEditorPaths = new Dictionary<EditorId, string> + CodeEditorPaths = new Dictionary<EditorId, string> { // Rely on PATH - { EditorId.MonoDevelop, "monodevelop" } + {EditorId.MonoDevelop, "monodevelop"} }; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f5fe85c722 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("GodotTools")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Godot Engine contributors")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs new file mode 100644 index 0000000000..3ae6c10bbf --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace GodotTools.Utils +{ + public static class CollectionExtensions + { + public static T SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T> predicate, T orElse = null) + where T : class + { + foreach (T elem in enumerable) + { + if (predicate(elem) != null) + return elem; + } + + return orElse; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs new file mode 100644 index 0000000000..c67d48b92a --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/Directory.cs @@ -0,0 +1,40 @@ +using System.IO; +using Godot; + +namespace GodotTools.Utils +{ + public static class Directory + { + private static string GlobalizePath(this string path) + { + return ProjectSettings.GlobalizePath(path); + } + + public static bool Exists(string path) + { + return System.IO.Directory.Exists(path.GlobalizePath()); + } + + /// Create directory recursively + public static DirectoryInfo CreateDirectory(string path) + { + return System.IO.Directory.CreateDirectory(path.GlobalizePath()); + } + + public static void Delete(string path, bool recursive) + { + System.IO.Directory.Delete(path.GlobalizePath(), recursive); + } + + + public static string[] GetDirectories(string path, string searchPattern, SearchOption searchOption) + { + return System.IO.Directory.GetDirectories(path.GlobalizePath(), searchPattern, searchOption); + } + + public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption) + { + return System.IO.Directory.GetFiles(path.GlobalizePath(), searchPattern, searchOption); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs new file mode 100644 index 0000000000..e1e2188edb --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/File.cs @@ -0,0 +1,43 @@ +using System; +using Godot; + +namespace GodotTools.Utils +{ + public static class File + { + private static string GlobalizePath(this string path) + { + return ProjectSettings.GlobalizePath(path); + } + + public static void WriteAllText(string path, string contents) + { + System.IO.File.WriteAllText(path.GlobalizePath(), contents); + } + + public static bool Exists(string path) + { + return System.IO.File.Exists(path.GlobalizePath()); + } + + public static DateTime GetLastWriteTime(string path) + { + return System.IO.File.GetLastWriteTime(path.GlobalizePath()); + } + + public static void Delete(string path) + { + System.IO.File.Delete(path.GlobalizePath()); + } + + public static void Copy(string sourceFileName, string destFileName) + { + System.IO.File.Copy(sourceFileName.GlobalizePath(), destFileName.GlobalizePath(), overwrite: true); + } + + public static byte[] ReadAllBytes(string path) + { + return System.IO.File.ReadAllBytes(path.GlobalizePath()); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs new file mode 100644 index 0000000000..e48b1115db --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace GodotTools.Utils +{ + public static class OS + { + [MethodImpl(MethodImplOptions.InternalCall)] + extern static string GetPlatformName(); + + const string HaikuName = "Haiku"; + const string OSXName = "OSX"; + const string ServerName = "Server"; + const string UWPName = "UWP"; + const string WindowsName = "Windows"; + const string X11Name = "X11"; + + public static bool IsHaiku() + { + return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsOSX() + { + return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsServer() + { + return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsUWP() + { + return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsWindows() + { + return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + public static bool IsX11() + { + return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + } + + private static bool? _isUnixCache; + private static readonly string[] UnixPlatforms = {HaikuName, OSXName, ServerName, X11Name}; + + public static bool IsUnix() + { + if (_isUnixCache.HasValue) + return _isUnixCache.Value; + + string osName = GetPlatformName(); + _isUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase)); + return _isUnixCache.Value; + } + + public static char PathSep => IsWindows() ? ';' : ':'; + + public static string PathWhich(string name) + { + string[] windowsExts = IsWindows() ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) : null; + string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); + + var searchDirs = new List<string>(); + + if (pathDirs != null) + searchDirs.AddRange(pathDirs); + + searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list + + foreach (var dir in searchDirs) + { + string path = Path.Combine(dir, name); + + if (IsWindows() && windowsExts != null) + { + foreach (var extension in windowsExts) + { + string pathWithExtension = path + extension; + + if (File.Exists(pathWithExtension)) + return pathWithExtension; + } + } + else + { + if (File.Exists(path)) + return path; + } + } + + return null; + } + + public static void RunProcess(string command, IEnumerable<string> arguments) + { + string CmdLineArgsToString(IEnumerable<string> args) + { + return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); + } + + ProcessStartInfo startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }; + + using (Process process = Process.Start(startInfo)) + { + if (process == null) + throw new Exception("No process was started"); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + } + } +} diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 5e0ce3554a..ebf317f08b 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -861,26 +861,22 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append("\n#pragma warning restore CS1591\n"); } -Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution) { - - String proj_dir = p_solution_dir.plus_file(CORE_API_ASSEMBLY_NAME); +Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_items) { DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(proj_dir)) { - Error err = da->make_dir_recursive(proj_dir); + if (!DirAccess::exists(p_proj_dir)) { + Error err = da->make_dir_recursive(p_proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(proj_dir); + da->change_dir(p_proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(proj_dir, "Core"); - String obj_type_dir = path_join(proj_dir, "ObjectType"); - - Vector<String> compile_items; + String core_dir = path_join(p_proj_dir, "Core"); + String obj_type_dir = path_join(p_proj_dir, "ObjectType"); // Generate source file for global scope constants and enums { @@ -891,7 +887,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, if (save_err != OK) return save_err; - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) { @@ -909,7 +905,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, if (err != OK) return err; - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } // Generate sources from compressed files @@ -939,7 +935,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, file->store_buffer(data.ptr(), data.size()); file->close(); - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } StringBuilder cs_icalls_content; @@ -981,43 +977,27 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, if (err != OK) return err; - compile_items.push_back(internal_methods_file); - - String guid = CSharpProject::generate_core_api_project(proj_dir, compile_items); - - DotNetSolution::ProjectInfo proj_info; - proj_info.guid = guid; - proj_info.relpath = String(CORE_API_ASSEMBLY_NAME).plus_file(CORE_API_ASSEMBLY_NAME ".csproj"); - proj_info.configs.push_back("Debug"); - proj_info.configs.push_back("Release"); - - r_solution.add_new_project(CORE_API_ASSEMBLY_NAME, proj_info); - - _log("The solution and C# project for the Core API was generated successfully\n"); + r_compile_items.push_back(internal_methods_file); return OK; } -Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution) { - - String proj_dir = p_solution_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); +Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items) { DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(proj_dir)) { - Error err = da->make_dir_recursive(proj_dir); + if (!DirAccess::exists(p_proj_dir)) { + Error err = da->make_dir_recursive(p_proj_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - da->change_dir(proj_dir); + da->change_dir(p_proj_dir); da->make_dir("Core"); da->make_dir("ObjectType"); - String core_dir = path_join(proj_dir, "Core"); - String obj_type_dir = path_join(proj_dir, "ObjectType"); - - Vector<String> compile_items; + String core_dir = path_join(p_proj_dir, "Core"); + String obj_type_dir = path_join(p_proj_dir, "ObjectType"); for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) { const TypeInterface &itype = E.get(); @@ -1034,7 +1014,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir if (err != OK) return err; - compile_items.push_back(output_file); + r_compile_items.push_back(output_file); } StringBuilder cs_icalls_content; @@ -1077,58 +1057,56 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir if (err != OK) return err; - compile_items.push_back(internal_methods_file); - - String guid = CSharpProject::generate_editor_api_project(proj_dir, "../" CORE_API_ASSEMBLY_NAME "/" CORE_API_ASSEMBLY_NAME ".csproj", compile_items); - - DotNetSolution::ProjectInfo proj_info; - proj_info.guid = guid; - proj_info.relpath = String(EDITOR_API_ASSEMBLY_NAME).plus_file(EDITOR_API_ASSEMBLY_NAME ".csproj"); - proj_info.configs.push_back("Debug"); - proj_info.configs.push_back("Release"); - - r_solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, proj_info); - - _log("The solution and C# project for the Editor API was generated successfully\n"); + r_compile_items.push_back(internal_methods_file); return OK; } Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { + String output_dir = DirAccess::get_full_path(p_output_dir, DirAccess::ACCESS_FILESYSTEM); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - if (!DirAccess::exists(p_output_dir)) { - Error err = da->make_dir_recursive(p_output_dir); + if (!DirAccess::exists(output_dir)) { + Error err = da->make_dir_recursive(output_dir); ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); } - DotNetSolution solution(API_SOLUTION_NAME); + Error proj_err; - if (!solution.set_path(p_output_dir)) - return ERR_FILE_NOT_FOUND; + // Generate GodotSharp source files - Error proj_err; + String core_proj_dir = output_dir.plus_file(CORE_API_ASSEMBLY_NAME); + Vector<String> core_compile_items; - proj_err = generate_cs_core_project(p_output_dir, solution); + proj_err = generate_cs_core_project(core_proj_dir, core_compile_items); if (proj_err != OK) { ERR_PRINT("Generation of the Core API C# project failed"); return proj_err; } - proj_err = generate_cs_editor_project(p_output_dir, solution); + // Generate GodotSharpEditor source files + + String editor_proj_dir = output_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); + Vector<String> editor_compile_items; + + proj_err = generate_cs_editor_project(editor_proj_dir, editor_compile_items); if (proj_err != OK) { ERR_PRINT("Generation of the Editor API C# project failed"); return proj_err; } - Error sln_error = solution.save(); - if (sln_error != OK) { - ERR_PRINT("Failed to save API solution"); - return sln_error; + // Generate solution + + if (!CSharpProject::generate_api_solution(output_dir, + core_proj_dir, core_compile_items, editor_proj_dir, editor_compile_items)) { + return ERR_CANT_CREATE; } + _log("The solution for the Godot API was generated successfully\n"); + return OK; } @@ -1311,8 +1289,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(MEMBER_BEGIN "private static Godot.Object singleton;\n"); output.append(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3 "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5 - "singleton = Engine.GetSingleton(" BINDINGS_NATIVE_NAME_FIELD ");\n" INDENT4 - "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); + "singleton = Engine.GetSingleton(typeof("); + output.append(itype.proxy_name); + output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); output.append(itype.name); @@ -3018,36 +2997,49 @@ void BindingsGenerator::_initialize() { void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) { const int NUM_OPTIONS = 2; - String mono_glue_option = "--generate-mono-glue"; - String cs_api_option = "--generate-cs-api"; + String generate_all_glue_option = "--generate-mono-glue"; + String generate_cs_glue_option = "--generate-mono-cs-glue"; + String generate_cpp_glue_option = "--generate-mono-cpp-glue"; - String mono_glue_path; - String cs_api_path; + String glue_dir_path; + String cs_dir_path; + String cpp_dir_path; int options_left = NUM_OPTIONS; const List<String>::Element *elem = p_cmdline_args.front(); while (elem && options_left) { - if (elem->get() == mono_glue_option) { + if (elem->get() == generate_all_glue_option) { const List<String>::Element *path_elem = elem->next(); if (path_elem) { - mono_glue_path = path_elem->get(); + glue_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(mono_glue_option + ": No output directory specified"); + ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to {GODOT_ROOT}/modules/mono/glue)"); } --options_left; - } else if (elem->get() == cs_api_option) { + } else if (elem->get() == generate_cs_glue_option) { const List<String>::Element *path_elem = elem->next(); if (path_elem) { - cs_api_path = path_elem->get(); + cs_dir_path = path_elem->get(); elem = elem->next(); } else { - ERR_PRINTS(cs_api_option + ": No output directory specified"); + ERR_PRINTS(generate_cs_glue_option + ": No output directory specified"); + } + + --options_left; + } else if (elem->get() == generate_cpp_glue_option) { + const List<String>::Element *path_elem = elem->next(); + + if (path_elem) { + cpp_dir_path = path_elem->get(); + elem = elem->next(); + } else { + ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified"); } --options_left; @@ -3056,18 +3048,26 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) elem = elem->next(); } - if (mono_glue_path.length() || cs_api_path.length()) { + if (glue_dir_path.length() || cs_dir_path.length() || cpp_dir_path.length()) { BindingsGenerator bindings_generator; bindings_generator.set_log_print_enabled(true); - if (mono_glue_path.length()) { - if (bindings_generator.generate_glue(mono_glue_path) != OK) - ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue"); + if (glue_dir_path.length()) { + if (bindings_generator.generate_glue(glue_dir_path) != OK) + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue"); + + if (bindings_generator.generate_cs_api(glue_dir_path.plus_file("Managed/Generated")) != OK) + ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API"); + } + + if (cs_dir_path.length()) { + if (bindings_generator.generate_cs_api(cs_dir_path) != OK) + ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API"); } - if (cs_api_path.length()) { - if (bindings_generator.generate_cs_api(cs_api_path) != OK) - ERR_PRINTS(cs_api_option + ": Failed to generate the C# API"); + if (cpp_dir_path.length()) { + if (bindings_generator.generate_glue(cpp_dir_path) != OK) + ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue"); } // Exit once done diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index ffc73a7e3e..8be51a6c55 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -33,7 +33,6 @@ #include "core/class_db.h" #include "core/string_builder.h" -#include "dotnet_solution.h" #include "editor/doc/doc_data.h" #include "editor/editor_help.h" @@ -614,12 +613,13 @@ class BindingsGenerator { void _initialize(); public: - Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution); - Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution); + Error generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_files); + Error generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items); Error generate_cs_api(const String &p_output_dir); Error generate_glue(const String &p_output_dir); - void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; } + _FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; } + _FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; } static uint32_t get_version(); diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp index fe79286556..d88b08c646 100644 --- a/modules/mono/editor/csharp_project.cpp +++ b/modules/mono/editor/csharp_project.cpp @@ -44,66 +44,54 @@ namespace CSharpProject { -String generate_core_api_project(const String &p_dir, const Vector<String> &p_files) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); - - Variant dir = p_dir; - Variant compile_items = p_files; - const Variant *args[2] = { &dir, &compile_items }; +bool generate_api_solution_impl(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items, + const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items, + GDMonoAssembly *p_tools_project_editor_assembly) { + + GDMonoClass *klass = p_tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ApiSolutionGenerator"); + + Variant solution_dir = p_solution_dir; + Variant core_proj_dir = p_core_proj_dir; + Variant core_compile_items = p_core_compile_items; + Variant editor_proj_dir = p_editor_proj_dir; + Variant editor_compile_items = p_editor_compile_items; + const Variant *args[5] = { &solution_dir, &core_proj_dir, &core_compile_items, &editor_proj_dir, &editor_compile_items }; MonoException *exc = NULL; - MonoObject *ret = klass->get_method("GenCoreApiProject", 2)->invoke(NULL, args, &exc); + klass->get_method("GenerateApiSolution", 5)->invoke(NULL, args, &exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(String()); + ERR_FAIL_V(false); } - return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); + return true; } -String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); - - Variant dir = p_dir; - Variant core_proj_path = p_core_proj_path; - Variant compile_items = p_files; - const Variant *args[3] = { &dir, &core_proj_path, &compile_items }; - MonoException *exc = NULL; - MonoObject *ret = klass->get_method("GenEditorApiProject", 3)->invoke(NULL, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(String()); - } - - return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); -} +bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items, + const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items) { -String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files) { + if (GDMono::get_singleton()->get_tools_project_editor_assembly()) { + return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items, + p_editor_proj_dir, p_editor_compile_items, + GDMono::get_singleton()->get_tools_project_editor_assembly()); + } else { + MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain"); + CRASH_COND(temp_domain == NULL); + _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain); - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + _GDMONO_SCOPE_DOMAIN_(temp_domain); - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); + GDMonoAssembly *tools_project_editor_assembly = NULL; - Variant dir = p_dir; - Variant name = p_name; - Variant compile_items = p_files; - const Variant *args[3] = { &dir, &name, &compile_items }; - MonoException *exc = NULL; - MonoObject *ret = klass->get_method("GenGameProject", 3)->invoke(NULL, args, &exc); + if (!GDMono::get_singleton()->load_assembly("GodotTools.ProjectEditor", &tools_project_editor_assembly)) { + ERR_EXPLAIN("Failed to load assembly: 'GodotTools.ProjectEditor'"); + ERR_FAIL_V(false); + } - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(String()); + return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items, + p_editor_proj_dir, p_editor_compile_items, + tools_project_editor_assembly); } - - return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String(); } void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) { @@ -111,9 +99,9 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str if (!GLOBAL_DEF("mono/project/auto_update_project", true)) return; - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly(); - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); + GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils"); Variant project_path = p_project_path; Variant item_type = p_item_type; @@ -128,126 +116,4 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str } } -Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - if (FileAccess::exists(p_output_path)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error rm_err = da->remove(p_output_path); - - ERR_EXPLAIN("Failed to remove old scripts metadata file"); - ERR_FAIL_COND_V(rm_err != OK, rm_err); - } - - GDMonoClass *project_utils = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); - - void *args[2] = { - GDMonoMarshal::mono_string_from_godot(p_project_path), - GDMonoMarshal::mono_string_from_godot("Compile") - }; - - MonoException *exc = NULL; - MonoArray *ret = (MonoArray *)project_utils->get_method("GetIncludeFiles", 2)->invoke_raw(NULL, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL_V(FAILED); - } - - PoolStringArray project_files = GDMonoMarshal::mono_array_to_PoolStringArray(ret); - PoolStringArray::Read r = project_files.read(); - - Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing(); - Dictionary new_dict; - - for (int i = 0; i < project_files.size(); i++) { - const String &project_file = ("res://" + r[i]).simplify_path(); - - uint64_t modified_time = FileAccess::get_modified_time(project_file); - - const Variant *old_file_var = old_dict.getptr(project_file); - if (old_file_var) { - Dictionary old_file_dict = old_file_var->operator Dictionary(); - - if (old_file_dict["modified_time"].operator uint64_t() == modified_time) { - // No changes so no need to parse again - new_dict[project_file] = old_file_dict; - continue; - } - } - - ScriptClassParser scp; - Error err = scp.parse_file(project_file); - if (err != OK) { - ERR_PRINTS("Parse error: " + scp.get_error()); - ERR_EXPLAIN("Failed to determine namespace and class for script: " + project_file); - ERR_FAIL_V(err); - } - - Vector<ScriptClassParser::ClassDecl> classes = scp.get_classes(); - - bool found = false; - Dictionary class_dict; - - String search_name = project_file.get_file().get_basename(); - - for (int j = 0; j < classes.size(); j++) { - const ScriptClassParser::ClassDecl &class_decl = classes[j]; - - if (class_decl.base.size() == 0) - continue; // Does not inherit nor implement anything, so it can't be a script class - - String class_cmp; - - if (class_decl.nested) { - class_cmp = class_decl.name.get_slice(".", class_decl.name.get_slice_count(".") - 1); - } else { - class_cmp = class_decl.name; - } - - if (class_cmp != search_name) - continue; - - class_dict["namespace"] = class_decl.namespace_; - class_dict["class_name"] = class_decl.name; - class_dict["nested"] = class_decl.nested; - - found = true; - break; - } - - if (found) { - Dictionary file_dict; - file_dict["modified_time"] = modified_time; - file_dict["class"] = class_dict; - new_dict[project_file] = file_dict; - } - } - - if (new_dict.size()) { - String json = JSON::print(new_dict, "", false); - - String base_dir = p_output_path.get_base_dir(); - - if (!DirAccess::exists(base_dir)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - - Error err = da->make_dir_recursive(base_dir); - ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); - } - - Error ferr; - FileAccess *f = FileAccess::open(p_output_path, FileAccess::WRITE, &ferr); - ERR_EXPLAIN("Cannot open file for writing: " + p_output_path); - ERR_FAIL_COND_V(ferr != OK, ferr); - f->store_string(json); - f->flush(); - f->close(); - memdelete(f); - } - - return OK; -} - } // namespace CSharpProject diff --git a/modules/mono/editor/csharp_project.h b/modules/mono/editor/csharp_project.h index b08c9090c7..b42762cea2 100644 --- a/modules/mono/editor/csharp_project.h +++ b/modules/mono/editor/csharp_project.h @@ -35,14 +35,11 @@ namespace CSharpProject { -String generate_core_api_project(const String &p_dir, const Vector<String> &p_files = Vector<String>()); -String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files = Vector<String>()); -String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>()); +bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items, + const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items); void add_item(const String &p_project_path, const String &p_item_type, const String &p_include); -Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path); - } // namespace CSharpProject #endif // CSHARP_PROJECT_H diff --git a/modules/mono/editor/dotnet_solution.cpp b/modules/mono/editor/dotnet_solution.cpp deleted file mode 100644 index 324752cafc..0000000000 --- a/modules/mono/editor/dotnet_solution.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/*************************************************************************/ -/* dotnet_solution.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "dotnet_solution.h" - -#include "core/os/dir_access.h" -#include "core/os/file_access.h" - -#include "../utils/path_utils.h" -#include "../utils/string_utils.h" -#include "csharp_project.h" - -#define SOLUTION_TEMPLATE \ - "Microsoft Visual Studio Solution File, Format Version 12.00\n" \ - "# Visual Studio 2012\n" \ - "%0\n" \ - "Global\n" \ - "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n" \ - "%1\n" \ - "\tEndGlobalSection\n" \ - "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n" \ - "%2\n" \ - "\tEndGlobalSection\n" \ - "EndGlobal\n" - -#define PROJECT_DECLARATION "Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"%0\", \"%1\", \"{%2}\"\nEndProject" - -#define SOLUTION_PLATFORMS_CONFIG "\t%0|Any CPU = %0|Any CPU" - -#define PROJECT_PLATFORMS_CONFIG \ - "\t\t{%0}.%1|Any CPU.ActiveCfg = %1|Any CPU\n" \ - "\t\t{%0}.%1|Any CPU.Build.0 = %1|Any CPU" - -void DotNetSolution::add_new_project(const String &p_name, const ProjectInfo &p_project_info) { - projects[p_name] = p_project_info; -} - -bool DotNetSolution::has_project(const String &p_name) const { - return projects.find(p_name) != NULL; -} - -const DotNetSolution::ProjectInfo &DotNetSolution::get_project_info(const String &p_name) const { - return projects[p_name]; -} - -bool DotNetSolution::remove_project(const String &p_name) { - return projects.erase(p_name); -} - -Error DotNetSolution::save() { - bool dir_exists = DirAccess::exists(path); - ERR_EXPLAIN("The directory does not exist."); - ERR_FAIL_COND_V(!dir_exists, ERR_FILE_NOT_FOUND); - - String projs_decl; - String sln_platform_cfg; - String proj_platform_cfg; - - for (Map<String, ProjectInfo>::Element *E = projects.front(); E; E = E->next()) { - const String &name = E->key(); - const ProjectInfo &proj_info = E->value(); - - bool is_front = E == projects.front(); - - if (!is_front) - projs_decl += "\n"; - - projs_decl += sformat(PROJECT_DECLARATION, name, proj_info.relpath.replace("/", "\\"), proj_info.guid); - - for (int i = 0; i < proj_info.configs.size(); i++) { - const String &config = proj_info.configs[i]; - - if (i != 0 || !is_front) { - sln_platform_cfg += "\n"; - proj_platform_cfg += "\n"; - } - - sln_platform_cfg += sformat(SOLUTION_PLATFORMS_CONFIG, config); - proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, proj_info.guid, config); - } - } - - String content = sformat(SOLUTION_TEMPLATE, projs_decl, sln_platform_cfg, proj_platform_cfg); - - FileAccess *file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE); - ERR_FAIL_NULL_V(file, ERR_FILE_CANT_WRITE); - file->store_string(content); - file->close(); - memdelete(file); - - return OK; -} - -bool DotNetSolution::set_path(const String &p_existing_path) { - if (p_existing_path.is_abs_path()) { - path = p_existing_path; - } else { - String abspath; - if (!rel_path_to_abs(p_existing_path, abspath)) - return false; - path = abspath; - } - - return true; -} - -String DotNetSolution::get_path() { - return path; -} - -DotNetSolution::DotNetSolution(const String &p_name) { - name = p_name; -} diff --git a/modules/mono/editor/dotnet_solution.h b/modules/mono/editor/dotnet_solution.h deleted file mode 100644 index 18933364fa..0000000000 --- a/modules/mono/editor/dotnet_solution.h +++ /dev/null @@ -1,63 +0,0 @@ -/*************************************************************************/ -/* dotnet_solution.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef NET_SOLUTION_H -#define NET_SOLUTION_H - -#include "core/map.h" -#include "core/ustring.h" - -struct DotNetSolution { - String name; - - struct ProjectInfo { - String guid; - String relpath; // Must be relative to the solution directory - Vector<String> configs; - }; - - void add_new_project(const String &p_name, const ProjectInfo &p_project_info); - bool has_project(const String &p_name) const; - const ProjectInfo &get_project_info(const String &p_name) const; - bool remove_project(const String &p_name); - - Error save(); - - bool set_path(const String &p_existing_path); - String get_path(); - - DotNetSolution(const String &p_name); - -private: - String path; - Map<String, ProjectInfo> projects; -}; - -#endif // NET_SOLUTION_H diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp new file mode 100644 index 0000000000..a3b5b450ef --- /dev/null +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -0,0 +1,429 @@ +/*************************************************************************/ +/* editor_internal_calls.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_internal_calls.h" + +#include "core/message_queue.h" +#include "core/os/os.h" +#include "core/version.h" +#include "editor/editor_node.h" +#include "editor/plugins/script_editor_plugin.h" +#include "editor/script_editor_debugger.h" +#include "main/main.h" + +#include "../csharp_script.h" +#include "../glue/cs_glue_version.gen.h" +#include "../godotsharp_dirs.h" +#include "../mono_gd/gd_mono_marshal.h" +#include "../utils/osx_utils.h" +#include "bindings_generator.h" +#include "godotsharp_export.h" +#include "script_class_parser.h" + +MonoString *godot_icall_GodotSharpDirs_ResDataDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResMetadataDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_metadata_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResAssembliesBaseDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_base_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResAssembliesDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResConfigDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_config_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResTempDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_base_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_MonoUserDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_user_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_MonoLogsDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_logs_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() { +#ifdef TOOLS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir()); +#else + return NULL; +#endif +} + +MonoString *godot_icall_GodotSharpDirs_DataMonoEtcDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_etc_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_DataMonoLibDir() { + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_lib_dir()); +} + +MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() { +#ifdef WINDOWS_ENABLED + return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir()); +#else + return NULL; +#endif +} + +void godot_icall_EditorProgress_Create(MonoString *p_task, MonoString *p_label, int32_t p_amount, MonoBoolean p_can_cancel) { + String task = GDMonoMarshal::mono_string_to_godot(p_task); + String label = GDMonoMarshal::mono_string_to_godot(p_label); + EditorNode::progress_add_task(task, label, p_amount, (bool)p_can_cancel); +} + +void godot_icall_EditorProgress_Dispose(MonoString *p_task) { + String task = GDMonoMarshal::mono_string_to_godot(p_task); + EditorNode::progress_end_task(task); +} + +MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_state, int32_t p_step, MonoBoolean p_force_refresh) { + String task = GDMonoMarshal::mono_string_to_godot(p_task); + String state = GDMonoMarshal::mono_string_to_godot(p_state); + return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh); +} + +BindingsGenerator *godot_icall_BindingsGenerator_Ctor() { + return memnew(BindingsGenerator); +} + +void godot_icall_BindingsGenerator_Dtor(BindingsGenerator *p_handle) { + memdelete(p_handle); +} + +MonoBoolean godot_icall_BindingsGenerator_LogPrintEnabled(BindingsGenerator *p_handle) { + return p_handle->is_log_print_enabled(); +} + +void godot_icall_BindingsGenerator_SetLogPrintEnabled(BindingsGenerator p_handle, MonoBoolean p_enabled) { + p_handle.set_log_print_enabled(p_enabled); +} + +int32_t godot_icall_BindingsGenerator_GenerateCsApi(BindingsGenerator *p_handle, MonoString *p_output_dir) { + String output_dir = GDMonoMarshal::mono_string_to_godot(p_output_dir); + return p_handle->generate_cs_api(output_dir); +} + +uint32_t godot_icall_BindingsGenerator_Version() { + return BindingsGenerator::get_version(); +} + +uint32_t godot_icall_BindingsGenerator_CsGlueVersion() { + return CS_GLUE_VERSION; +} + +int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes) { + String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath); + + ScriptClassParser scp; + Error err = scp.parse_file(filepath); + if (err == OK) { + Array classes = GDMonoMarshal::mono_object_to_variant(p_classes); + const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes(); + + for (int i = 0; i < class_decls.size(); i++) { + const ScriptClassParser::ClassDecl &classDecl = class_decls[i]; + + Dictionary classDeclDict; + classDeclDict["name"] = classDecl.name; + classDeclDict["namespace"] = classDecl.namespace_; + classDeclDict["nested"] = classDecl.nested; + classDeclDict["base_count"] = classDecl.base.size(); + classes.push_back(classDeclDict); + } + } + return err; +} + +uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString *p_project_dll_name, MonoString *p_project_dll_src_path, + MonoString *p_build_config, MonoString *p_custom_lib_dir, MonoObject *r_dependencies) { + String project_dll_name = GDMonoMarshal::mono_string_to_godot(p_project_dll_name); + String project_dll_src_path = GDMonoMarshal::mono_string_to_godot(p_project_dll_src_path); + String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config); + String custom_lib_dir = GDMonoMarshal::mono_string_to_godot(p_custom_lib_dir); + Dictionary dependencies = GDMonoMarshal::mono_object_to_variant(r_dependencies); + + return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies); +} + +float godot_icall_Internal_EditorScale() { + return EDSCALE; +} + +MonoObject *godot_icall_Internal_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { + String setting = GDMonoMarshal::mono_string_to_godot(p_setting); + Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); + Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); + return GDMonoMarshal::variant_to_mono_object(result); +} + +MonoObject *godot_icall_Internal_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { + String setting = GDMonoMarshal::mono_string_to_godot(p_setting); + Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); + Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); + return GDMonoMarshal::variant_to_mono_object(result); +} + +MonoString *godot_icall_Internal_FullTemplatesDir() { + String full_templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); + return GDMonoMarshal::mono_string_from_godot(full_templates_dir); +} + +MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) { + String path = GDMonoMarshal::mono_string_to_godot(p_path); + return GDMonoMarshal::mono_string_from_godot(path.simplify_path()); +} + +MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id) { +#ifdef OSX_ENABLED + String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id); + return (MonoBoolean)osx_is_app_bundle_installed; +#else + (void)p_bundle_id; // UNUSED + return (MonoBoolean) false; +#endif +} + +MonoBoolean godot_icall_Internal_MetadataIsApiAssemblyInvalidated(int32_t p_api_type) { + return GDMono::get_singleton()->metadata_is_api_assembly_invalidated((APIAssembly::Type)p_api_type); +} + +void godot_icall_Internal_MetadataSetApiAssemblyInvalidated(int32_t p_api_type, MonoBoolean p_invalidated) { + GDMono::get_singleton()->metadata_set_api_assembly_invalidated((APIAssembly::Type)p_api_type, (bool)p_invalidated); +} + +MonoBoolean godot_icall_Internal_IsMessageQueueFlushing() { + return (MonoBoolean)MessageQueue::get_singleton()->is_flushing(); +} + +MonoBoolean godot_icall_Internal_GodotIs32Bits() { + return sizeof(void *) == 4; +} + +MonoBoolean godot_icall_Internal_GodotIsRealTDouble() { +#ifdef REAL_T_IS_DOUBLE + return (MonoBoolean) true; +#else + return (MonoBoolean) false; +#endif +} + +void godot_icall_Internal_GodotMainIteration() { + Main::iteration(); +} + +uint64_t godot_icall_Internal_GetCoreApiHash() { + return ClassDB::get_api_hash(ClassDB::API_CORE); +} + +uint64_t godot_icall_Internal_GetEditorApiHash() { + return ClassDB::get_api_hash(ClassDB::API_EDITOR); +} + +MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() { +#ifdef GD_MONO_HOT_RELOAD + return (MonoBoolean)CSharpLanguage::get_singleton()->is_assembly_reloading_needed(); +#else + return (MonoBoolean) false; +#endif +} + +void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) { +#ifdef GD_MONO_HOT_RELOAD + _GodotSharp::get_singleton()->call_deferred("_reload_assemblies", (bool)p_soft_reload); +#endif +} + +void godot_icall_Internal_ScriptEditorDebuggerReloadScripts() { + ScriptEditor::get_singleton()->get_debugger()->reload_scripts(); +} + +MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) { + Ref<Resource> resource = GDMonoMarshal::mono_object_to_variant(p_resource); + return (MonoBoolean)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus); +} + +void godot_icall_Internal_EditorNodeShowScriptScreen() { + EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); +} + +MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) { + Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing(); + + MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype); + + uint32_t type_encoding = mono_type_get_type(dict_type); + MonoClass *type_class_raw = mono_class_from_mono_type(dict_type); + GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw); + + return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class)); +} + +MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { +#ifdef WINDOWS_ENABLED + String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir; + return GDMonoMarshal::mono_string_from_godot(install_root_dir); +#else + return NULL; +#endif +} + +MonoString *godot_icall_Utils_OS_GetPlatformName() { + String os_name = OS::get_singleton()->get_name(); + return GDMonoMarshal::mono_string_from_godot(os_name); +} + +void register_editor_internal_calls() { + + // GodotSharpDirs + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", (void *)godot_icall_GodotSharpDirs_ResDataDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", (void *)godot_icall_GodotSharpDirs_ResMetadataDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesBaseDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", (void *)godot_icall_GodotSharpDirs_ResConfigDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", (void *)godot_icall_GodotSharpDirs_ResTempDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", (void *)godot_icall_GodotSharpDirs_MonoUserDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", (void *)godot_icall_GodotSharpDirs_MonoLogsDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", (void *)godot_icall_GodotSharpDirs_MonoSolutionsDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", (void *)godot_icall_GodotSharpDirs_BuildLogsDirs); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", (void *)godot_icall_GodotSharpDirs_ProjectSlnPath); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", (void *)godot_icall_GodotSharpDirs_ProjectCsProjPath); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", (void *)godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", (void *)godot_icall_GodotSharpDirs_DataMonoEtcDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", (void *)godot_icall_GodotSharpDirs_DataMonoLibDir); + mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", (void *)godot_icall_GodotSharpDirs_DataMonoBinDir); + + // EditorProgress + mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", (void *)godot_icall_EditorProgress_Create); + mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", (void *)godot_icall_EditorProgress_Dispose); + mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", (void *)godot_icall_EditorProgress_Step); + + // BiningsGenerator + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Ctor", (void *)godot_icall_BindingsGenerator_Ctor); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Dtor", (void *)godot_icall_BindingsGenerator_Dtor); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_LogPrintEnabled", (void *)godot_icall_BindingsGenerator_LogPrintEnabled); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_SetLogPrintEnabled", (void *)godot_icall_BindingsGenerator_SetLogPrintEnabled); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_GenerateCsApi", (void *)godot_icall_BindingsGenerator_GenerateCsApi); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Version", (void *)godot_icall_BindingsGenerator_Version); + mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_CsGlueVersion", (void *)godot_icall_BindingsGenerator_CsGlueVersion); + + // ScriptClassParser + mono_add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", (void *)godot_icall_ScriptClassParser_ParseFile); + + // GodotSharpExport + mono_add_internal_call("GodotTools.GodotSharpExport::internal_GetExportedAssemblyDependencies", (void *)godot_icall_GodotSharpExport_GetExportedAssemblyDependencies); + + // Internals + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorScale", (void *)godot_icall_Internal_EditorScale); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GlobalDef", (void *)godot_icall_Internal_GlobalDef); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorDef", (void *)godot_icall_Internal_EditorDef); + mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir); + mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath); + mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled); + mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataIsApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataIsApiAssemblyInvalidated); + mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataSetApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataSetApiAssemblyInvalidated); + mono_add_internal_call("GodotTools.Internals.Internal::internal_IsMessageQueueFlushing", (void *)godot_icall_Internal_IsMessageQueueFlushing); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", (void *)godot_icall_Internal_GetCoreApiHash); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash); + mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebuggerReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebuggerReloadScripts); + mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit); + mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen); + mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing); + mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot); + + // Utils.OS + mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName); +} diff --git a/modules/mono/editor/mono_build_info.h b/modules/mono/editor/editor_internal_calls.h index b0ae2ed52e..1682da66e5 100644 --- a/modules/mono/editor/mono_build_info.h +++ b/modules/mono/editor/editor_internal_calls.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* mono_build_info.h */ +/* editor_internal_calls.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,28 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef MONO_BUILD_INFO_H -#define MONO_BUILD_INFO_H +#ifndef EDITOR_INTERNAL_CALL_H +#define EDITOR_INTERNAL_CALL_H -#include "core/ustring.h" -#include "core/vector.h" +void register_editor_internal_calls(); -struct MonoBuildInfo { - - struct Hasher { - static uint32_t hash(const MonoBuildInfo &p_key); - }; - - String solution; - String configuration; - Vector<String> custom_props; - - bool operator==(const MonoBuildInfo &p_b) const; - - String get_log_dirpath(); - - MonoBuildInfo(); - MonoBuildInfo(const String &p_solution, const String &p_config); -}; - -#endif // MONO_BUILD_INFO_H +#endif // EDITOR_INTERNAL_CALL_H diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp deleted file mode 100644 index a962d6df27..0000000000 --- a/modules/mono/editor/godotsharp_builds.cpp +++ /dev/null @@ -1,632 +0,0 @@ -/*************************************************************************/ -/* godotsharp_builds.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "godotsharp_builds.h" - -#include "core/os/os.h" -#include "core/vector.h" -#include "main/main.h" - -#include "../glue/cs_glue_version.gen.h" -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../utils/path_utils.h" -#include "bindings_generator.h" -#include "csharp_project.h" -#include "godotsharp_editor.h" - -#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)" -#define PROP_NAME_MSBUILD_VS "MSBuild (VS Build Tools)" -#define PROP_NAME_XBUILD "xbuild (Deprecated)" - -void godot_icall_BuildInstance_ExitCallback(MonoString *p_solution, MonoString *p_config, int p_exit_code) { - - String solution = GDMonoMarshal::mono_string_to_godot(p_solution); - String config = GDMonoMarshal::mono_string_to_godot(p_config); - GodotSharpBuilds::get_singleton()->build_exit_callback(MonoBuildInfo(solution, config), p_exit_code); -} - -static Vector<const char *> _get_msbuild_hint_dirs() { - Vector<const char *> ret; -#ifdef OSX_ENABLED - ret.push_back("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); - ret.push_back("/usr/local/var/homebrew/linked/mono/bin/"); -#endif - ret.push_back("/opt/novell/mono/bin/"); - return ret; -} - -#ifdef UNIX_ENABLED -String _find_build_engine_on_unix(const String &p_name) { - String ret = path_which(p_name); - - if (ret.length()) - return ret; - - String ret_fallback = path_which(p_name + ".exe"); - if (ret_fallback.length()) - return ret_fallback; - - static Vector<const char *> locations = _get_msbuild_hint_dirs(); - - for (int i = 0; i < locations.size(); i++) { - String hint_path = locations[i] + p_name; - - if (FileAccess::exists(hint_path)) { - return hint_path; - } - } - - return String(); -} -#endif - -MonoString *godot_icall_BuildInstance_get_MSBuildPath() { - - GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))); - -#if defined(WINDOWS_ENABLED) - switch (build_tool) { - case GodotSharpBuilds::MSBUILD_VS: { - static String msbuild_tools_path; - - if (msbuild_tools_path.empty() || !FileAccess::exists(msbuild_tools_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path(); - - if (msbuild_tools_path.empty()) { - ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Tried with path: " + msbuild_tools_path); - return NULL; - } - } - - if (!msbuild_tools_path.ends_with("\\")) - msbuild_tools_path += "\\"; - - return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe"); - } break; - case GodotSharpBuilds::MSBUILD_MONO: { - String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat"); - - if (!FileAccess::exists(msbuild_path)) { - ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_MONO "'. Tried with path: " + msbuild_path); - return NULL; - } - - return GDMonoMarshal::mono_string_from_godot(msbuild_path); - } break; - case GodotSharpBuilds::XBUILD: { - String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat"); - - if (!FileAccess::exists(xbuild_path)) { - ERR_PRINTS("Cannot find executable for '" PROP_NAME_XBUILD "'. Tried with path: " + xbuild_path); - return NULL; - } - - return GDMonoMarshal::mono_string_from_godot(xbuild_path); - } break; - default: - ERR_EXPLAIN("You don't deserve to live"); - CRASH_NOW(); - } -#elif defined(UNIX_ENABLED) - static String msbuild_path; - static String xbuild_path; - - if (build_tool == GodotSharpBuilds::XBUILD) { - if (xbuild_path.empty() || !FileAccess::exists(xbuild_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - xbuild_path = _find_build_engine_on_unix("msbuild"); - } - - if (xbuild_path.empty()) { - ERR_PRINT("Cannot find binary for '" PROP_NAME_XBUILD "'"); - return NULL; - } - } else { - if (msbuild_path.empty() || !FileAccess::exists(msbuild_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - msbuild_path = _find_build_engine_on_unix("msbuild"); - } - - if (msbuild_path.empty()) { - ERR_PRINT("Cannot find binary for '" PROP_NAME_MSBUILD_MONO "'"); - return NULL; - } - } - - return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path); -#else - (void)build_tool; // UNUSED - - ERR_EXPLAIN("Not implemented on this platform"); - ERR_FAIL_V(NULL); -#endif -} - -MonoString *godot_icall_BuildInstance_get_MonoWindowsBinDir() { - -#if defined(WINDOWS_ENABLED) - const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info(); - if (mono_reg_info.bin_dir.length()) { - return GDMonoMarshal::mono_string_from_godot(mono_reg_info.bin_dir); - } - - ERR_EXPLAIN("Cannot find Mono's binaries directory in the registry"); - ERR_FAIL_V(NULL); -#else - return NULL; -#endif -} - -MonoBoolean godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows() { - -#if defined(WINDOWS_ENABLED) - return GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))) == GodotSharpBuilds::MSBUILD_MONO; -#else - return false; -#endif -} - -MonoBoolean godot_icall_BuildInstance_get_PrintBuildOutput() { - - return (bool)EDITOR_GET("mono/builds/print_build_output"); -} - -void GodotSharpBuilds::register_internal_calls() { - - static bool registered = false; - ERR_FAIL_COND(registered); - registered = true; - - mono_add_internal_call("GodotSharpTools.Build.BuildSystem::godot_icall_BuildInstance_ExitCallback", (void *)godot_icall_BuildInstance_ExitCallback); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildPath", (void *)godot_icall_BuildInstance_get_MSBuildPath); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MonoWindowsBinDir", (void *)godot_icall_BuildInstance_get_MonoWindowsBinDir); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows", (void *)godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows); - mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_PrintBuildOutput", (void *)godot_icall_BuildInstance_get_PrintBuildOutput); -} - -void GodotSharpBuilds::show_build_error_dialog(const String &p_message) { - - GodotSharpEditor::get_singleton()->show_error_dialog(p_message, "Build error"); - MonoBottomPanel::get_singleton()->show_build_tab(); -} - -bool GodotSharpBuilds::build_api_sln(const String &p_api_sln_dir, const String &p_config) { - - String api_sln_file = p_api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); - - String core_api_assembly_dir = p_api_sln_dir.plus_file(CORE_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config); - String core_api_assembly_file = core_api_assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - - String editor_api_assembly_dir = p_api_sln_dir.plus_file(EDITOR_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config); - String editor_api_assembly_file = editor_api_assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - if (!FileAccess::exists(core_api_assembly_file) || !FileAccess::exists(editor_api_assembly_file)) { - MonoBuildInfo api_build_info(api_sln_file, p_config); - // TODO Replace this global NoWarn with '#pragma warning' directives on generated files, - // once we start to actively document manually maintained C# classes - api_build_info.custom_props.push_back("NoWarn=1591"); // Ignore missing documentation warnings - - if (!GodotSharpBuilds::get_singleton()->build(api_build_info)) { - show_build_error_dialog("Failed to build " API_SOLUTION_NAME " solution."); - return false; - } - } - - return true; -} - -bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) { - - // Create destination directory if needed - if (!DirAccess::exists(p_dst_dir)) { - DirAccess *da = DirAccess::create_for_path(p_dst_dir); - Error err = da->make_dir_recursive(p_dst_dir); - memdelete(da); - - if (err != OK) { - show_build_error_dialog("Failed to create destination directory for the API assemblies. Error: " + itos(err)); - return false; - } - } - - String assembly_file = p_assembly_name + ".dll"; - String assembly_src = p_src_dir.plus_file(assembly_file); - String assembly_dst = p_dst_dir.plus_file(assembly_file); - - if (!FileAccess::exists(assembly_dst) || - FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - - String xml_file = p_assembly_name + ".xml"; - if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK) - WARN_PRINTS("Failed to copy " + xml_file); - - String pdb_file = p_assembly_name + ".pdb"; - if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK) - WARN_PRINTS("Failed to copy " + pdb_file); - - Error err = da->copy(assembly_src, assembly_dst); - - if (err != OK) { - show_build_error_dialog("Failed to copy " + assembly_file); - return false; - } - - GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false); - } - - return true; -} - -String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) { - - uint64_t api_hash = p_api_type == APIAssembly::API_CORE ? - GDMono::get_singleton()->get_api_core_hash() : - GDMono::get_singleton()->get_api_editor_hash(); - return String::num_uint64(api_hash) + - "_" + String::num_uint64(BindingsGenerator::get_version()) + - "_" + String::num_uint64(CS_GLUE_VERSION); -} - -bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) { - - String api_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; - - String editor_prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir(); - String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - - if (FileAccess::exists(editor_prebuilt_api_dir.plus_file(api_name + ".dll"))) { - EditorProgress pr("mono_copy_prebuilt_api_assembly", "Copying prebuilt " + api_name + " assembly...", 1); - pr.step("Copying " + api_name + " assembly", 0); - return GodotSharpBuilds::copy_api_assembly(editor_prebuilt_api_dir, res_assemblies_dir, api_name, p_api_type); - } - - String api_build_config = "Release"; - - EditorProgress pr("mono_build_release_" API_SOLUTION_NAME, "Building " API_SOLUTION_NAME " solution...", 3); - - pr.step("Generating " API_SOLUTION_NAME " solution", 0); - - String api_sln_dir = GodotSharpDirs::get_mono_solutions_dir() - .plus_file(_api_folder_name(APIAssembly::API_CORE)); - - String api_sln_file = api_sln_dir.plus_file(API_SOLUTION_NAME ".sln"); - - if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) { - BindingsGenerator bindings_generator; - - if (!OS::get_singleton()->is_stdout_verbose()) { - bindings_generator.set_log_print_enabled(false); - } - - Error err = bindings_generator.generate_cs_api(api_sln_dir); - if (err != OK) { - show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err)); - return false; - } - } - - pr.step("Building " API_SOLUTION_NAME " solution", 1); - - if (!GodotSharpBuilds::build_api_sln(api_sln_dir, api_build_config)) - return false; - - pr.step("Copying " + api_name + " assembly", 2); - - // Copy the built assembly to the assemblies directory - String api_assembly_dir = api_sln_dir.plus_file(api_name).plus_file("bin").plus_file(api_build_config); - if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type)) - return false; - - return true; -} - -bool GodotSharpBuilds::build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines) { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) - return true; // No solution to build - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) - return false; - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) - return false; - - EditorProgress pr("mono_project_debug_build", "Building project solution...", 1); - pr.step("Building project solution", 0); - - MonoBuildInfo build_info(GodotSharpDirs::get_project_sln_path(), p_config); - - // Add Godot defines -#ifdef WINDOWS_ENABLED - String constants = "GodotDefineConstants=\""; -#else - String constants = "GodotDefineConstants=\\\""; -#endif - - for (int i = 0; i < p_godot_defines.size(); i++) { - constants += "GODOT_" + p_godot_defines[i].to_upper().replace("-", "_").replace(" ", "_").replace(";", "_") + ";"; - } - -#ifdef REAL_T_IS_DOUBLE - constants += "GODOT_REAL_T_IS_DOUBLE;"; -#endif - -#ifdef WINDOWS_ENABLED - constants += "\""; -#else - constants += "\\\""; -#endif - build_info.custom_props.push_back(constants); - - if (!GodotSharpBuilds::get_singleton()->build(build_info)) { - GodotSharpBuilds::show_build_error_dialog("Failed to build project solution"); - return false; - } - - return true; -} - -bool GodotSharpBuilds::editor_build_callback() { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) - return true; // No solution to build - - String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor"); - String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player"); - - Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor); - ERR_FAIL_COND_V(metadata_err != OK, false); - - if (FileAccess::exists(scripts_metadata_path_editor)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player); - - ERR_EXPLAIN("Failed to copy scripts metadata file"); - ERR_FAIL_COND_V(copy_err != OK, false); - } - - Vector<String> godot_defines; - godot_defines.push_back(OS::get_singleton()->get_name()); - godot_defines.push_back(sizeof(void *) == 4 ? "32" : "64"); - return build_project_blocking("Tools", godot_defines); -} - -GodotSharpBuilds *GodotSharpBuilds::singleton = NULL; - -void GodotSharpBuilds::build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code) { - - BuildProcess *match = builds.getptr(p_build_info); - ERR_FAIL_NULL(match); - - BuildProcess &bp = *match; - bp.on_exit(p_exit_code); -} - -void GodotSharpBuilds::restart_build(MonoBuildTab *p_build_tab) { -} - -void GodotSharpBuilds::stop_build(MonoBuildTab *p_build_tab) { -} - -bool GodotSharpBuilds::build(const MonoBuildInfo &p_build_info) { - - BuildProcess *match = builds.getptr(p_build_info); - - if (match) { - BuildProcess &bp = *match; - bp.start(true); - return bp.exit_code == 0; - } else { - BuildProcess bp = BuildProcess(p_build_info); - bp.start(true); - builds.set(p_build_info, bp); - return bp.exit_code == 0; - } -} - -bool GodotSharpBuilds::build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) { - - BuildProcess *match = builds.getptr(p_build_info); - - if (match) { - BuildProcess &bp = *match; - bp.start(); - return !bp.exited; // failed to start - } else { - BuildProcess bp = BuildProcess(p_build_info, p_callback); - bp.start(); - builds.set(p_build_info, bp); - return !bp.exited; // failed to start - } -} - -GodotSharpBuilds::GodotSharpBuilds() { - - singleton = this; - - EditorNode::get_singleton()->add_build_callback(&GodotSharpBuilds::editor_build_callback); - - // Build tool settings - EditorSettings *ed_settings = EditorSettings::get_singleton(); - -#ifdef WINDOWS_ENABLED - EDITOR_DEF("mono/builds/build_tool", MSBUILD_VS); -#else - EDITOR_DEF("mono/builds/build_tool", MSBUILD_MONO); -#endif - - ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM, - PROP_NAME_MSBUILD_MONO -#ifdef WINDOWS_ENABLED - "," PROP_NAME_MSBUILD_VS -#endif - "," PROP_NAME_XBUILD)); - - EDITOR_DEF("mono/builds/print_build_output", false); -} - -GodotSharpBuilds::~GodotSharpBuilds() { - - singleton = NULL; -} - -void GodotSharpBuilds::BuildProcess::on_exit(int p_exit_code) { - - exited = true; - exit_code = p_exit_code; - build_tab->on_build_exit(p_exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR); - build_instance.unref(); - - if (exit_callback) - exit_callback(exit_code); -} - -void GodotSharpBuilds::BuildProcess::start(bool p_blocking) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - exit_code = -1; - - String log_dirpath = build_info.get_log_dirpath(); - - if (build_tab) { - build_tab->on_build_start(); - } else { - build_tab = memnew(MonoBuildTab(build_info, log_dirpath)); - MonoBottomPanel::get_singleton()->add_build_tab(build_tab); - } - - if (p_blocking) { - // Required in order to update the build tasks list - Main::iteration(); - } - - if (!exited) { - exited = true; - String message = "Tried to start build process, but it is already running"; - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - - exited = false; - - // Remove old issues file - - String issues_file = get_msbuild_issues_filename(); - DirAccessRef d = DirAccess::create_for_path(log_dirpath); - if (d->file_exists(issues_file)) { - Error err = d->remove(issues_file); - if (err != OK) { - exited = true; - String file_path = ProjectSettings::get_singleton()->localize_path(log_dirpath).plus_file(issues_file); - String message = "Cannot remove issues file: " + file_path; - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - } - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Build", "BuildInstance"); - - MonoObject *mono_object = mono_object_new(mono_domain_get(), klass->get_mono_ptr()); - - // Construct - - Variant solution = build_info.solution; - Variant config = build_info.configuration; - - const Variant *ctor_args[2] = { &solution, &config }; - - MonoException *exc = NULL; - GDMonoMethod *ctor = klass->get_method(".ctor", 2); - ctor->invoke(mono_object, ctor_args, &exc); - - if (exc) { - exited = true; - GDMonoUtils::debug_unhandled_exception(exc); - String message = "The build constructor threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc); - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - - // Call Build - - String logger_assembly_path = GDMono::get_singleton()->get_editor_tools_assembly()->get_path(); - Variant logger_assembly = ProjectSettings::get_singleton()->globalize_path(logger_assembly_path); - Variant logger_output_dir = log_dirpath; - Variant custom_props = build_info.custom_props; - - const Variant *args[3] = { &logger_assembly, &logger_output_dir, &custom_props }; - - exc = NULL; - GDMonoMethod *build_method = klass->get_method(p_blocking ? "Build" : "BuildAsync", 3); - build_method->invoke(mono_object, args, &exc); - - if (exc) { - exited = true; - GDMonoUtils::debug_unhandled_exception(exc); - String message = "The build method threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc); - build_tab->on_build_exec_failed(message); - ERR_EXPLAIN(message); - ERR_FAIL(); - } - - // Build returned - - if (p_blocking) { - exited = true; - exit_code = klass->get_field("exitCode")->get_int_value(mono_object); - - if (exit_code != 0) { - String log_filepath = build_info.get_log_dirpath().plus_file(get_msbuild_log_filename()); - print_verbose("MSBuild exited with code: " + itos(exit_code) + ". Log file: " + log_filepath); - } - - build_tab->on_build_exit(exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR); - } else { - build_instance = MonoGCHandle::create_strong(mono_object); - exited = false; - } -} - -GodotSharpBuilds::BuildProcess::BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) : - build_info(p_build_info), - build_tab(NULL), - exit_callback(p_callback), - exited(true), - exit_code(-1) { -} diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h deleted file mode 100644 index 2e9050e12e..0000000000 --- a/modules/mono/editor/godotsharp_builds.h +++ /dev/null @@ -1,103 +0,0 @@ -/*************************************************************************/ -/* godotsharp_builds.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GODOTSHARP_BUILDS_H -#define GODOTSHARP_BUILDS_H - -#include "../mono_gd/gd_mono.h" -#include "mono_bottom_panel.h" -#include "mono_build_info.h" - -typedef void (*GodotSharpBuild_ExitCallback)(int); - -class GodotSharpBuilds { - -private: - struct BuildProcess { - Ref<MonoGCHandle> build_instance; - MonoBuildInfo build_info; - MonoBuildTab *build_tab; - GodotSharpBuild_ExitCallback exit_callback; - bool exited; - int exit_code; - - void on_exit(int p_exit_code); - void start(bool p_blocking = false); - - BuildProcess() {} - BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL); - }; - - HashMap<MonoBuildInfo, BuildProcess, MonoBuildInfo::Hasher> builds; - - static String _api_folder_name(APIAssembly::Type p_api_type); - - static GodotSharpBuilds *singleton; - -public: - enum BuildTool { - MSBUILD_MONO, -#ifdef WINDOWS_ENABLED - MSBUILD_VS, -#endif - XBUILD // Deprecated - }; - - _FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; } - - static void register_internal_calls(); - - static void show_build_error_dialog(const String &p_message); - - static const char *get_msbuild_issues_filename() { return "msbuild_issues.csv"; } - static const char *get_msbuild_log_filename() { return "msbuild_log.txt"; } - - void build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code); - - void restart_build(MonoBuildTab *p_build_tab); - void stop_build(MonoBuildTab *p_build_tab); - - bool build(const MonoBuildInfo &p_build_info); - bool build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL); - - static bool build_api_sln(const String &p_api_sln_dir, const String &p_config); - static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type); - - static bool make_api_assembly(APIAssembly::Type p_api_type); - - static bool build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines); - - static bool editor_build_callback(); - - GodotSharpBuilds(); - ~GodotSharpBuilds(); -}; - -#endif // GODOTSHARP_BUILDS_H diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp deleted file mode 100644 index 8dc5ced29f..0000000000 --- a/modules/mono/editor/godotsharp_editor.cpp +++ /dev/null @@ -1,582 +0,0 @@ -/*************************************************************************/ -/* godotsharp_editor.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "godotsharp_editor.h" - -#include "core/message_queue.h" -#include "core/os/os.h" -#include "core/project_settings.h" -#include "scene/gui/control.h" -#include "scene/main/node.h" - -#include "../csharp_script.h" -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../utils/path_utils.h" -#include "bindings_generator.h" -#include "csharp_project.h" -#include "dotnet_solution.h" -#include "godotsharp_export.h" - -#ifdef OSX_ENABLED -#include "../utils/osx_utils.h" -#endif - -#ifdef WINDOWS_ENABLED -#include "../utils/mono_reg_utils.h" -#endif - -GodotSharpEditor *GodotSharpEditor::singleton = NULL; - -bool GodotSharpEditor::_create_project_solution() { - - EditorProgress pr("create_csharp_solution", TTR("Generating solution..."), 2); - - pr.step(TTR("Generating C# project...")); - - String path = OS::get_singleton()->get_resource_dir(); - String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - if (appname_safe.empty()) { - appname_safe = "UnnamedProject"; - } - - String guid = CSharpProject::generate_game_project(path, appname_safe); - - if (guid.length()) { - - DotNetSolution solution(appname_safe); - - if (!solution.set_path(path)) { - show_error_dialog(TTR("Failed to create solution.")); - return false; - } - - DotNetSolution::ProjectInfo proj_info; - proj_info.guid = guid; - proj_info.relpath = appname_safe + ".csproj"; - proj_info.configs.push_back("Debug"); - proj_info.configs.push_back("Release"); - proj_info.configs.push_back("Tools"); - - solution.add_new_project(appname_safe, proj_info); - - Error sln_error = solution.save(); - - if (sln_error != OK) { - show_error_dialog(TTR("Failed to save solution.")); - return false; - } - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) - return false; - - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) - return false; - - pr.step(TTR("Done")); - - // Here, after all calls to progress_task_step - call_deferred("_remove_create_sln_menu_option"); - - } else { - show_error_dialog(TTR("Failed to create C# project.")); - } - - return true; -} - -void GodotSharpEditor::_make_api_solutions_if_needed() { - // I'm sick entirely of ProgressDialog - - static int attempts_left = 100; - - if (MessageQueue::get_singleton()->is_flushing() || !SceneTree::get_singleton()) { - ERR_FAIL_COND(attempts_left == 0); // You've got to be kidding - - if (SceneTree::get_singleton()) { - SceneTree::get_singleton()->connect("idle_frame", this, "_make_api_solutions_if_needed", Vector<Variant>()); - } else { - call_deferred("_make_api_solutions_if_needed"); - } - - attempts_left--; - return; - } - - // Recursion guard needed because signals don't play well with ProgressDialog either, but unlike - // the message queue, with signals the collateral damage should be minimal in the worst case. - static bool recursion_guard = false; - if (!recursion_guard) { - recursion_guard = true; - - // Oneshot signals don't play well with ProgressDialog either, so we do it this way instead - SceneTree::get_singleton()->disconnect("idle_frame", this, "_make_api_solutions_if_needed"); - - _make_api_solutions_if_needed_impl(); - - recursion_guard = false; - } -} - -void GodotSharpEditor::_make_api_solutions_if_needed_impl() { - // If the project has a solution and C# project make sure the API assemblies are present and up to date - String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - - if (!FileAccess::exists(res_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll")) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) { - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE)) - return; - } - - if (!FileAccess::exists(res_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll")) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) { - if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR)) - return; // Redundant? I don't think so - } -} - -void GodotSharpEditor::_remove_create_sln_menu_option() { - - menu_popup->remove_item(menu_popup->get_item_index(MENU_CREATE_SLN)); - - bottom_panel_btn->show(); -} - -void GodotSharpEditor::_show_about_dialog() { - - bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start"); - about_dialog_checkbox->set_pressed(show_on_start); - about_dialog->popup_centered_minsize(); -} - -void GodotSharpEditor::_toggle_about_dialog_on_start(bool p_enabled) { - - bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start"); - if (show_on_start != p_enabled) { - EditorSettings::get_singleton()->set_setting("mono/editor/show_info_on_start", p_enabled); - } -} - -void GodotSharpEditor::_build_solution_pressed() { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) { - if (!_create_project_solution()) - return; // Failed to create solution - } - - MonoBottomPanel::get_singleton()->call("_build_project_pressed"); -} - -void GodotSharpEditor::_menu_option_pressed(int p_id) { - - switch (p_id) { - case MENU_CREATE_SLN: { - - _create_project_solution(); - } break; - case MENU_ABOUT_CSHARP: { - - _show_about_dialog(); - } break; - default: - ERR_FAIL(); - } -} - -void GodotSharpEditor::_notification(int p_notification) { - - switch (p_notification) { - - case NOTIFICATION_READY: { - - bool show_info_dialog = EDITOR_GET("mono/editor/show_info_on_start"); - if (show_info_dialog) { - about_dialog->set_exclusive(true); - _show_about_dialog(); - // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive then. - about_dialog->set_exclusive(false); - } - } - } -} - -void GodotSharpEditor::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_build_solution_pressed"), &GodotSharpEditor::_build_solution_pressed); - ClassDB::bind_method(D_METHOD("_create_project_solution"), &GodotSharpEditor::_create_project_solution); - ClassDB::bind_method(D_METHOD("_make_api_solutions_if_needed"), &GodotSharpEditor::_make_api_solutions_if_needed); - ClassDB::bind_method(D_METHOD("_remove_create_sln_menu_option"), &GodotSharpEditor::_remove_create_sln_menu_option); - ClassDB::bind_method(D_METHOD("_toggle_about_dialog_on_start"), &GodotSharpEditor::_toggle_about_dialog_on_start); - ClassDB::bind_method(D_METHOD("_menu_option_pressed", "id"), &GodotSharpEditor::_menu_option_pressed); -} - -MonoBoolean godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled(MonoString *p_bundle_id) { -#ifdef OSX_ENABLED - return (MonoBoolean)osx_is_app_bundle_installed(GDMonoMarshal::mono_string_to_godot(p_bundle_id)); -#else - (void)p_bundle_id; // UNUSED - ERR_FAIL_V(false); -#endif -} - -MonoString *godot_icall_Utils_OS_GetPlatformName() { - return GDMonoMarshal::mono_string_from_godot(OS::get_singleton()->get_name()); -} - -void GodotSharpEditor::register_internal_calls() { - - static bool registered = false; - ERR_FAIL_COND(registered); - registered = true; - - mono_add_internal_call("GodotSharpTools.Editor.MonoDevelopInstance::IsApplicationBundleInstalled", (void *)godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled); - mono_add_internal_call("GodotSharpTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName); - - GodotSharpBuilds::register_internal_calls(); - GodotSharpExport::register_internal_calls(); -} - -void GodotSharpEditor::show_error_dialog(const String &p_message, const String &p_title) { - - error_dialog->set_title(p_title); - error_dialog->set_text(p_message); - error_dialog->popup_centered_minsize(); -} - -Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { - - ExternalEditor editor = ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))); - - switch (editor) { - case EDITOR_VSCODE: { - static String vscode_path; - - if (vscode_path.empty() || !FileAccess::exists(vscode_path)) { - // Try to search it again if it wasn't found last time or if it was removed from its location - bool found = false; - - // TODO: Use initializer lists once C++11 is allowed - - static Vector<String> vscode_names; - if (vscode_names.empty()) { - vscode_names.push_back("code"); - vscode_names.push_back("code-oss"); - vscode_names.push_back("vscode"); - vscode_names.push_back("vscode-oss"); - vscode_names.push_back("visual-studio-code"); - vscode_names.push_back("visual-studio-code-oss"); - } - for (int i = 0; i < vscode_names.size(); i++) { - vscode_path = path_which(vscode_names[i]); - if (!vscode_path.empty()) { - found = true; - break; - } - } - - if (!found) - vscode_path.clear(); // Not found, clear so next time the empty() check is enough - } - - List<String> args; - -#ifdef OSX_ENABLED - // The package path is '/Applications/Visual Studio Code.app' - static const String vscode_bundle_id = "com.microsoft.VSCode"; - static bool osx_app_bundle_installed = osx_is_app_bundle_installed(vscode_bundle_id); - - if (osx_app_bundle_installed) { - args.push_back("-b"); - args.push_back(vscode_bundle_id); - - // The reusing of existing windows made by the 'open' command might not choose a wubdiw that is - // editing our folder. It's better to ask for a new window and let VSCode do the window management. - args.push_back("-n"); - - // The open process must wait until the application finishes (which is instant in VSCode's case) - args.push_back("--wait-apps"); - - args.push_back("--args"); - } -#endif - - args.push_back(ProjectSettings::get_singleton()->get_resource_path()); - - String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path()); - - if (p_line >= 0) { - args.push_back("-g"); - args.push_back(script_path + ":" + itos(p_line + 1) + ":" + itos(p_col)); - } else { - args.push_back(script_path); - } - -#ifdef OSX_ENABLED - ERR_EXPLAIN("Cannot find code editor: VSCode"); - ERR_FAIL_COND_V(!osx_app_bundle_installed && vscode_path.empty(), ERR_FILE_NOT_FOUND); - - String command = osx_app_bundle_installed ? "/usr/bin/open" : vscode_path; -#else - ERR_EXPLAIN("Cannot find code editor: VSCode"); - ERR_FAIL_COND_V(vscode_path.empty(), ERR_FILE_NOT_FOUND); - - String command = vscode_path; -#endif - - Error err = OS::get_singleton()->execute(command, args, false); - - if (err != OK) { - ERR_PRINT("Error when trying to execute code editor: VSCode"); - return err; - } - } break; -#ifdef OSX_ENABLED - case EDITOR_VISUALSTUDIO_MAC: - // [[fallthrough]]; -#endif - case EDITOR_MONODEVELOP: { -#ifdef OSX_ENABLED - bool is_visualstudio = editor == EDITOR_VISUALSTUDIO_MAC; - - MonoDevelopInstance **instance = is_visualstudio ? - &visualstudio_mac_instance : - &monodevelop_instance; - - MonoDevelopInstance::EditorId editor_id = is_visualstudio ? - MonoDevelopInstance::VISUALSTUDIO_FOR_MAC : - MonoDevelopInstance::MONODEVELOP; -#else - MonoDevelopInstance **instance = &monodevelop_instance; - MonoDevelopInstance::EditorId editor_id = MonoDevelopInstance::MONODEVELOP; -#endif - - if (!*instance) - *instance = memnew(MonoDevelopInstance(GodotSharpDirs::get_project_sln_path(), editor_id)); - - String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path()); - - if (p_line >= 0) { - script_path += ";" + itos(p_line + 1) + ";" + itos(p_col); - } - - (*instance)->execute(script_path); - } break; - default: - return ERR_UNAVAILABLE; - } - - return OK; -} - -bool GodotSharpEditor::overrides_external_editor() { - - return ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))) != EDITOR_NONE; -} - -GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { - - singleton = this; - - monodevelop_instance = NULL; -#ifdef OSX_ENABLED - visualstudio_mac_instance = NULL; -#endif - - editor = p_editor; - - error_dialog = memnew(AcceptDialog); - editor->get_gui_base()->add_child(error_dialog); - - bottom_panel_btn = editor->add_bottom_panel_item(TTR("Mono"), memnew(MonoBottomPanel(editor))); - - godotsharp_builds = memnew(GodotSharpBuilds); - - editor->add_child(memnew(MonoReloadNode)); - - menu_popup = memnew(PopupMenu); - menu_popup->hide(); - menu_popup->set_as_toplevel(true); - menu_popup->set_pass_on_modal_close_click(false); - - editor->add_tool_submenu_item("Mono", menu_popup); - - // TODO: Remove or edit this info dialog once Mono support is no longer in alpha - { - menu_popup->add_item(TTR("About C# support"), MENU_ABOUT_CSHARP); - about_dialog = memnew(AcceptDialog); - editor->get_gui_base()->add_child(about_dialog); - about_dialog->set_title("Important: C# support is not feature-complete"); - - // We don't use set_text() as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox - // we'll add. Instead we add containers and a new autowrapped Label inside. - - // Main VBoxContainer (icon + label on top, checkbox at bottom) - VBoxContainer *about_vbc = memnew(VBoxContainer); - about_dialog->add_child(about_vbc); - - // HBoxContainer for icon + label - HBoxContainer *about_hbc = memnew(HBoxContainer); - about_vbc->add_child(about_hbc); - - TextureRect *about_icon = memnew(TextureRect); - about_hbc->add_child(about_icon); - Ref<Texture> about_icon_tex = about_icon->get_icon("NodeWarning", "EditorIcons"); - about_icon->set_texture(about_icon_tex); - - Label *about_label = memnew(Label); - about_hbc->add_child(about_label); - about_label->set_custom_minimum_size(Size2(600, 150) * EDSCALE); - about_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); - about_label->set_autowrap(true); - String about_text = - String("C# support in Godot Engine is in late alpha stage and, while already usable, ") + - "it is not meant for use in production.\n\n" + - "Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " + - "Bugs and usability issues will be addressed gradually over future releases, " + - "potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" + - "If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" + - " https://github.com/godotengine/godot/issues\n\n" + - "Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!"; - about_label->set_text(about_text); - - EDITOR_DEF("mono/editor/show_info_on_start", true); - - // CheckBox in main container - about_dialog_checkbox = memnew(CheckBox); - about_vbc->add_child(about_dialog_checkbox); - about_dialog_checkbox->set_text("Show this warning when starting the editor"); - about_dialog_checkbox->connect("toggled", this, "_toggle_about_dialog_on_start"); - } - - String sln_path = GodotSharpDirs::get_project_sln_path(); - String csproj_path = GodotSharpDirs::get_project_csproj_path(); - - if (FileAccess::exists(sln_path) && FileAccess::exists(csproj_path)) { - // Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized. - call_deferred("_make_api_solutions_if_needed"); - } else { - bottom_panel_btn->hide(); - menu_popup->add_item(TTR("Create C# solution"), MENU_CREATE_SLN); - } - - menu_popup->connect("id_pressed", this, "_menu_option_pressed"); - - ToolButton *build_button = memnew(ToolButton); - build_button->set_text("Build"); - build_button->set_tooltip("Build solution"); - build_button->set_focus_mode(Control::FOCUS_NONE); - build_button->connect("pressed", this, "_build_solution_pressed"); - editor->get_menu_hb()->add_child(build_button); - - // External editor settings - EditorSettings *ed_settings = EditorSettings::get_singleton(); - EDITOR_DEF("mono/editor/external_editor", EDITOR_NONE); - - String settings_hint_str = "Disabled"; - -#if defined(WINDOWS_ENABLED) - settings_hint_str += ",MonoDevelop,Visual Studio Code"; -#elif defined(OSX_ENABLED) - settings_hint_str += ",Visual Studio,MonoDevelop,Visual Studio Code"; -#elif defined(UNIX_ENABLED) - settings_hint_str += ",MonoDevelop,Visual Studio Code"; -#endif - - ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/editor/external_editor", PROPERTY_HINT_ENUM, settings_hint_str)); - - // Export plugin - Ref<GodotSharpExport> godotsharp_export; - godotsharp_export.instance(); - EditorExport::get_singleton()->add_export_plugin(godotsharp_export); -} - -GodotSharpEditor::~GodotSharpEditor() { - - singleton = NULL; - - memdelete(godotsharp_builds); - - if (monodevelop_instance) { - memdelete(monodevelop_instance); - monodevelop_instance = NULL; - } -} - -MonoReloadNode *MonoReloadNode::singleton = NULL; - -void MonoReloadNode::_reload_timer_timeout() { - - if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { - CSharpLanguage::get_singleton()->reload_assemblies(false); - } -} - -void MonoReloadNode::restart_reload_timer() { - - reload_timer->stop(); - reload_timer->start(); -} - -void MonoReloadNode::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_reload_timer_timeout"), &MonoReloadNode::_reload_timer_timeout); -} - -void MonoReloadNode::_notification(int p_what) { - switch (p_what) { - case MainLoop::NOTIFICATION_WM_FOCUS_IN: { - restart_reload_timer(); - if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { - CSharpLanguage::get_singleton()->reload_assemblies(false); - } - } break; - default: { - } break; - }; -} - -MonoReloadNode::MonoReloadNode() { - - singleton = this; - - reload_timer = memnew(Timer); - add_child(reload_timer); - reload_timer->set_one_shot(false); - reload_timer->set_wait_time(EDITOR_DEF("mono/assembly_watch_interval_sec", 0.5)); - reload_timer->connect("timeout", this, "_reload_timer_timeout"); - reload_timer->start(); -} - -MonoReloadNode::~MonoReloadNode() { - - singleton = NULL; -} diff --git a/modules/mono/editor/godotsharp_editor.h b/modules/mono/editor/godotsharp_editor.h deleted file mode 100644 index d5bd8ba126..0000000000 --- a/modules/mono/editor/godotsharp_editor.h +++ /dev/null @@ -1,134 +0,0 @@ -/*************************************************************************/ -/* godotsharp_editor.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef GODOTSHARP_EDITOR_H -#define GODOTSHARP_EDITOR_H - -#include "godotsharp_builds.h" -#include "monodevelop_instance.h" - -class GodotSharpEditor : public Node { - GDCLASS(GodotSharpEditor, Node); - - EditorNode *editor; - - MenuButton *menu_button; - PopupMenu *menu_popup; - - AcceptDialog *error_dialog; - AcceptDialog *about_dialog; - CheckBox *about_dialog_checkbox; - - ToolButton *bottom_panel_btn; - - GodotSharpBuilds *godotsharp_builds; - - MonoDevelopInstance *monodevelop_instance; -#ifdef OSX_ENABLED - MonoDevelopInstance *visualstudio_mac_instance; -#endif - - bool _create_project_solution(); - void _make_api_solutions_if_needed(); - void _make_api_solutions_if_needed_impl(); - - void _remove_create_sln_menu_option(); - void _show_about_dialog(); - void _toggle_about_dialog_on_start(bool p_enabled); - - void _menu_option_pressed(int p_id); - - void _build_solution_pressed(); - - static GodotSharpEditor *singleton; - -protected: - void _notification(int p_notification); - static void _bind_methods(); - -public: - enum MenuOptions { - MENU_CREATE_SLN, - MENU_ABOUT_CSHARP, - }; - - enum ExternalEditor { - EDITOR_NONE, -#if defined(WINDOWS_ENABLED) - //EDITOR_VISUALSTUDIO, // TODO - EDITOR_MONODEVELOP, - EDITOR_VSCODE -#elif defined(OSX_ENABLED) - EDITOR_VISUALSTUDIO_MAC, - EDITOR_MONODEVELOP, - EDITOR_VSCODE -#elif defined(UNIX_ENABLED) - EDITOR_MONODEVELOP, - EDITOR_VSCODE -#endif - }; - - _FORCE_INLINE_ static GodotSharpEditor *get_singleton() { return singleton; } - - static void register_internal_calls(); - - void show_error_dialog(const String &p_message, const String &p_title = "Error"); - - Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col); - bool overrides_external_editor(); - - GodotSharpEditor(EditorNode *p_editor); - ~GodotSharpEditor(); -}; - -class MonoReloadNode : public Node { - GDCLASS(MonoReloadNode, Node); - - Timer *reload_timer; - - void _reload_timer_timeout(); - - static MonoReloadNode *singleton; - -protected: - static void _bind_methods(); - - void _notification(int p_what); - -public: - _FORCE_INLINE_ static MonoReloadNode *get_singleton() { return singleton; } - - void restart_reload_timer(); - - MonoReloadNode(); - ~MonoReloadNode(); -}; - -#endif // GODOTSHARP_EDITOR_H diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 590ec007ad..020bb70a08 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -30,180 +30,28 @@ #include "godotsharp_export.h" -#include "core/version.h" +#include <mono/metadata/image.h> -#include "../csharp_script.h" -#include "../godotsharp_defs.h" -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "csharp_project.h" -#include "godotsharp_builds.h" +#include "../mono_gd/gd_mono.h" +#include "../mono_gd/gd_mono_assembly.h" -static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() { - String current_version = VERSION_FULL_CONFIG; - String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(current_version); - return GDMonoMarshal::mono_string_from_godot(ProjectSettings::get_singleton()->globalize_path(templates_dir)); -} - -static MonoString *godot_icall_GodotSharpExport_GetDataDirName() { - String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - return GDMonoMarshal::mono_string_from_godot("data_" + appname_safe); -} - -void GodotSharpExport::register_internal_calls() { - static bool registered = false; - ERR_FAIL_COND(registered); - registered = true; - - mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetTemplatesDir", (void *)godot_icall_GodotSharpExport_GetTemplatesDir); - mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetDataDirName", (void *)godot_icall_GodotSharpExport_GetDataDirName); -} - -void GodotSharpExport::_export_file(const String &p_path, const String &p_type, const Set<String> &) { - - if (p_type != CSharpLanguage::get_singleton()->get_type()) - return; - - ERR_FAIL_COND(p_path.get_extension() != CSharpLanguage::get_singleton()->get_extension()); - - // TODO what if the source file is not part of the game's C# project - - if (!GLOBAL_GET("mono/export/include_scripts_content")) { - // We don't want to include the source code on exported games - add_file(p_path, Vector<uint8_t>(), false); - skip(); - } -} - -void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags) { - - // TODO right now there is no way to stop the export process with an error - - ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - ERR_FAIL_NULL(TOOLS_DOMAIN); - ERR_FAIL_NULL(GDMono::get_singleton()->get_editor_tools_assembly()); - - if (FileAccess::exists(GodotSharpDirs::get_project_sln_path())) { - String build_config = p_debug ? "Debug" : "Release"; - - String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata." + String(p_debug ? "debug" : "release")); - Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path); - ERR_FAIL_COND(metadata_err != OK); - - ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path)); - - // Turn export features into defines - Vector<String> godot_defines; - for (Set<String>::Element *E = p_features.front(); E; E = E->next()) { - godot_defines.push_back(E->get()); - } - ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config, godot_defines)); - - // Add dependency assemblies - - Map<String, String> dependencies; - - String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name"); - String project_dll_name_safe = OS::get_singleton()->get_safe_dir_name(project_dll_name); - if (project_dll_name_safe.empty()) { - project_dll_name_safe = "UnnamedProject"; - } - - String project_dll_src_dir = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config); - String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name_safe + ".dll"); - dependencies.insert(project_dll_name_safe, project_dll_src_path); - - { - MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); - ERR_FAIL_NULL(export_domain); - _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); - - _GDMONO_SCOPE_DOMAIN_(export_domain); +String get_assemblyref_name(MonoImage *p_image, int index) { + const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF); - GDMonoAssembly *scripts_assembly = NULL; - bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name_safe, - project_dll_src_path, &scripts_assembly, /* refonly: */ true); + uint32_t cols[MONO_ASSEMBLYREF_SIZE]; - ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name_safe); - ERR_FAIL_COND(!load_success); + mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE); - Vector<String> search_dirs; - String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); - String android_bcl_dir = templates_dir.plus_file("android-bcl"); - - String custom_lib_dir; - - if (p_features.find("Android") && DirAccess::exists(android_bcl_dir)) { - custom_lib_dir = android_bcl_dir; - } - - GDMonoAssembly::fill_search_dirs(search_dirs, build_config, custom_lib_dir); - - Error depend_error = _get_assembly_dependencies(scripts_assembly, search_dirs, dependencies); - ERR_FAIL_COND(depend_error != OK); - } - - for (Map<String, String>::Element *E = dependencies.front(); E; E = E->next()) { - String depend_src_path = E->value(); - String depend_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(depend_src_path.get_file()); - ERR_FAIL_COND(!_add_file(depend_src_path, depend_dst_path)); - } - } - - // Mono specific export template extras (data dir) - - GDMonoClass *export_class = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "GodotSharpExport"); - ERR_FAIL_NULL(export_class); - GDMonoMethod *export_begin_method = export_class->get_method("_ExportBegin", 4); - ERR_FAIL_NULL(export_begin_method); - - MonoArray *features = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_features.size()); - int i = 0; - for (const Set<String>::Element *E = p_features.front(); E; E = E->next()) { - MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E->get()); - mono_array_setref(features, i, boxed); - i++; - } - - MonoBoolean debug = p_debug; - MonoString *path = GDMonoMarshal::mono_string_from_godot(p_path); - uint32_t flags = p_flags; - void *args[4] = { features, &debug, path, &flags }; - MonoException *exc = NULL; - export_begin_method->invoke_raw(NULL, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL(); - } -} - -bool GodotSharpExport::_add_file(const String &p_src_path, const String &p_dst_path, bool p_remap) { - - FileAccessRef f = FileAccess::open(p_src_path, FileAccess::READ); - ERR_FAIL_COND_V(!f, false); - - Vector<uint8_t> data; - data.resize(f->get_len()); - f->get_buffer(data.ptrw(), data.size()); - - add_file(p_dst_path, data, p_remap); - - return true; + return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])); } -Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies) { - +Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) { MonoImage *image = p_assembly->get_image(); for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { - MonoAssemblyName *ref_aname = aname_prealloc; - mono_assembly_get_assemblyref(image, i, ref_aname); - String ref_name = mono_assembly_name_get_name(ref_aname); + String ref_name = get_assemblyref_name(image, i); - if (r_dependencies.find(ref_name)) + if (r_dependencies.has(ref_name)) continue; GDMonoAssembly *ref_assembly = NULL; @@ -242,9 +90,9 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c ERR_FAIL_V(ERR_CANT_RESOLVE); } - r_dependencies.insert(ref_name, ref_assembly->get_path()); + r_dependencies[ref_name] = ref_assembly->get_path(); - Error err = _get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); + Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies); if (err != OK) { ERR_EXPLAIN("Cannot load one of the dependencies for the assembly: " + ref_name); ERR_FAIL_V(err); @@ -254,14 +102,22 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c return OK; } -GodotSharpExport::GodotSharpExport() { - // MonoAssemblyName is an incomplete type (internal to mono), so we can't allocate it ourselves. - // There isn't any api to allocate an empty one either, so we need to do it this way. - aname_prealloc = mono_assembly_name_new("whatever"); - mono_assembly_name_free(aname_prealloc); // "it does not frees the object itself, only the name members" (typo included) -} +Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) { + MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); + ERR_FAIL_NULL_V(export_domain, FAILED); + _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); + + _GDMONO_SCOPE_DOMAIN_(export_domain); + + GDMonoAssembly *scripts_assembly = NULL; + bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name, + p_project_dll_src_path, &scripts_assembly, /* refonly: */ true); + + ERR_EXPLAIN("Cannot load assembly (refonly): " + p_project_dll_name); + ERR_FAIL_COND_V(!load_success, ERR_CANT_RESOLVE); + + Vector<String> search_dirs; + GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir); -GodotSharpExport::~GodotSharpExport() { - if (aname_prealloc) - mono_free(aname_prealloc); + return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies); } diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h index 4dc8ea75d5..8d121a6bc3 100644 --- a/modules/mono/editor/godotsharp_export.h +++ b/modules/mono/editor/godotsharp_export.h @@ -31,29 +31,19 @@ #ifndef GODOTSHARP_EXPORT_H #define GODOTSHARP_EXPORT_H -#include <mono/metadata/image.h> - -#include "editor/editor_export.h" +#include "core/dictionary.h" +#include "core/error_list.h" +#include "core/ustring.h" #include "../mono_gd/gd_mono_header.h" -class GodotSharpExport : public EditorExportPlugin { - - MonoAssemblyName *aname_prealloc; - - bool _add_file(const String &p_src_path, const String &p_dst_path, bool p_remap = false); - - Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies); - -protected: - virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features); - virtual void _export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags); +namespace GodotSharpExport { -public: - static void register_internal_calls(); +Error get_exported_assembly_dependencies(const String &p_project_dll_name, + const String &p_project_dll_src_path, const String &p_build_config, + const String &p_custom_lib_dir, Dictionary &r_dependencies); +Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies); - GodotSharpExport(); - ~GodotSharpExport(); -}; +} // namespace GodotSharpExport #endif // GODOTSHARP_EXPORT_H diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp deleted file mode 100644 index 5d9e39b6c2..0000000000 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ /dev/null @@ -1,528 +0,0 @@ -/*************************************************************************/ -/* mono_bottom_panel.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "mono_bottom_panel.h" - -#include "editor/plugins/script_editor_plugin.h" -#include "editor/script_editor_debugger.h" - -#include "../csharp_script.h" -#include "../godotsharp_dirs.h" -#include "csharp_project.h" -#include "godotsharp_editor.h" - -MonoBottomPanel *MonoBottomPanel::singleton = NULL; - -void MonoBottomPanel::_update_build_tabs_list() { - - build_tabs_list->clear(); - - int current_tab = build_tabs->get_current_tab(); - - bool no_current_tab = current_tab < 0 || current_tab >= build_tabs->get_tab_count(); - - for (int i = 0; i < build_tabs->get_child_count(); i++) { - - MonoBuildTab *tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(i)); - - if (tab) { - String item_name = tab->build_info.solution.get_file().get_basename(); - item_name += " [" + tab->build_info.configuration + "]"; - - build_tabs_list->add_item(item_name, tab->get_icon_texture()); - - String item_tooltip = "Solution: " + tab->build_info.solution; - item_tooltip += "\nConfiguration: " + tab->build_info.configuration; - item_tooltip += "\nStatus: "; - - if (tab->build_exited) { - item_tooltip += tab->build_result == MonoBuildTab::RESULT_SUCCESS ? "Succeeded" : "Errored"; - } else { - item_tooltip += "Running"; - } - - if (!tab->build_exited || tab->build_result == MonoBuildTab::RESULT_ERROR) { - item_tooltip += "\nErrors: " + itos(tab->error_count); - } - - item_tooltip += "\nWarnings: " + itos(tab->warning_count); - - build_tabs_list->set_item_tooltip(i, item_tooltip); - - if (no_current_tab || current_tab == i) { - build_tabs_list->select(i); - _build_tabs_item_selected(i); - } - } - } -} - -void MonoBottomPanel::add_build_tab(MonoBuildTab *p_build_tab) { - - build_tabs->add_child(p_build_tab); - raise_build_tab(p_build_tab); -} - -void MonoBottomPanel::raise_build_tab(MonoBuildTab *p_build_tab) { - - ERR_FAIL_COND(p_build_tab->get_parent() != build_tabs); - build_tabs->move_child(p_build_tab, 0); - _update_build_tabs_list(); -} - -void MonoBottomPanel::show_build_tab() { - - for (int i = 0; i < panel_tabs->get_tab_count(); i++) { - if (panel_tabs->get_tab_control(i) == panel_builds_tab) { - panel_tabs->set_current_tab(i); - editor->make_bottom_panel_item_visible(this); - return; - } - } - - ERR_PRINT("Builds tab not found"); -} - -void MonoBottomPanel::_build_tabs_item_selected(int p_idx) { - - ERR_FAIL_INDEX(p_idx, build_tabs->get_tab_count()); - - build_tabs->set_current_tab(p_idx); - if (!build_tabs->is_visible()) - build_tabs->set_visible(true); - - warnings_btn->set_visible(true); - errors_btn->set_visible(true); - view_log_btn->set_visible(true); -} - -void MonoBottomPanel::_build_tabs_nothing_selected() { - - if (build_tabs->get_tab_count() != 0) { // just in case - build_tabs->set_visible(false); - - // This callback is called when clicking on the empty space of the list. - // ItemList won't deselect the items automatically, so we must do it ourselves. - build_tabs_list->unselect_all(); - } - - warnings_btn->set_visible(false); - errors_btn->set_visible(false); - view_log_btn->set_visible(false); -} - -void MonoBottomPanel::_warnings_toggled(bool p_pressed) { - - int current_tab = build_tabs->get_current_tab(); - ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count()); - MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab)); - build_tab->warnings_visible = p_pressed; - build_tab->_update_issues_list(); -} - -void MonoBottomPanel::_errors_toggled(bool p_pressed) { - - int current_tab = build_tabs->get_current_tab(); - ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count()); - MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab)); - build_tab->errors_visible = p_pressed; - build_tab->_update_issues_list(); -} - -void MonoBottomPanel::_build_project_pressed() { - - if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) - return; // No solution to build - - String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor"); - String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player"); - - Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor); - ERR_FAIL_COND(metadata_err != OK); - - if (FileAccess::exists(scripts_metadata_path_editor)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player); - - ERR_EXPLAIN("Failed to copy scripts metadata file"); - ERR_FAIL_COND(copy_err != OK); - } - - Vector<String> godot_defines; - godot_defines.push_back(OS::get_singleton()->get_name()); - godot_defines.push_back((sizeof(void *) == 4 ? "32" : "64")); - bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools", godot_defines); - - if (build_success) { - // Notify running game for hot-reload - ScriptEditor::get_singleton()->get_debugger()->reload_scripts(); - - // Hot-reload in the editor - MonoReloadNode::get_singleton()->restart_reload_timer(); - - if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) { - CSharpLanguage::get_singleton()->reload_assemblies(false); - } - } -} - -void MonoBottomPanel::_view_log_pressed() { - - if (build_tabs_list->is_anything_selected()) { - Vector<int> selected_items = build_tabs_list->get_selected_items(); - CRASH_COND(selected_items.size() != 1); - int selected_item = selected_items[0]; - - MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_tab_control(selected_item)); - ERR_FAIL_NULL(build_tab); - - String log_dirpath = build_tab->get_build_info().get_log_dirpath(); - - OS::get_singleton()->shell_open(log_dirpath.plus_file(GodotSharpBuilds::get_msbuild_log_filename())); - } -} - -void MonoBottomPanel::_notification(int p_what) { - - switch (p_what) { - - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); - panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); - panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); - } break; - } -} - -void MonoBottomPanel::_bind_methods() { - - ClassDB::bind_method(D_METHOD("_build_project_pressed"), &MonoBottomPanel::_build_project_pressed); - ClassDB::bind_method(D_METHOD("_view_log_pressed"), &MonoBottomPanel::_view_log_pressed); - ClassDB::bind_method(D_METHOD("_warnings_toggled", "pressed"), &MonoBottomPanel::_warnings_toggled); - ClassDB::bind_method(D_METHOD("_errors_toggled", "pressed"), &MonoBottomPanel::_errors_toggled); - ClassDB::bind_method(D_METHOD("_build_tabs_item_selected", "idx"), &MonoBottomPanel::_build_tabs_item_selected); - ClassDB::bind_method(D_METHOD("_build_tabs_nothing_selected"), &MonoBottomPanel::_build_tabs_nothing_selected); -} - -MonoBottomPanel::MonoBottomPanel(EditorNode *p_editor) { - - singleton = this; - - editor = p_editor; - - set_v_size_flags(SIZE_EXPAND_FILL); - set_anchors_and_margins_preset(Control::PRESET_WIDE); - - panel_tabs = memnew(TabContainer); - panel_tabs->set_tab_align(TabContainer::ALIGN_LEFT); - panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); - panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); - panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); - panel_tabs->set_custom_minimum_size(Size2(0, 228) * EDSCALE); - panel_tabs->set_v_size_flags(SIZE_EXPAND_FILL); - add_child(panel_tabs); - - { // Builds - panel_builds_tab = memnew(VBoxContainer); - panel_builds_tab->set_name(TTR("Builds")); - panel_builds_tab->set_h_size_flags(SIZE_EXPAND_FILL); - panel_tabs->add_child(panel_builds_tab); - - HBoxContainer *toolbar_hbc = memnew(HBoxContainer); - toolbar_hbc->set_h_size_flags(SIZE_EXPAND_FILL); - panel_builds_tab->add_child(toolbar_hbc); - - Button *build_project_btn = memnew(Button); - build_project_btn->set_text(TTR("Build Project")); - build_project_btn->set_focus_mode(FOCUS_NONE); - build_project_btn->connect("pressed", this, "_build_project_pressed"); - toolbar_hbc->add_child(build_project_btn); - - toolbar_hbc->add_spacer(); - - warnings_btn = memnew(ToolButton); - warnings_btn->set_text(TTR("Warnings")); - warnings_btn->set_toggle_mode(true); - warnings_btn->set_pressed(true); - warnings_btn->set_visible(false); - warnings_btn->set_focus_mode(FOCUS_NONE); - warnings_btn->connect("toggled", this, "_warnings_toggled"); - toolbar_hbc->add_child(warnings_btn); - - errors_btn = memnew(ToolButton); - errors_btn->set_text(TTR("Errors")); - errors_btn->set_toggle_mode(true); - errors_btn->set_pressed(true); - errors_btn->set_visible(false); - errors_btn->set_focus_mode(FOCUS_NONE); - errors_btn->connect("toggled", this, "_errors_toggled"); - toolbar_hbc->add_child(errors_btn); - - toolbar_hbc->add_spacer(); - - view_log_btn = memnew(Button); - view_log_btn->set_text(TTR("View log")); - view_log_btn->set_focus_mode(FOCUS_NONE); - view_log_btn->set_visible(false); - view_log_btn->connect("pressed", this, "_view_log_pressed"); - toolbar_hbc->add_child(view_log_btn); - - HSplitContainer *hsc = memnew(HSplitContainer); - hsc->set_h_size_flags(SIZE_EXPAND_FILL); - hsc->set_v_size_flags(SIZE_EXPAND_FILL); - panel_builds_tab->add_child(hsc); - - build_tabs_list = memnew(ItemList); - build_tabs_list->set_h_size_flags(SIZE_EXPAND_FILL); - build_tabs_list->connect("item_selected", this, "_build_tabs_item_selected"); - build_tabs_list->connect("nothing_selected", this, "_build_tabs_nothing_selected"); - hsc->add_child(build_tabs_list); - - build_tabs = memnew(TabContainer); - build_tabs->set_tab_align(TabContainer::ALIGN_LEFT); - build_tabs->set_h_size_flags(SIZE_EXPAND_FILL); - build_tabs->set_tabs_visible(false); - hsc->add_child(build_tabs); - } -} - -MonoBottomPanel::~MonoBottomPanel() { - - singleton = NULL; -} - -void MonoBuildTab::_load_issues_from_file(const String &p_csv_file) { - - FileAccessRef f = FileAccess::open(p_csv_file, FileAccess::READ); - - if (!f) - return; - - while (!f->eof_reached()) { - Vector<String> csv_line = f->get_csv_line(); - - if (csv_line.size() == 1 && csv_line[0].empty()) - return; - - ERR_CONTINUE(csv_line.size() != 7); - - BuildIssue issue; - issue.warning = csv_line[0] == "warning"; - issue.file = csv_line[1]; - issue.line = csv_line[2].to_int(); - issue.column = csv_line[3].to_int(); - issue.code = csv_line[4]; - issue.message = csv_line[5]; - issue.project_file = csv_line[6]; - - if (issue.warning) - warning_count += 1; - else - error_count += 1; - - issues.push_back(issue); - } -} - -void MonoBuildTab::_update_issues_list() { - - issues_list->clear(); - - Ref<Texture> warning_icon = get_icon("Warning", "EditorIcons"); - Ref<Texture> error_icon = get_icon("Error", "EditorIcons"); - - for (int i = 0; i < issues.size(); i++) { - - const BuildIssue &issue = issues[i]; - - if (!(issue.warning ? warnings_visible : errors_visible)) - continue; - - String tooltip; - tooltip += String("Message: ") + issue.message; - - if (issue.code.length()) { - tooltip += String("\nCode: ") + issue.code; - } - - tooltip += String("\nType: ") + (issue.warning ? "warning" : "error"); - - String text; - - if (issue.file.length()) { - String sline = String::num_int64(issue.line); - String scolumn = String::num_int64(issue.column); - - text += issue.file + "("; - text += sline + ","; - text += scolumn + "): "; - - tooltip += "\nFile: " + issue.file; - tooltip += "\nLine: " + sline; - tooltip += "\nColumn: " + scolumn; - } - - if (issue.project_file.length()) { - tooltip += "\nProject: " + issue.project_file; - } - - text += issue.message; - - int line_break_idx = text.find("\n"); - issues_list->add_item(line_break_idx == -1 ? text : text.substr(0, line_break_idx), - issue.warning ? warning_icon : error_icon); - int index = issues_list->get_item_count() - 1; - issues_list->set_item_tooltip(index, tooltip); - issues_list->set_item_metadata(index, i); - } -} - -Ref<Texture> MonoBuildTab::get_icon_texture() const { - - if (build_exited) { - if (build_result == RESULT_ERROR) { - return get_icon("StatusError", "EditorIcons"); - } else { - return get_icon("StatusSuccess", "EditorIcons"); - } - } else { - return get_icon("Stop", "EditorIcons"); - } -} - -MonoBuildInfo MonoBuildTab::get_build_info() { - - return build_info; -} - -void MonoBuildTab::on_build_start() { - - build_exited = false; - - issues.clear(); - warning_count = 0; - error_count = 0; - _update_issues_list(); - - MonoBottomPanel::get_singleton()->raise_build_tab(this); -} - -void MonoBuildTab::on_build_exit(BuildResult result) { - - build_exited = true; - build_result = result; - - _load_issues_from_file(logs_dir.plus_file(GodotSharpBuilds::get_msbuild_issues_filename())); - _update_issues_list(); - - MonoBottomPanel::get_singleton()->raise_build_tab(this); -} - -void MonoBuildTab::on_build_exec_failed(const String &p_cause) { - - build_exited = true; - build_result = RESULT_ERROR; - - issues_list->clear(); - - BuildIssue issue; - issue.message = p_cause; - issue.warning = false; - - error_count += 1; - issues.push_back(issue); - - _update_issues_list(); - - MonoBottomPanel::get_singleton()->raise_build_tab(this); -} - -void MonoBuildTab::restart_build() { - - ERR_FAIL_COND(!build_exited); - GodotSharpBuilds::get_singleton()->restart_build(this); -} - -void MonoBuildTab::stop_build() { - - ERR_FAIL_COND(build_exited); - GodotSharpBuilds::get_singleton()->stop_build(this); -} - -void MonoBuildTab::_issue_activated(int p_idx) { - - ERR_FAIL_INDEX(p_idx, issues_list->get_item_count()); - - // Get correct issue idx from issue list - int issue_idx = this->issues_list->get_item_metadata(p_idx); - - ERR_FAIL_INDEX(issue_idx, issues.size()); - - const BuildIssue &issue = issues[issue_idx]; - - if (issue.project_file.empty() && issue.file.empty()) - return; - - String project_dir = issue.project_file.length() ? issue.project_file.get_base_dir() : build_info.solution.get_base_dir(); - - String file = project_dir.simplify_path().plus_file(issue.file.simplify_path()); - - if (!FileAccess::exists(file)) - return; - - file = ProjectSettings::get_singleton()->localize_path(file); - - if (file.begins_with("res://")) { - Ref<Script> script = ResourceLoader::load(file, CSharpLanguage::get_singleton()->get_type()); - - if (script.is_valid() && ScriptEditor::get_singleton()->edit(script, issue.line, issue.column)) { - EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); - } - } -} - -void MonoBuildTab::_bind_methods() { - - ClassDB::bind_method("_issue_activated", &MonoBuildTab::_issue_activated); -} - -MonoBuildTab::MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir) : - build_exited(false), - issues_list(memnew(ItemList)), - error_count(0), - warning_count(0), - errors_visible(true), - warnings_visible(true), - logs_dir(p_logs_dir), - build_info(p_build_info) { - issues_list->set_v_size_flags(SIZE_EXPAND_FILL); - issues_list->connect("item_activated", this, "_issue_activated"); - add_child(issues_list); -} diff --git a/modules/mono/editor/mono_bottom_panel.h b/modules/mono/editor/mono_bottom_panel.h deleted file mode 100644 index 9b362e51df..0000000000 --- a/modules/mono/editor/mono_bottom_panel.h +++ /dev/null @@ -1,150 +0,0 @@ -/*************************************************************************/ -/* mono_bottom_panel.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef MONO_BOTTOM_PANEL_H -#define MONO_BOTTOM_PANEL_H - -#include "editor/editor_node.h" -#include "scene/gui/control.h" - -#include "mono_build_info.h" - -class MonoBuildTab; - -class MonoBottomPanel : public VBoxContainer { - - GDCLASS(MonoBottomPanel, VBoxContainer); - - EditorNode *editor; - - TabContainer *panel_tabs; - - VBoxContainer *panel_builds_tab; - - ItemList *build_tabs_list; - TabContainer *build_tabs; - - ToolButton *warnings_btn; - ToolButton *errors_btn; - Button *view_log_btn; - - void _update_build_tabs_list(); - - void _build_tabs_item_selected(int p_idx); - void _build_tabs_nothing_selected(); - - void _warnings_toggled(bool p_pressed); - void _errors_toggled(bool p_pressed); - - void _build_project_pressed(); - void _view_log_pressed(); - - static MonoBottomPanel *singleton; - -protected: - void _notification(int p_what); - - static void _bind_methods(); - -public: - _FORCE_INLINE_ static MonoBottomPanel *get_singleton() { return singleton; } - - void add_build_tab(MonoBuildTab *p_build_tab); - void raise_build_tab(MonoBuildTab *p_build_tab); - - void show_build_tab(); - - MonoBottomPanel(EditorNode *p_editor = NULL); - ~MonoBottomPanel(); -}; - -class MonoBuildTab : public VBoxContainer { - - GDCLASS(MonoBuildTab, VBoxContainer); - -public: - enum BuildResult { - RESULT_ERROR, - RESULT_SUCCESS - }; - - struct BuildIssue { - bool warning; - String file; - int line; - int column; - String code; - String message; - String project_file; - }; - -private: - friend class MonoBottomPanel; - - bool build_exited; - BuildResult build_result; - - Vector<BuildIssue> issues; - ItemList *issues_list; - - int error_count; - int warning_count; - - bool errors_visible; - bool warnings_visible; - - String logs_dir; - - MonoBuildInfo build_info; - - void _load_issues_from_file(const String &p_csv_file); - void _update_issues_list(); - - void _issue_activated(int p_idx); - -protected: - static void _bind_methods(); - -public: - Ref<Texture> get_icon_texture() const; - - MonoBuildInfo get_build_info(); - - void on_build_start(); - void on_build_exit(BuildResult result); - void on_build_exec_failed(const String &p_cause); - - void restart_build(); - void stop_build(); - - MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir); -}; - -#endif // MONO_BOTTOM_PANEL_H diff --git a/modules/mono/editor/mono_build_info.cpp b/modules/mono/editor/mono_build_info.cpp deleted file mode 100644 index b386c06435..0000000000 --- a/modules/mono/editor/mono_build_info.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/*************************************************************************/ -/* mono_build_info.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "mono_build_info.h" - -#include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_utils.h" - -uint32_t MonoBuildInfo::Hasher::hash(const MonoBuildInfo &p_key) { - - uint32_t hash = 0; - - GDMonoUtils::hash_combine(hash, p_key.solution.hash()); - GDMonoUtils::hash_combine(hash, p_key.configuration.hash()); - - return hash; -} - -bool MonoBuildInfo::operator==(const MonoBuildInfo &p_b) const { - - return p_b.solution == solution && p_b.configuration == configuration; -} - -String MonoBuildInfo::get_log_dirpath() { - - return GodotSharpDirs::get_build_logs_dir().plus_file(solution.md5_text() + "_" + configuration); -} - -MonoBuildInfo::MonoBuildInfo() {} - -MonoBuildInfo::MonoBuildInfo(const String &p_solution, const String &p_config) { - - solution = p_solution; - configuration = p_config; -} diff --git a/modules/mono/editor/monodevelop_instance.cpp b/modules/mono/editor/monodevelop_instance.cpp deleted file mode 100644 index 3caa56d1d0..0000000000 --- a/modules/mono/editor/monodevelop_instance.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************/ -/* monodevelop_instance.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "monodevelop_instance.h" - -#include "../mono_gd/gd_mono.h" -#include "../mono_gd/gd_mono_class.h" - -void MonoDevelopInstance::execute(const Vector<String> &p_files) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - ERR_FAIL_NULL(execute_method); - ERR_FAIL_COND(gc_handle.is_null()); - - MonoException *exc = NULL; - - Variant files = p_files; - const Variant *args[1] = { &files }; - execute_method->invoke(gc_handle->get_target(), args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL(); - } -} - -void MonoDevelopInstance::execute(const String &p_file) { - - Vector<String> files; - files.push_back(p_file); - execute(files); -} - -MonoDevelopInstance::MonoDevelopInstance(const String &p_solution, EditorId p_editor_id) { - - _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) - - GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "MonoDevelopInstance"); - - MonoObject *obj = mono_object_new(TOOLS_DOMAIN, klass->get_mono_ptr()); - - GDMonoMethod *ctor = klass->get_method(".ctor", 2); - MonoException *exc = NULL; - - Variant solution = p_solution; - Variant editor_id = p_editor_id; - const Variant *args[2] = { &solution, &editor_id }; - ctor->invoke(obj, args, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_FAIL(); - } - - gc_handle = MonoGCHandle::create_strong(obj); - execute_method = klass->get_method("Execute", 1); -} diff --git a/modules/mono/editor/monodevelop_instance.h b/modules/mono/editor/monodevelop_instance.h deleted file mode 100644 index 3b3af9607b..0000000000 --- a/modules/mono/editor/monodevelop_instance.h +++ /dev/null @@ -1,56 +0,0 @@ -/*************************************************************************/ -/* monodevelop_instance.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef MONODEVELOP_INSTANCE_H -#define MONODEVELOP_INSTANCE_H - -#include "core/reference.h" - -#include "../mono_gc_handle.h" -#include "../mono_gd/gd_mono_method.h" - -class MonoDevelopInstance { - - Ref<MonoGCHandle> gc_handle; - GDMonoMethod *execute_method; - -public: - enum EditorId { - MONODEVELOP = 0, - VISUALSTUDIO_FOR_MAC = 1 - }; - - void execute(const Vector<String> &p_files); - void execute(const String &p_file); - - MonoDevelopInstance(const String &p_solution, EditorId p_editor_id); -}; - -#endif // MONODEVELOP_INSTANCE_H |