diff options
Diffstat (limited to 'modules/mono/editor')
42 files changed, 1481 insertions, 1052 deletions
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index 86a0a4393e..8304d9e321 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -1,13 +1,13 @@ <Project Sdk="Microsoft.Build.NoTargets/2.0.1"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> + <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <Description>MSBuild .NET Sdk for Godot projects.</Description> <Authors>Godot Engine contributors</Authors> <PackageId>Godot.NET.Sdk</PackageId> <Version>4.0.0</Version> - <PackageVersion>4.0.0-dev2</PackageVersion> <PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl> <PackageType>MSBuildSdk</PackageType> <PackageTags>MSBuildSdk</PackageTags> @@ -19,7 +19,13 @@ <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn> </PropertyGroup> - <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') "> + <Target Name="ReadGodotNETSdkVersion" BeforeTargets="BeforeBuild;BeforeRebuild;CoreCompile"> + <PropertyGroup> + <PackageVersion>$([System.IO.File]::ReadAllText('$(ProjectDir)Godot.NET.Sdk_PackageVersion.txt').Trim())</PackageVersion> + </PropertyGroup> + </Target> + + <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') " DependsOnTargets="ReadGodotNETSdkVersion"> <PropertyGroup> <NuspecProperties> id=$(PackageId); @@ -32,4 +38,13 @@ </NuspecProperties> </PropertyGroup> </Target> + + <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"> + <PropertyGroup> + <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath> + <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> + </PropertyGroup> + <Copy SourceFiles="$(OutputPath)$(PackageId).$(PackageVersion).nupkg" + DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> + </Target> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec index 5b5cefe80e..ba68a4da43 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec @@ -17,6 +17,6 @@ <repository url="$projecturl$" /> </metadata> <files> - <file src="Sdk\**" target="Sdk" />\ + <file src="Sdk\**" target="Sdk" /> </files> </package> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt new file mode 100644 index 0000000000..34749489b9 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk_PackageVersion.txt @@ -0,0 +1 @@ +4.0.0-dev3 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index dfc59e6ccb..5febcf3175 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -14,15 +14,15 @@ <GodotProjectDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir> <GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir> - <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.mono\temp\'. --> - <BaseOutputPath>$(GodotProjectDir).mono\temp\bin\</BaseOutputPath> - <OutputPath>$(GodotProjectDir).mono\temp\bin\$(Configuration)\</OutputPath> + <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.godot\mono\temp\'. --> + <BaseOutputPath>$(GodotProjectDir).godot\mono\temp\bin\</BaseOutputPath> + <OutputPath>$(GodotProjectDir).godot\mono\temp\bin\$(Configuration)\</OutputPath> <!-- Use custom IntermediateOutputPath and BaseIntermediateOutputPath only if it wasn't already set. Otherwise the old values may have already been changed by MSBuild which can cause problems with NuGet. --> - <IntermediateOutputPath Condition=" '$(IntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\$(Configuration)\</IntermediateOutputPath> - <BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\</BaseIntermediateOutputPath> + <IntermediateOutputPath Condition=" '$(IntermediateOutputPath)' == '' ">$(GodotProjectDir).godot\mono\temp\obj\$(Configuration)\</IntermediateOutputPath> + <BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).godot\mono\temp\obj\</BaseIntermediateOutputPath> <!-- Do not append the target framework name to the output path. --> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> @@ -88,7 +88,7 @@ <PropertyGroup> <!-- ExportDebug also defines DEBUG like Debug does. --> <DefineConstants Condition=" '$(Configuration)' == 'ExportDebug' ">$(DefineConstants);DEBUG</DefineConstants> - <!-- Debug defines TOOLS to differenciate between Debug and ExportDebug configurations. --> + <!-- Debug defines TOOLS to differentiate between Debug and ExportDebug configurations. --> <DefineConstants Condition=" '$(Configuration)' == 'Debug' ">$(DefineConstants);TOOLS</DefineConstants> <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants> @@ -102,11 +102,11 @@ --> <Reference Include="GodotSharp"> <Private>false</Private> - <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath> + <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath> </Reference> <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' "> <Private>false</Private> - <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath> + <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath> </Reference> </ItemGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index c2549b4ad5..9b7b422276 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -15,14 +15,14 @@ namespace GodotTools.BuildLogger public void Initialize(IEventSource eventSource) { if (null == Parameters) - throw new LoggerException("Log directory was not set."); + throw new LoggerException("Log directory parameter not specified."); var parameters = Parameters.Split(new[] { ';' }); string logDir = parameters[0]; if (string.IsNullOrEmpty(logDir)) - throw new LoggerException("Log directory was not set."); + throw new LoggerException("Log directory parameter is empty."); if (parameters.Length > 1) throw new LoggerException("Too many parameters passed."); @@ -51,36 +51,46 @@ namespace GodotTools.BuildLogger { throw new LoggerException("Failed to create log file: " + ex.Message); } - else - { - // Unexpected failure - throw; - } + + // Unexpected failure + throw; } eventSource.ProjectStarted += eventSource_ProjectStarted; - eventSource.TaskStarted += eventSource_TaskStarted; + eventSource.ProjectFinished += eventSource_ProjectFinished; eventSource.MessageRaised += eventSource_MessageRaised; eventSource.WarningRaised += eventSource_WarningRaised; eventSource.ErrorRaised += eventSource_ErrorRaised; - eventSource.ProjectFinished += eventSource_ProjectFinished; } - void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) + private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) + { + WriteLine(e.Message); + indent++; + } + + private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) + { + indent--; + WriteLine(e.Message); + } + + private 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) + if (!string.IsNullOrEmpty(e.ProjectFile)) line += $" [{e.ProjectFile}]"; WriteLine(line); string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + - $@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}"; + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; issuesStreamWriter.WriteLine(errorLine); } - void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) + private void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) { string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}"; @@ -89,8 +99,9 @@ namespace GodotTools.BuildLogger 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)}"; + string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; issuesStreamWriter.WriteLine(warningLine); } @@ -106,40 +117,6 @@ namespace GodotTools.BuildLogger } } - 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) diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index 012b69032e..b217ae4bf7 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; namespace GodotTools.Core { @@ -14,14 +15,18 @@ namespace GodotTools.Core if (Path.DirectorySeparatorChar == '\\') dir = dir.Replace("/", "\\") + "\\"; - Uri fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute); - Uri relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute); + var fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute); + var relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute); - return relRoot.MakeRelativeUri(fullPath).ToString(); + // MakeRelativeUri converts spaces to %20, hence why we need UnescapeDataString + return Uri.UnescapeDataString(relRoot.MakeRelativeUri(fullPath).ToString()); } public static string NormalizePath(this string path) { + if (string.IsNullOrEmpty(path)) + return path; + bool rooted = path.IsAbsolutePath(); path = path.Replace('\\', '/'); @@ -31,7 +36,17 @@ namespace GodotTools.Core path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim(); - return rooted ? Path.DirectorySeparatorChar + path : path; + if (!rooted) + return path; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + string maybeDrive = parts[0]; + if (maybeDrive.Length == 2 && maybeDrive[1] == ':') + return path; // Already has drive letter + } + + return Path.DirectorySeparatorChar + path; } private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory); @@ -43,9 +58,9 @@ namespace GodotTools.Core path.StartsWith(DriveRoot, StringComparison.Ordinal); } - public static string ToSafeDirName(this string dirName, bool allowDirSeparator) + public static string ToSafeDirName(this string dirName, bool allowDirSeparator = false) { - var invalidChars = new List<string> { ":", "*", "?", "\"", "<", ">", "|" }; + var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"}; if (allowDirSeparator) { diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs index 572c541412..0f50c90531 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs @@ -121,7 +121,7 @@ namespace GodotTools.IdeMessaging this.messageHandler = messageHandler; this.logger = logger; - string projectMetadataDir = Path.Combine(godotProjectDir, ".mono", "metadata"); + string projectMetadataDir = Path.Combine(godotProjectDir, ".godot", "mono", "metadata"); MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName); diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs index 6f318aab4a..cc0da44a13 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs @@ -2,6 +2,7 @@ using GodotTools.Core; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; namespace GodotTools.ProjectEditor @@ -88,7 +89,7 @@ namespace GodotTools.ProjectEditor string solutionPath = Path.Combine(DirectoryPath, Name + ".sln"); string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg); - File.WriteAllText(solutionPath, content); + File.WriteAllText(solutionPath, content, Encoding.UTF8); // UTF-8 with BOM } public DotNetSolution(string name) diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 9cb50014b0..37123ba2b2 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -10,14 +10,23 @@ </ItemGroup> <ItemGroup> <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" /> + <ProjectReference Include="..\GodotTools.Shared\GodotTools.Shared.csproj" /> </ItemGroup> + <!-- + The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described + here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486 + We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when + searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed. + --> <ItemGroup> - <!-- - The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described - here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486 - We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when - searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed. - --> <None Include="MSBuild.exe" CopyToOutputDirectory="Always" /> </ItemGroup> + <Target Name="CopyMSBuildStubWindows" AfterTargets="Build" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) "> + <PropertyGroup> + <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> + <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir> + </PropertyGroup> + <!-- Need to copy it here as well on Windows --> + <Copy SourceFiles="MSBuild.exe" DestinationFiles="$(GodotOutputDataDir)\Mono\lib\mono\v4.0\MSBuild.exe" /> + </Target> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 5541876f9e..7d49d251dd 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -1,15 +1,15 @@ using System; using System.IO; +using System.Text; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using GodotTools.Shared; namespace GodotTools.ProjectEditor { public static class ProjectGenerator { - public const string GodotSdkVersionToUse = "4.0.0-dev2"; - - public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GodotSdkVersionToUse}"; + public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GeneratedGodotNupkgsVersions.GodotNETSdk}"; public static ProjectRootElement GenGameProject(string name) { @@ -41,7 +41,8 @@ namespace GodotTools.ProjectEditor var root = GenGameProject(name); - root.Save(path); + // Save (without BOM) + root.Save(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); return Guid.NewGuid().ToString().ToUpper(); } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 4041c56597..4e2c0f17cc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -61,10 +61,9 @@ namespace GodotTools.ProjectEditor if (item.ItemType != itemType) continue; - string normalizedExclude = item.Exclude.NormalizePath(); - - var glob = MSBuildGlob.Parse(normalizedExclude); + string normalizedRemove = item.Remove.NormalizePath(); + var glob = MSBuildGlob.Parse(normalizedRemove); excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile))); } diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets new file mode 100644 index 0000000000..1d382dcb43 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets @@ -0,0 +1,36 @@ +<Project> + <!-- Generate C# file with the version of all the nupkgs bundled with Godot --> + + <Target Name="SetPropertiesForGenerateGodotNupkgsVersions"> + <PropertyGroup> + <GodotNETSdkPackageVersionFile>$(SolutionDir)..\Godot.NET.Sdk\Godot.NET.Sdk\Godot.NET.Sdk_PackageVersion.txt</GodotNETSdkPackageVersionFile> + <GeneratedGodotNupkgsVersionsFile>$(IntermediateOutputPath)GodotNupkgsVersions.g.cs</GeneratedGodotNupkgsVersionsFile> + </PropertyGroup> + </Target> + + <Target Name="GenerateGodotNupkgsVersionsFile" + DependsOnTargets="PrepareForBuild;_GenerateGodotNupkgsVersionsFile" + BeforeTargets="BeforeCompile;CoreCompile"> + <ItemGroup> + <Compile Include="$(GeneratedGodotNupkgsVersionsFile)" /> + <FileWrites Include="$(GeneratedGodotNupkgsVersionsFile)" /> + </ItemGroup> + </Target> + <Target Name="_GenerateGodotNupkgsVersionsFile" + DependsOnTargets="SetPropertiesForGenerateGodotNupkgsVersions" + Inputs="$(MSBuildProjectFile);@(GodotNETSdkPackageVersionFile)" + Outputs="$(GeneratedGodotNupkgsVersionsFile)"> + <PropertyGroup> + <GenerateGodotNupkgsVersionsCode><![CDATA[ +namespace $(RootNamespace) { + public class GeneratedGodotNupkgsVersions { + public const string GodotNETSdk = "$([System.IO.File]::ReadAllText('$(GodotNETSdkPackageVersionFile)').Trim())"%3b + } +} +]]></GenerateGodotNupkgsVersionsCode> + </PropertyGroup> + <WriteLinesToFile Lines="$(GenerateGodotNupkgsVersionsCode)" + File="$(GeneratedGodotNupkgsVersionsFile)" + Overwrite="True" WriteOnlyWhenDifferent="True" /> + </Target> +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj new file mode 100644 index 0000000000..3bc1698c15 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj @@ -0,0 +1,6 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>netstandard2.0</TargetFramework> + </PropertyGroup> + <Import Project="GenerateGodotNupkgsVersions.targets" /> +</Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index ba5379e562..d3107a69db 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeMessaging", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.OpenVisualStudio", "GodotTools.OpenVisualStudio\GodotTools.OpenVisualStudio.csproj", "{EAFFF236-FA96-4A4D-BD23-0E51EF988277}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Shared", "GodotTools.Shared\GodotTools.Shared.csproj", "{2758FFAF-8237-4CF2-B569-66BF8B3587BB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,5 +45,9 @@ Global {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Debug|Any CPU.Build.0 = Debug|Any CPU {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.ActiveCfg = Release|Any CPU {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.Build.0 = Release|Any CPU + {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs deleted file mode 100644 index 3ab669a9f3..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs +++ /dev/null @@ -1,339 +0,0 @@ -using Godot; -using System; -using System.IO; -using Godot.Collections; -using GodotTools.Internals; -using static GodotTools.Internals.Globals; -using File = GodotTools.Utils.File; -using Path = System.IO.Path; - -namespace GodotTools -{ - public class BottomPanel : VBoxContainer - { - private EditorInterface editorInterface; - - private TabContainer panelTabs; - - private VBoxContainer panelBuildsTab; - - private ItemList buildTabsList; - private TabContainer buildTabs; - - private Button warningsBtn; - private Button errorsBtn; - private Button viewLogBtn; - - private void _UpdateBuildTab(int index, int? currentTab) - { - var tab = (BuildTab)buildTabs.GetChild(index); - - 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 == BuildTab.BuildResults.Success ? "Succeeded" : "Errored"; - else - itemTooltip += "Running"; - - if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error) - itemTooltip += $"\nErrors: {tab.ErrorCount}"; - - itemTooltip += $"\nWarnings: {tab.WarningCount}"; - - buildTabsList.SetItemTooltip(index, itemTooltip); - - // If this tab was already selected before the changes or if no tab was selected - if (currentTab == null || currentTab == index) - { - buildTabsList.Select(index); - _BuildTabsItemSelected(index); - } - } - - private void _UpdateBuildTabsList() - { - buildTabsList.Clear(); - - int? currentTab = buildTabs.CurrentTab; - - if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) - currentTab = null; - - for (int i = 0; i < buildTabs.GetChildCount(); i++) - _UpdateBuildTab(i, currentTab); - } - - public BuildTab GetBuildTabFor(BuildInfo buildInfo) - { - foreach (var buildTab in new Array<BuildTab>(buildTabs.GetChildren())) - { - if (buildTab.BuildInfo.Equals(buildInfo)) - return buildTab; - } - - var newBuildTab = new BuildTab(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 = (BuildTab)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 = (BuildTab)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"); - - CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); - - if (File.Exists(editorScriptsMetadataPath)) - { - try - { - File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); - } - catch (IOException e) - { - GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}"); - return; - } - } - - bool buildSuccess = BuildManager.BuildProjectBlocking("Debug"); - - if (!buildSuccess) - return; - - // Notify running game for hot-reload - Internal.EditorDebuggerNodeReloadScripts(); - - // Hot-reload in the editor - GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); - - if (Internal.IsAssembliesReloadingNeeded()) - Internal.ReloadAssemblies(softReload: false); - } - - private 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 = (BuildTab)buildTabs.GetTabControl(selectedItem); - - OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, BuildManager.MsBuildLogFileName)); - } - - public override void _Notification(int what) - { - base._Notification(what); - - if (what == EditorSettings.NotificationEditorSettingsChanged) - { - var editorBaseControl = editorInterface.GetBaseControl(); - panelTabs.AddThemeStyleboxOverride("panel", editorBaseControl.GetThemeStylebox("DebuggerPanel", "EditorStyles")); - panelTabs.AddThemeStyleboxOverride("tab_fg", editorBaseControl.GetThemeStylebox("DebuggerTabFG", "EditorStyles")); - panelTabs.AddThemeStyleboxOverride("tab_bg", editorBaseControl.GetThemeStylebox("DebuggerTabBG", "EditorStyles")); - } - } - - public void AddBuildTab(BuildTab buildTab) - { - buildTabs.AddChild(buildTab); - RaiseBuildTab(buildTab); - } - - public void RaiseBuildTab(BuildTab 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) * EditorScale, - SizeFlagsVertical = (int)SizeFlags.ExpandFill - }; - panelTabs.AddThemeStyleboxOverride("panel", editorBaseControl.GetThemeStylebox("DebuggerPanel", "EditorStyles")); - panelTabs.AddThemeStyleboxOverride("tab_fg", editorBaseControl.GetThemeStylebox("DebuggerTabFG", "EditorStyles")); - panelTabs.AddThemeStyleboxOverride("tab_bg", editorBaseControl.GetThemeStylebox("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.PressedSignal += BuildProjectPressed; - toolBarHBox.AddChild(buildProjectBtn); - - toolBarHBox.AddSpacer(begin: false); - - warningsBtn = new Button - { - Text = "Warnings".TTR(), - ToggleMode = true, - Pressed = true, - Visible = false, - FocusMode = FocusModeEnum.None - }; - warningsBtn.Toggled += _WarningsToggled; - toolBarHBox.AddChild(warningsBtn); - - errorsBtn = new Button - { - Text = "Errors".TTR(), - ToggleMode = true, - Pressed = true, - Visible = false, - FocusMode = FocusModeEnum.None - }; - errorsBtn.Toggled += _ErrorsToggled; - toolBarHBox.AddChild(errorsBtn); - - toolBarHBox.AddSpacer(begin: false); - - viewLogBtn = new Button - { - Text = "View log".TTR(), - FocusMode = FocusModeEnum.None, - Visible = false - }; - viewLogBtn.PressedSignal += _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.ItemSelected += _BuildTabsItemSelected; - buildTabsList.NothingSelected += _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/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs index ab090c46e7..51055dc9b9 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs @@ -4,7 +4,7 @@ using Godot.Collections; using GodotTools.Internals; using Path = System.IO.Path; -namespace GodotTools +namespace GodotTools.Build { [Serializable] public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization @@ -20,7 +20,9 @@ namespace GodotTools public override bool Equals(object obj) { if (obj is BuildInfo other) - return other.Solution == Solution && other.Configuration == Configuration; + return other.Solution == Solution && other.Targets == Targets && + other.Configuration == Configuration && other.Restore == Restore && + other.CustomProperties == CustomProperties && other.LogsDirPath == LogsDirPath; return false; } @@ -31,7 +33,11 @@ namespace GodotTools { int hash = 17; hash = hash * 29 + Solution.GetHashCode(); + hash = hash * 29 + Targets.GetHashCode(); hash = hash * 29 + Configuration.GetHashCode(); + hash = hash * 29 + Restore.GetHashCode(); + hash = hash * 29 + CustomProperties.GetHashCode(); + hash = hash * 29 + LogsDirPath.GetHashCode(); return hash; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 6399991b84..b96b0c8175 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -1,20 +1,18 @@ using System; -using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using GodotTools.Build; using GodotTools.Ides.Rider; using GodotTools.Internals; -using GodotTools.Utils; using JetBrains.Annotations; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; +using OS = GodotTools.Utils.OS; -namespace GodotTools +namespace GodotTools.Build { public static class BuildManager { - private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>(); + private static BuildInfo _buildInProgress; public const string PropNameMSBuildMono = "MSBuild (Mono)"; public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)"; @@ -24,6 +22,14 @@ namespace GodotTools public const string MsBuildIssuesFileName = "msbuild_issues.csv"; public const string MsBuildLogFileName = "msbuild_log.txt"; + public delegate void BuildLaunchFailedEventHandler(BuildInfo buildInfo, string reason); + + public static event BuildLaunchFailedEventHandler BuildLaunchFailed; + public static event Action<BuildInfo> BuildStarted; + public static event Action<BuildResult> BuildFinished; + public static event Action<string> StdOutputReceived; + public static event Action<string> StdErrorReceived; + private static void RemoveOldIssuesFile(BuildInfo buildInfo) { var issuesFile = GetIssuesFilePath(buildInfo); @@ -36,12 +42,13 @@ namespace GodotTools private static void ShowBuildErrorDialog(string message) { - GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error"); - GodotSharpEditor.Instance.BottomPanel.ShowBuildTab(); + var plugin = GodotSharpEditor.Instance; + plugin.ShowErrorDialog(message, "Build error"); + plugin.MakeBottomPanelItemVisible(plugin.MSBuildPanel); } - public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException(); - public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException(); + public static void RestartBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); + public static void StopBuild(BuildOutputView buildOutputView) => throw new NotImplementedException(); private static string GetLogFilePath(BuildInfo buildInfo) { @@ -61,15 +68,14 @@ namespace GodotTools public static bool Build(BuildInfo buildInfo) { - if (BuildsInProgress.Contains(buildInfo)) + if (_buildInProgress != null) throw new InvalidOperationException("A build is already in progress"); - BuildsInProgress.Add(buildInfo); + _buildInProgress = buildInfo; try { - BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); - buildTab.OnBuildStart(); + BuildStarted?.Invoke(buildInfo); // Required in order to update the build tasks list Internal.GodotMainIteration(); @@ -80,44 +86,44 @@ namespace GodotTools } catch (IOException e) { - buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); Console.Error.WriteLine(e); } try { - int exitCode = BuildSystem.Build(buildInfo); + int exitCode = BuildSystem.Build(buildInfo, StdOutputReceived, StdErrorReceived); if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); + BuildFinished?.Invoke(exitCode == 0 ? BuildResult.Success : BuildResult.Error); return exitCode == 0; } catch (Exception e) { - buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); Console.Error.WriteLine(e); return false; } } finally { - BuildsInProgress.Remove(buildInfo); + _buildInProgress = null; } } public static async Task<bool> BuildAsync(BuildInfo buildInfo) { - if (BuildsInProgress.Contains(buildInfo)) + if (_buildInProgress != null) throw new InvalidOperationException("A build is already in progress"); - BuildsInProgress.Add(buildInfo); + _buildInProgress = buildInfo; try { - BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo); + BuildStarted?.Invoke(buildInfo); try { @@ -125,43 +131,57 @@ namespace GodotTools } catch (IOException e) { - buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); Console.Error.WriteLine(e); } try { - int exitCode = await BuildSystem.BuildAsync(buildInfo); + int exitCode = await BuildSystem.BuildAsync(buildInfo, StdOutputReceived, StdErrorReceived); if (exitCode != 0) PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error); + BuildFinished?.Invoke(exitCode == 0 ? BuildResult.Success : BuildResult.Error); return exitCode == 0; } catch (Exception e) { - buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); Console.Error.WriteLine(e); return false; } } finally { - BuildsInProgress.Remove(buildInfo); + _buildInProgress = null; } } - public static bool BuildProjectBlocking(string config, [CanBeNull] string platform = null) + public static bool BuildProjectBlocking(string config, [CanBeNull] string[] targets = null, [CanBeNull] string platform = null) { - if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets ?? new[] {"Build"}, config, restore: true); + + // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it. + if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform)) + buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); + + if (Internal.GodotIsRealTDouble()) + buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); + + return BuildProjectBlocking(buildInfo); + } + + private static bool BuildProjectBlocking(BuildInfo buildInfo) + { + if (!File.Exists(buildInfo.Solution)) return true; // No solution to build // Make sure the API assemblies are up to date before building the project. // We may not have had the chance to update the release API assemblies, and the debug ones // may have been deleted by the user at some point after they were loaded by the Godot editor. - string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(config == "ExportRelease" ? "Release" : "Debug"); + string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(buildInfo.Configuration == "ExportRelease" ? "Release" : "Debug"); if (!string.IsNullOrEmpty(apiAssembliesUpdateError)) { @@ -173,15 +193,6 @@ namespace GodotTools { pr.Step("Building project solution", 0); - var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets: new[] {"Build"}, config, restore: true); - - // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it. - if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform)) - buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); - - if (Internal.GodotIsRealTDouble()) - buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); - if (!Build(buildInfo)) { ShowBuildErrorDialog("Failed to build project solution"); @@ -197,33 +208,51 @@ namespace GodotTools if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return true; // No solution to build + try + { + // Make sure our packages are added to the fallback folder + NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); + } + catch (Exception e) + { + Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); + } + + GenerateEditorScriptMetadata(); + + if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) + return true; // Requested play from an external editor/IDE which already built the project + + return BuildProjectBlocking("Debug"); + } + + // NOTE: This will be replaced with C# source generators in 4.0 + public static void GenerateEditorScriptMetadata() + { string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor"); string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player"); CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath); - if (File.Exists(editorScriptsMetadataPath)) - File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); - - var currentPlayRequest = GodotSharpEditor.Instance.CurrentPlaySettings; + if (!File.Exists(editorScriptsMetadataPath)) + return; - if (currentPlayRequest != null) + try { - if (currentPlayRequest.Value.HasDebugger) - { - // Set the environment variable that will tell the player to connect to the IDE debugger - // TODO: We should probably add a better way to do this - Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", - "--debugger-agent=transport=dt_socket" + - $",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" + - ",server=n"); - } - - if (!currentPlayRequest.Value.BuildBeforePlaying) - return true; // Requested play from an external editor/IDE which already built the project + File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath); + } + catch (IOException e) + { + throw new IOException("Failed to copy scripts metadata file.", innerException: e); } + } - return BuildProjectBlocking("Debug"); + // NOTE: This will be replaced with C# source generators in 4.0 + public static string GenerateExportedGameScriptMetadata(bool isDebug) + { + string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); + CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); + return scriptsMetadataPath; } public static void Initialize() @@ -269,8 +298,6 @@ namespace GodotTools ["hint"] = Godot.PropertyHint.Enum, ["hint_string"] = hintString }); - - EditorDef("mono/builds/print_build_output", false); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs new file mode 100644 index 0000000000..1a1639aac7 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -0,0 +1,417 @@ +using Godot; +using System; +using Godot.Collections; +using GodotTools.Internals; +using JetBrains.Annotations; +using File = GodotTools.Utils.File; +using Path = System.IO.Path; + +namespace GodotTools.Build +{ + public class BuildOutputView : VBoxContainer, ISerializationListener + { + [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; + private TextEdit buildLog; + private PopupMenu issuesListContextMenu; + + private readonly object pendingBuildLogTextLock = new object(); + [NotNull] private string pendingBuildLogText = string.Empty; + + [Signal] public event Action BuildStateChanged; + + public bool HasBuildExited { get; private set; } = false; + + public BuildResult? 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 Texture2D BuildStateIcon + { + get + { + if (!HasBuildExited) + return GetThemeIcon("Stop", "EditorIcons"); + + if (BuildResult == Build.BuildResult.Error) + return GetThemeIcon("Error", "EditorIcons"); + + if (WarningCount > 1) + return GetThemeIcon("Warning", "EditorIcons"); + + return null; + } + } + + private BuildInfo BuildInfo { get; set; } + + public bool LogVisible + { + set => buildLog.Visible = value; + } + + private void LoadIssuesFromFile(string csvFile) + { + using (var file = new Godot.File()) + { + try + { + 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 && string.IsNullOrEmpty(csvColumns[0])) + 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); + } + } + finally + { + file.Close(); // Disposing it is not enough. We need to call Close() + } + } + } + + 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)(long)issuesList.GetItemMetadata(idx); + + if (issueIndex < 0 || issueIndex >= issues.Count) + throw new IndexOutOfRangeException("Issue index out of range"); + + BuildIssue issue = issues[issueIndex]; + + if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) + 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 = GetThemeIcon("Warning", "EditorIcons")) + using (var errorIcon = GetThemeIcon("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 (!string.IsNullOrEmpty(issue.Code)) + tooltip += $"\nCode: {issue.Code}"; + + tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}"; + + string text = string.Empty; + + if (!string.IsNullOrEmpty(issue.File)) + { + text += $"{issue.File}({issue.Line},{issue.Column}): "; + + tooltip += $"\nFile: {issue.File}"; + tooltip += $"\nLine: {issue.Line}"; + tooltip += $"\nColumn: {issue.Column}"; + } + + if (!string.IsNullOrEmpty(issue.ProjectFile)) + 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); + } + } + } + + private void BuildLaunchFailed(BuildInfo buildInfo, string cause) + { + HasBuildExited = true; + BuildResult = Build.BuildResult.Error; + + issuesList.Clear(); + + var issue = new BuildIssue {Message = cause, Warning = false}; + + ErrorCount += 1; + issues.Add(issue); + + UpdateIssuesList(); + + EmitSignal(nameof(BuildStateChanged)); + } + + private void BuildStarted(BuildInfo buildInfo) + { + BuildInfo = buildInfo; + HasBuildExited = false; + + issues.Clear(); + WarningCount = 0; + ErrorCount = 0; + buildLog.Text = string.Empty; + + UpdateIssuesList(); + + EmitSignal(nameof(BuildStateChanged)); + } + + private void BuildFinished(BuildResult result) + { + HasBuildExited = true; + BuildResult = result; + + LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); + + UpdateIssuesList(); + + EmitSignal(nameof(BuildStateChanged)); + } + + private void UpdateBuildLogText() + { + lock (pendingBuildLogTextLock) + { + buildLog.Text += pendingBuildLogText; + pendingBuildLogText = string.Empty; + ScrollToLastNonEmptyLogLine(); + } + } + + private void StdOutputReceived(string text) + { + lock (pendingBuildLogTextLock) + { + if (pendingBuildLogText.Length == 0) + CallDeferred(nameof(UpdateBuildLogText)); + pendingBuildLogText += text + "\n"; + } + } + + private void StdErrorReceived(string text) + { + lock (pendingBuildLogTextLock) + { + if (pendingBuildLogText.Length == 0) + CallDeferred(nameof(UpdateBuildLogText)); + pendingBuildLogText += text + "\n"; + } + } + + private void ScrollToLastNonEmptyLogLine() + { + int line; + for (line = buildLog.GetLineCount(); line > 0; line--) + { + string lineText = buildLog.GetLine(line); + + if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim())) + break; + } + + buildLog.CursorSetLine(line); + } + + public void RestartBuild() + { + if (!HasBuildExited) + throw new InvalidOperationException("Build already started"); + + BuildManager.RestartBuild(this); + } + + public void StopBuild() + { + if (!HasBuildExited) + throw new InvalidOperationException("Build is not in progress"); + + BuildManager.StopBuild(this); + } + + private enum IssuesContextMenuOption + { + Copy + } + + private void IssuesListContextOptionPressed(int id) + { + switch ((IssuesContextMenuOption)id) + { + case IssuesContextMenuOption.Copy: + { + // We don't allow multi-selection but just in case that changes later... + string text = null; + + foreach (int issueIndex in issuesList.GetSelectedItems()) + { + if (text != null) + text += "\n"; + text += issuesList.GetItemText(issueIndex); + } + + if (text != null) + DisplayServer.ClipboardSet(text); + break; + } + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid issue context menu option"); + } + } + + private void IssuesListRmbSelected(int index, Vector2 atPosition) + { + _ = index; // Unused + + issuesListContextMenu.Clear(); + issuesListContextMenu.Size = new Vector2i(1, 1); + + if (issuesList.IsAnythingSelected()) + { + // Add menu entries for the selected item + issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"), + label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy); + } + + if (issuesListContextMenu.GetItemCount() > 0) + { + issuesListContextMenu.Position = (Vector2i)(issuesList.RectGlobalPosition + atPosition); + issuesListContextMenu.Popup(); + } + } + + public override void _Ready() + { + base._Ready(); + + SizeFlagsVertical = (int)SizeFlags.ExpandFill; + + var hsc = new HSplitContainer + { + SizeFlagsHorizontal = (int)SizeFlags.ExpandFill, + SizeFlagsVertical = (int)SizeFlags.ExpandFill + }; + AddChild(hsc); + + issuesList = new ItemList + { + SizeFlagsVertical = (int)SizeFlags.ExpandFill, + SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the build log + }; + issuesList.ItemActivated += IssueActivated; + issuesList.AllowRmbSelect = true; + issuesList.ItemRmbSelected += IssuesListRmbSelected; + hsc.AddChild(issuesList); + + issuesListContextMenu = new PopupMenu(); + issuesListContextMenu.IdPressed += IssuesListContextOptionPressed; + issuesList.AddChild(issuesListContextMenu); + + buildLog = new TextEdit + { + Readonly = true, + SizeFlagsVertical = (int)SizeFlags.ExpandFill, + SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the issues list + }; + hsc.AddChild(buildLog); + + AddBuildEventListeners(); + } + + private void AddBuildEventListeners() + { + BuildManager.BuildLaunchFailed += BuildLaunchFailed; + BuildManager.BuildStarted += BuildStarted; + BuildManager.BuildFinished += BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred + BuildManager.StdOutputReceived += StdOutputReceived; + BuildManager.StdErrorReceived += StdErrorReceived; + } + + public void OnBeforeSerialize() + { + // In case it didn't update yet. We don't want to have to serialize any pending output. + UpdateBuildLogText(); + } + + public void OnAfterDeserialize() + { + AddBuildEventListeners(); // Re-add them + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs new file mode 100644 index 0000000000..59055b60c3 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs @@ -0,0 +1,8 @@ +namespace GodotTools.Build +{ + public enum BuildResult + { + Error, + Success + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index d9862ae361..bac7a2e6db 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -44,10 +44,7 @@ namespace GodotTools.Build } } - private static bool PrintBuildOutput => - (bool)EditorSettings.GetSetting("mono/builds/print_build_output"); - - private static Process LaunchBuild(BuildInfo buildInfo) + private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) { (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild(); @@ -58,13 +55,13 @@ namespace GodotTools.Build var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs); - bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput; - - if (!redirectOutput || Godot.OS.IsStdoutVerbose()) - Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"); + string launchMessage = $"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"; + stdOutHandler?.Invoke(launchMessage); + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine(launchMessage); - startInfo.RedirectStandardOutput = redirectOutput; - startInfo.RedirectStandardError = redirectOutput; + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; startInfo.UseShellExecute = false; if (UsingMonoMsBuildOnWindows) @@ -82,20 +79,22 @@ namespace GodotTools.Build var process = new Process {StartInfo = startInfo}; + if (stdOutHandler != null) + process.OutputDataReceived += (s, e) => stdOutHandler.Invoke(e.Data); + if (stdErrHandler != null) + process.ErrorDataReceived += (s, e) => stdErrHandler.Invoke(e.Data); + process.Start(); - if (redirectOutput) - { - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); return process; } - public static int Build(BuildInfo buildInfo) + public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) { - using (var process = LaunchBuild(buildInfo)) + using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { process.WaitForExit(); @@ -103,9 +102,9 @@ namespace GodotTools.Build } } - public static async Task<int> BuildAsync(BuildInfo buildInfo) + public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) { - using (var process = LaunchBuild(buildInfo)) + using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { await process.WaitForExitAsync(); @@ -152,10 +151,5 @@ namespace GodotTools.Build 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/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs new file mode 100644 index 0000000000..708ec73454 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -0,0 +1,185 @@ +using System; +using Godot; +using GodotTools.Internals; +using JetBrains.Annotations; +using static GodotTools.Internals.Globals; +using File = GodotTools.Utils.File; + +namespace GodotTools.Build +{ + public class MSBuildPanel : VBoxContainer + { + public BuildOutputView BuildOutputView { get; private set; } + + private Button errorsBtn; + private Button warningsBtn; + private Button viewLogBtn; + + private void WarningsToggled(bool pressed) + { + BuildOutputView.WarningsVisible = pressed; + BuildOutputView.UpdateIssuesList(); + } + + private void ErrorsToggled(bool pressed) + { + BuildOutputView.ErrorsVisible = pressed; + BuildOutputView.UpdateIssuesList(); + } + + [UsedImplicitly] + public void BuildSolution() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return; // No solution to build + + try + { + // Make sure our packages are added to the fallback folder + NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); + } + catch (Exception e) + { + GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); + } + + BuildManager.GenerateEditorScriptMetadata(); + + if (!BuildManager.BuildProjectBlocking("Debug")) + return; // Build failed + + // Notify running game for hot-reload + Internal.EditorDebuggerNodeReloadScripts(); + + // Hot-reload in the editor + GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + [UsedImplicitly] + private void RebuildSolution() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return; // No solution to build + + try + { + // Make sure our packages are added to the fallback folder + NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath); + } + catch (Exception e) + { + GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); + } + + BuildManager.GenerateEditorScriptMetadata(); + + if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Rebuild"})) + return; // Build failed + + // Notify running game for hot-reload + Internal.EditorDebuggerNodeReloadScripts(); + + // Hot-reload in the editor + GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); + + if (Internal.IsAssembliesReloadingNeeded()) + Internal.ReloadAssemblies(softReload: false); + } + + [UsedImplicitly] + private void CleanSolution() + { + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + return; // No solution to build + + BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Clean"}); + } + + private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed; + + private void BuildMenuOptionPressed(int id) + { + switch ((BuildMenuOptions)id) + { + case BuildMenuOptions.BuildSolution: + BuildSolution(); + break; + case BuildMenuOptions.RebuildSolution: + RebuildSolution(); + break; + case BuildMenuOptions.CleanSolution: + CleanSolution(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option"); + } + } + + private enum BuildMenuOptions + { + BuildSolution, + RebuildSolution, + CleanSolution + } + + public override void _Ready() + { + base._Ready(); + + RectMinSize = new Vector2(0, 228) * EditorScale; + SizeFlagsVertical = (int)SizeFlags.ExpandFill; + + var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill}; + AddChild(toolBarHBox); + + var buildMenuBtn = new MenuButton {Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons")}; + toolBarHBox.AddChild(buildMenuBtn); + + var buildMenu = buildMenuBtn.GetPopup(); + buildMenu.AddItem("Build Solution".TTR(), (int)BuildMenuOptions.BuildSolution); + buildMenu.AddItem("Rebuild Solution".TTR(), (int)BuildMenuOptions.RebuildSolution); + buildMenu.AddItem("Clean Solution".TTR(), (int)BuildMenuOptions.CleanSolution); + buildMenu.IdPressed += BuildMenuOptionPressed; + + errorsBtn = new Button + { + HintTooltip = "Show Errors".TTR(), + Icon = GetThemeIcon("StatusError", "EditorIcons"), + ExpandIcon = false, + ToggleMode = true, + Pressed = true, + FocusMode = FocusModeEnum.None + }; + errorsBtn.Toggled += ErrorsToggled; + toolBarHBox.AddChild(errorsBtn); + + warningsBtn = new Button + { + HintTooltip = "Show Warnings".TTR(), + Icon = GetThemeIcon("NodeWarning", "EditorIcons"), + ExpandIcon = false, + ToggleMode = true, + Pressed = true, + FocusMode = FocusModeEnum.None + }; + warningsBtn.Toggled += WarningsToggled; + toolBarHBox.AddChild(warningsBtn); + + viewLogBtn = new Button + { + Text = "Show Output".TTR(), + ToggleMode = true, + Pressed = true, + FocusMode = FocusModeEnum.None + }; + viewLogBtn.Toggled += ViewLogToggled; + toolBarHBox.AddChild(viewLogBtn); + + BuildOutputView = new BuildOutputView(); + AddChild(BuildOutputView); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs index f36e581a5f..e2feb66e35 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs @@ -31,7 +31,7 @@ namespace GodotTools.Build string dotnetCliPath = OS.PathWhich("dotnet"); if (!string.IsNullOrEmpty(dotnetCliPath)) return (dotnetCliPath, BuildTool.DotnetCli); - GD.PushError("Cannot find dotnet CLI executable. Fallback to MSBuild from Visual Studio."); + GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Visual Studio."); goto case BuildTool.MsBuildVs; } case BuildTool.MsBuildVs: @@ -89,7 +89,7 @@ namespace GodotTools.Build string dotnetCliPath = OS.PathWhich("dotnet"); if (!string.IsNullOrEmpty(dotnetCliPath)) return (dotnetCliPath, BuildTool.DotnetCli); - GD.PushError("Cannot find dotnet CLI executable. Fallback to MSBuild from Mono."); + GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Mono."); goto case BuildTool.MsBuildMono; } case BuildTool.MsBuildMono: @@ -119,7 +119,7 @@ namespace GodotTools.Build { var result = new List<string>(); - if (OS.IsOSX) + if (OS.IsMacOS) { result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); result.Add("/usr/local/var/homebrew/linked/mono/bin/"); @@ -161,8 +161,21 @@ namespace GodotTools.Build // Try to find 15.0 with vswhere - string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)"); - vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; + var envNames = Internal.GodotIs32Bits() ? new[] {"ProgramFiles", "ProgramW6432"} : new[] {"ProgramFiles(x86)", "ProgramFiles"}; + + string vsWherePath = null; + foreach (var envName in envNames) + { + vsWherePath = Environment.GetEnvironmentVariable(envName); + if (!string.IsNullOrEmpty(vsWherePath)) + { + vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; + if (File.Exists(vsWherePath)) + break; + } + + vsWherePath = null; + } var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"}; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs new file mode 100644 index 0000000000..793ef7fd71 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs @@ -0,0 +1,296 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Xml; +using Godot; +using GodotTools.Internals; +using GodotTools.Shared; +using Directory = GodotTools.Utils.Directory; +using Environment = System.Environment; +using File = GodotTools.Utils.File; + +namespace GodotTools.Build +{ + public static class NuGetUtils + { + public const string GodotFallbackFolderName = "Godot Offline Packages"; + + public static string GodotFallbackFolderPath + => Path.Combine(GodotSharpDirs.MonoUserDir, "GodotNuGetFallbackFolder"); + + private static void AddFallbackFolderToNuGetConfig(string nuGetConfigPath, string name, string path) + { + var xmlDoc = new XmlDocument(); + xmlDoc.Load(nuGetConfigPath); + + const string nuGetConfigRootName = "configuration"; + + var rootNode = xmlDoc.DocumentElement; + + if (rootNode == null) + { + // No root node, create it + rootNode = xmlDoc.CreateElement(nuGetConfigRootName); + xmlDoc.AppendChild(rootNode); + + // Since this can be considered pretty much a new NuGet.Config, add the default nuget.org source as well + XmlElement nugetOrgSourceEntry = xmlDoc.CreateElement("add"); + nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = "nuget.org"; + nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = "https://api.nuget.org/v3/index.json"; + nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("protocolVersion")).Value = "3"; + rootNode.AppendChild(xmlDoc.CreateElement("packageSources")).AppendChild(nugetOrgSourceEntry); + } + else + { + // Check that the root node is the expected one + if (rootNode.Name != nuGetConfigRootName) + throw new Exception("Invalid root Xml node for NuGet.Config. " + + $"Expected '{nuGetConfigRootName}' got '{rootNode.Name}'."); + } + + var fallbackFoldersNode = rootNode["fallbackPackageFolders"] ?? + rootNode.AppendChild(xmlDoc.CreateElement("fallbackPackageFolders")); + + // Check if it already has our fallback package folder + for (var xmlNode = fallbackFoldersNode.FirstChild; xmlNode != null; xmlNode = xmlNode.NextSibling) + { + if (xmlNode.NodeType != XmlNodeType.Element) + continue; + + var xmlElement = (XmlElement)xmlNode; + if (xmlElement.Name == "add" && + xmlElement.Attributes["key"]?.Value == name && + xmlElement.Attributes["value"]?.Value == path) + { + return; + } + } + + XmlElement newEntry = xmlDoc.CreateElement("add"); + newEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = name; + newEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = path; + + fallbackFoldersNode.AppendChild(newEntry); + + xmlDoc.Save(nuGetConfigPath); + } + + /// <summary> + /// Returns all the paths where the user NuGet.Config files can be found. + /// Does not determine whether the returned files exist or not. + /// </summary> + private static string[] GetAllUserNuGetConfigFilePaths() + { + // Where to find 'NuGet/NuGet.Config': + // + // - Mono/.NETFramework (standalone NuGet): + // Uses Environment.SpecialFolder.ApplicationData + // - Windows: '%APPDATA%' + // - Linux/macOS: '$HOME/.config' + // - CoreCLR (dotnet CLI NuGet): + // - Windows: '%APPDATA%' + // - Linux/macOS: '$DOTNET_CLI_HOME/.nuget' otherwise '$HOME/.nuget' + + string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + + if (Utils.OS.IsWindows) + { + // %APPDATA% for both + return new[] {Path.Combine(applicationData, "NuGet", "NuGet.Config")}; + } + + var paths = new string[2]; + + // CoreCLR (dotnet CLI NuGet) + + string dotnetCliHome = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME"); + if (!string.IsNullOrEmpty(dotnetCliHome)) + { + paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "NuGet.Config"); + } + else + { + string home = Environment.GetEnvironmentVariable("HOME"); + if (string.IsNullOrEmpty(home)) + throw new InvalidOperationException("Required environment variable 'HOME' is not set."); + paths[0] = Path.Combine(home, ".nuget", "NuGet", "NuGet.Config"); + } + + // Mono/.NETFramework (standalone NuGet) + + // ApplicationData is $HOME/.config on Linux/macOS + paths[1] = Path.Combine(applicationData, "NuGet", "NuGet.Config"); + + return paths; + } + + // nupkg extraction + // + // Exclude: (NuGet.Client -> NuGet.Packaging.PackageHelper.ExcludePaths) + // package/ + // _rels/ + // [Content_Types].xml + // + // Don't ignore files that begin with a dot (.) + // + // The nuspec is not lower case inside the nupkg but must be made lower case when extracted. + + /// <summary> + /// Adds the specified fallback folder to the user NuGet.Config files, + /// for both standalone NuGet (Mono/.NETFramework) and dotnet CLI NuGet. + /// </summary> + public static void AddFallbackFolderToUserNuGetConfigs(string name, string path) + { + foreach (string nuGetConfigPath in GetAllUserNuGetConfigFilePaths()) + { + if (!System.IO.File.Exists(nuGetConfigPath)) + { + // It doesn't exist, so we create a default one + const string defaultConfig = @"<?xml version=""1.0"" encoding=""utf-8""?> +<configuration> + <packageSources> + <add key=""nuget.org"" value=""https://api.nuget.org/v3/index.json"" protocolVersion=""3"" /> + </packageSources> +</configuration> +"; + System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM + } + + AddFallbackFolderToNuGetConfig(nuGetConfigPath, name, path); + } + } + + private static void AddPackageToFallbackFolder(string fallbackFolder, + string nupkgPath, string packageId, string packageVersion) + { + // dotnet CLI provides no command for this, but we can do it manually. + // + // - The expected structure is as follows: + // fallback_folder/ + // <package.name>/<version>/ + // <package.name>.<version>.nupkg + // <package.name>.<version>.nupkg.sha512 + // <package.name>.nuspec + // ... extracted nupkg files (check code for excluded files) ... + // + // - <package.name> and <version> must be in lower case. + // - The sha512 of the nupkg is base64 encoded. + // - We can get the nuspec from the nupkg which is a Zip file. + + string packageIdLower = packageId.ToLower(); + string packageVersionLower = packageVersion.ToLower(); + + string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower); + string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg"); + string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512"); + + if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath)) + return; // Already added (for speed we don't check if every file is properly extracted) + + Directory.CreateDirectory(destDir); + + // Generate .nupkg.sha512 file + + using (var alg = SHA512.Create()) + { + alg.ComputeHash(File.ReadAllBytes(nupkgPath)); + string base64Hash = Convert.ToBase64String(alg.Hash); + File.WriteAllText(nupkgSha512DestPath, base64Hash); + } + + // Extract nupkg + ExtractNupkg(destDir, nupkgPath, packageId, packageVersion); + + // Copy .nupkg + File.Copy(nupkgPath, nupkgDestPath); + } + + private static readonly string[] NupkgExcludePaths = + { + "_rels/", + "package/", + "[Content_Types].xml" + }; + + private static void ExtractNupkg(string destDir, string nupkgPath, string packageId, string packageVersion) + { + // NOTE: Must use SimplifyGodotPath to make sure we don't extract files outside the destination directory. + + using (var archive = ZipFile.OpenRead(nupkgPath)) + { + // Extract .nuspec manually as it needs to be in lower case + + var nuspecEntry = archive.GetEntry(packageId + ".nuspec"); + + if (nuspecEntry == null) + throw new InvalidOperationException($"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file."); + + nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name.ToLower().SimplifyGodotPath())); + + // Extract the other package files + + foreach (var entry in archive.Entries) + { + // NOTE: SimplifyGodotPath() removes trailing slash and backslash, + // so we can't use the result to check if the entry is a directory. + + string entryFullName = entry.FullName.Replace('\\', '/'); + + // Check if the file must be ignored + if ( // Excluded files. + NupkgExcludePaths.Any(e => entryFullName.StartsWith(e, StringComparison.OrdinalIgnoreCase)) || + // Nupkg hash files and nupkg metadata files on all directory. + entryFullName.EndsWith(".nupkg.sha512", StringComparison.OrdinalIgnoreCase) || + entryFullName.EndsWith(".nupkg.metadata", StringComparison.OrdinalIgnoreCase) || + // Nuspec at root level. We already extracted it previously but in lower case. + entryFullName.IndexOf('/') == -1 && entryFullName.EndsWith(".nuspec")) + { + continue; + } + + string entryFullNameSimplified = entryFullName.SimplifyGodotPath(); + string destFilePath = Path.Combine(destDir, entryFullNameSimplified); + bool isDir = entryFullName.EndsWith("/"); + + if (isDir) + { + Directory.CreateDirectory(destFilePath); + } + else + { + Directory.CreateDirectory(Path.GetDirectoryName(destFilePath)); + entry.ExtractToFile(destFilePath, overwrite: true); + } + } + } + } + + /// <summary> + /// Copies and extracts all the Godot bundled packages to the Godot NuGet fallback folder. + /// Does nothing if the packages were already copied. + /// </summary> + public static void AddBundledPackagesToFallbackFolder(string fallbackFolder) + { + GD.Print("Copying Godot Offline Packages..."); + + string nupkgsLocation = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "nupkgs"); + + void AddPackage(string packageId, string packageVersion) + { + string nupkgPath = Path.Combine(nupkgsLocation, $"{packageId}.{packageVersion}.nupkg"); + AddPackageToFallbackFolder(fallbackFolder, nupkgPath, packageId, packageVersion); + } + + foreach (var (packageId, packageVersion) in PackagesToAdd) + AddPackage(packageId, packageVersion); + } + + private static readonly (string packageId, string packageVersion)[] PackagesToAdd = + { + ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk) + }; + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs deleted file mode 100644 index 8596cd24af..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs +++ /dev/null @@ -1,267 +0,0 @@ -using Godot; -using System; -using Godot.Collections; -using GodotTools.Internals; -using File = GodotTools.Utils.File; -using Path = System.IO.Path; - -namespace GodotTools -{ - public class BuildTab : 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 Texture2D IconTexture - { - get - { - if (!BuildExited) - return GetThemeIcon("Stop", "EditorIcons"); - - if (BuildResult == BuildResults.Error) - return GetThemeIcon("StatusError", "EditorIcons"); - - return GetThemeIcon("StatusSuccess", "EditorIcons"); - } - } - - public BuildInfo BuildInfo { get; private set; } - - private void _LoadIssuesFromFile(string csvFile) - { - using (var file = new Godot.File()) - { - try - { - 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 && string.IsNullOrEmpty(csvColumns[0])) - 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); - } - } - finally - { - file.Close(); // Disposing it is not enough. We need to call Close() - } - } - } - - 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)(long)issuesList.GetItemMetadata(idx); - - if (issueIndex < 0 || issueIndex >= issues.Count) - throw new IndexOutOfRangeException("Issue index out of range"); - - BuildIssue issue = issues[issueIndex]; - - if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) - 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 = GetThemeIcon("Warning", "EditorIcons")) - using (var errorIcon = GetThemeIcon("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 (!string.IsNullOrEmpty(issue.Code)) - tooltip += $"\nCode: {issue.Code}"; - - tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}"; - - string text = string.Empty; - - if (!string.IsNullOrEmpty(issue.File)) - { - text += $"{issue.File}({issue.Line},{issue.Column}): "; - - tooltip += $"\nFile: {issue.File}"; - tooltip += $"\nLine: {issue.Line}"; - tooltip += $"\nColumn: {issue.Column}"; - } - - if (!string.IsNullOrEmpty(issue.ProjectFile)) - 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.BottomPanel.RaiseBuildTab(this); - } - - public void OnBuildExit(BuildResults result) - { - BuildExited = true; - BuildResult = result; - - _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName)); - UpdateIssuesList(); - - GodotSharpEditor.Instance.BottomPanel.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.BottomPanel.RaiseBuildTab(this); - } - - public void RestartBuild() - { - if (!BuildExited) - throw new InvalidOperationException("Build already started"); - - BuildManager.RestartBuild(this); - } - - public void StopBuild() - { - if (!BuildExited) - throw new InvalidOperationException("Build is not in progress"); - - BuildManager.StopBuild(this); - } - - public override void _Ready() - { - base._Ready(); - - issuesList = new ItemList { SizeFlagsVertical = (int)SizeFlags.ExpandFill }; - issuesList.ItemActivated += _IssueActivated; - AddChild(issuesList); - } - - private BuildTab() - { - } - - public BuildTab(BuildInfo buildInfo) - { - BuildInfo = buildInfo; - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index a8afb38728..1d800b8151 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -48,7 +48,7 @@ namespace GodotTools var firstMatch = classes.FirstOrDefault(classDecl => classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object. - classDecl.SearchName != searchName // Filter by the name we're looking for + classDecl.SearchName == searchName // Filter by the name we're looking for ); if (firstMatch == null) diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index f60e469503..5bb8d444c2 100755 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -120,7 +120,7 @@ namespace GodotTools.Export string assemblyPath = assembly.Value; string outputFileExtension = platform == OS.Platforms.Windows ? ".dll" : - platform == OS.Platforms.OSX ? ".dylib" : + platform == OS.Platforms.MacOS ? ".dylib" : ".so"; string outputFileName = assemblyName + ".dll" + outputFileExtension; @@ -132,7 +132,7 @@ namespace GodotTools.Export ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir); - if (platform == OS.Platforms.OSX) + if (platform == OS.Platforms.MacOS) { exporter.AddSharedObject(tempOutputFilePath, tags: null); } @@ -328,7 +328,7 @@ MONO_AOT_MODE_LAST = 1000, if (lipoExitCode != 0) throw new Exception($"Command 'lipo' exited with code: {lipoExitCode}"); - // TODO: Add the AOT lib and interpreter libs as device only to supress warnings when targeting the simulator + // TODO: Add the AOT lib and interpreter libs as device only to suppress warnings when targeting the simulator // Add the fat AOT static library to the Xcode project exporter.AddIosProjectStaticLib(fatOutputFilePath); @@ -581,7 +581,7 @@ MONO_AOT_MODE_LAST = 1000, string arch = bits == "64" ? "x86_64" : "i686"; return $"windows-{arch}"; } - case OS.Platforms.OSX: + case OS.Platforms.MacOS: { Debug.Assert(bits == null || bits == "64"); string arch = "x86_64"; diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 554763eecb..e9bb701562 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using GodotTools.Build; using GodotTools.Core; using GodotTools.Internals; using JetBrains.Annotations; @@ -19,7 +20,7 @@ namespace GodotTools.Export public class ExportPlugin : EditorExportPlugin { [Flags] - enum I18NCodesets + enum I18NCodesets : long { None = 0, CJK = 1, @@ -143,6 +144,8 @@ namespace GodotTools.Export private void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags) { + _ = flags; // Unused + if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return; @@ -154,12 +157,10 @@ namespace GodotTools.Export string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; - string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}"); - CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath); - + string scriptsMetadataPath = BuildManager.GenerateExportedGameScriptMetadata(isDebug); AddFile(scriptsMetadataPath, scriptsMetadataPath); - if (!BuildManager.BuildProjectBlocking(buildConfig, platform)) + if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform)) throw new Exception("Failed to build project"); // Add dependency assemblies @@ -172,6 +173,8 @@ namespace GodotTools.Export assemblies[projectDllName] = projectDllSrcPath; + string bclDir = DeterminePlatformBclDir(platform); + if (platform == OS.Platforms.Android) { string godotAndroidExtProfileDir = GetBclProfileDir("godot_android_ext"); @@ -182,8 +185,49 @@ namespace GodotTools.Export assemblies["Mono.Android"] = monoAndroidAssemblyPath; } + else if (platform == OS.Platforms.HTML5) + { + // Ideally these would be added automatically since they're referenced by the wasm BCL assemblies. + // However, at least in the case of 'WebAssembly.Net.Http' for some reason the BCL assemblies + // reference a different version even though the assembly is the same, for some weird reason. - string bclDir = DeterminePlatformBclDir(platform); + var wasmFrameworkAssemblies = new[] {"WebAssembly.Bindings", "WebAssembly.Net.WebSockets"}; + + foreach (string thisWasmFrameworkAssemblyName in wasmFrameworkAssemblies) + { + string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName + ".dll"); + if (!File.Exists(thisWasmFrameworkAssemblyPath)) + throw new FileNotFoundException($"Assembly not found: '{thisWasmFrameworkAssemblyName}'", thisWasmFrameworkAssemblyPath); + assemblies[thisWasmFrameworkAssemblyName] = thisWasmFrameworkAssemblyPath; + } + + // Assemblies that can have a different name in a newer version. Newer version must come first and it has priority. + (string newName, string oldName)[] wasmFrameworkAssembliesOneOf = new[] + { + ("System.Net.Http.WebAssemblyHttpHandler", "WebAssembly.Net.Http") + }; + + foreach (var thisWasmFrameworkAssemblyName in wasmFrameworkAssembliesOneOf) + { + string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.newName + ".dll"); + if (File.Exists(thisWasmFrameworkAssemblyPath)) + { + assemblies[thisWasmFrameworkAssemblyName.newName] = thisWasmFrameworkAssemblyPath; + } + else + { + thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.oldName + ".dll"); + if (!File.Exists(thisWasmFrameworkAssemblyPath)) + { + throw new FileNotFoundException("Expected one of the following assemblies but none were found: " + + $"'{thisWasmFrameworkAssemblyName.newName}' / '{thisWasmFrameworkAssemblyName.oldName}'", + thisWasmFrameworkAssemblyPath); + } + + assemblies[thisWasmFrameworkAssemblyName.oldName] = thisWasmFrameworkAssemblyPath; + } + } + } var initialAssemblies = assemblies.Duplicate(); internal_GetExportedAssemblyDependencies(initialAssemblies, buildConfig, bclDir, assemblies); @@ -340,7 +384,7 @@ namespace GodotTools.Export private static bool PlatformHasTemplateDir(string platform) { // OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest. - return !new[] {OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform); + return !new[] {OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform); } private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform) @@ -411,7 +455,7 @@ namespace GodotTools.Export case OS.Platforms.Windows: case OS.Platforms.UWP: return "net_4_x_win"; - case OS.Platforms.OSX: + case OS.Platforms.MacOS: case OS.Platforms.LinuxBSD: case OS.Platforms.Server: case OS.Platforms.Haiku: @@ -430,7 +474,7 @@ namespace GodotTools.Export private static string DetermineDataDirNameForProject() { var appName = (string)ProjectSettings.GetSetting("application/config/name"); - string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false); + string appNameSafe = appName.ToSafeDirName(); return $"data_{appNameSafe}"; } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index a363ecc920..58561c5097 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -4,9 +4,9 @@ using GodotTools.Export; using GodotTools.Utils; using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using GodotTools.Build; using GodotTools.Ides; using GodotTools.Ides.Rider; using GodotTools.Internals; @@ -19,7 +19,6 @@ using Path = System.IO.Path; namespace GodotTools { - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] public class GodotSharpEditor : EditorPlugin, ISerializationListener { private EditorSettings editorSettings; @@ -31,20 +30,22 @@ namespace GodotTools private CheckBox aboutDialogCheckBox; private Button bottomPanelBtn; + private Button toolBarBuildButton; public GodotIdeManager GodotIdeManager { get; private set; } private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization - public BottomPanel BottomPanel { get; private set; } + public MSBuildPanel MSBuildPanel { get; private set; } - public PlaySettings? CurrentPlaySettings { get; set; } + public bool SkipBuildBeforePlaying { get; set; } = false; public static string ProjectAssemblyName { get { var projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); + projectAssemblyName = projectAssemblyName.ToSafeDirName(); if (string.IsNullOrEmpty(projectAssemblyName)) projectAssemblyName = "UnnamedProject"; return projectAssemblyName; @@ -126,6 +127,7 @@ namespace GodotTools { menuPopup.RemoveItem(menuPopup.GetItemIndex((int)MenuOptions.CreateSln)); bottomPanelBtn.Show(); + toolBarBuildButton.Show(); } private void _ShowAboutDialog() @@ -145,12 +147,27 @@ namespace GodotTools case MenuOptions.AboutCSharp: _ShowAboutDialog(); break; + case MenuOptions.SetupGodotNugetFallbackFolder: + { + try + { + string fallbackFolder = NuGetUtils.GodotFallbackFolderPath; + NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, fallbackFolder); + NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder); + } + catch (Exception e) + { + ShowErrorDialog("Failed to setup Godot NuGet Offline Packages: " + e.Message); + } + + break; + } default: throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option"); } } - private void _BuildSolutionPressed() + private void BuildSolutionPressed() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) { @@ -158,23 +175,22 @@ namespace GodotTools return; // Failed to create solution } - Instance.BottomPanel.BuildProjectPressed(); + Instance.MSBuildPanel.BuildSolution(); } - public override void _Notification(int what) + public override void _Ready() { - base._Notification(what); + base._Ready(); - if (what == NotificationReady) + MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged; + + bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start"); + if (showInfoDialog) { - bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start"); - if (showInfoDialog) - { - aboutDialog.Exclusive = 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.Exclusive = false; - } + aboutDialog.Exclusive = 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.Exclusive = false; } } @@ -182,6 +198,7 @@ namespace GodotTools { CreateSln, AboutCSharp, + SetupGodotNugetFallbackFolder, } public void ShowErrorDialog(string message, string title = "Error") @@ -269,7 +286,7 @@ namespace GodotTools bool osxAppBundleInstalled = false; - if (OS.IsOSX) + if (OS.IsMacOS) { // The package path is '/Applications/Visual Studio Code.app' const string vscodeBundleId = "com.microsoft.VSCode"; @@ -309,7 +326,7 @@ namespace GodotTools string command; - if (OS.IsOSX) + if (OS.IsMacOS) { if (!osxAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath)) { @@ -390,6 +407,12 @@ namespace GodotTools } } + private void BuildStateChanged() + { + if (bottomPanelBtn != null) + bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon; + } + public override void EnablePlugin() { base.EnablePlugin(); @@ -406,20 +429,20 @@ namespace GodotTools errorDialog = new AcceptDialog(); editorBaseControl.AddChild(errorDialog); - BottomPanel = new BottomPanel(); - - bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR()); + MSBuildPanel = new MSBuildPanel(); + bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); menuPopup = new PopupMenu(); menuPopup.Hide(); - AddToolSubmenuItem("Mono", menuPopup); + AddToolSubmenuItem("C#", menuPopup); // TODO: Remove or edit this info dialog once Mono support is no longer in alpha { menuPopup.AddItem("About C# support".TTR(), (int)MenuOptions.AboutCSharp); + menuPopup.AddItem("Setup Godot NuGet Offline Packages".TTR(), (int)MenuOptions.SetupGodotNugetFallbackFolder); aboutDialog = new AcceptDialog(); editorBaseControl.AddChild(aboutDialog); aboutDialog.Title = "Important: C# support is not feature-complete"; @@ -467,6 +490,15 @@ namespace GodotTools aboutVBox.AddChild(aboutDialogCheckBox); } + toolBarBuildButton = new Button + { + Text = "Build", + HintTooltip = "Build solution", + FocusMode = Control.FocusModeEnum.None + }; + toolBarBuildButton.PressedSignal += BuildSolutionPressed; + AddControlToContainer(CustomControlContainer.Toolbar, toolBarBuildButton); + if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { ApplyNecessaryChangesToSolution(); @@ -474,20 +506,12 @@ namespace GodotTools else { bottomPanelBtn.Hide(); + toolBarBuildButton.Hide(); menuPopup.AddItem("Create C# solution".TTR(), (int)MenuOptions.CreateSln); } menuPopup.IdPressed += _MenuOptionPressed; - var buildButton = new Button - { - Text = "Build", - HintTooltip = "Build solution", - FocusMode = Control.FocusModeEnum.None - }; - buildButton.PressedSignal += _BuildSolutionPressed; - AddControlToContainer(CustomControlContainer.Toolbar, buildButton); - // External editor settings EditorDef("mono/editor/external_editor", ExternalEditorId.None); @@ -500,7 +524,7 @@ namespace GodotTools $",Visual Studio Code:{(int)ExternalEditorId.VsCode}" + $",JetBrains Rider:{(int)ExternalEditorId.Rider}"; } - else if (OS.IsOSX) + else if (OS.IsMacOS) { settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" + $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" + @@ -528,6 +552,16 @@ namespace GodotTools exportPlugin.RegisterExportSettings(); exportPluginWeak = WeakRef(exportPlugin); + try + { + // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included + NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.GodotFallbackFolderPath); + } + catch (Exception e) + { + GD.PushError("Failed to add Godot NuGet Offline Packages to NuGet.Config: " + e.Message); + } + BuildManager.Initialize(); RiderPathManager.Initialize(); @@ -566,6 +600,7 @@ namespace GodotTools public static GodotSharpEditor Instance { get; private set; } + [UsedImplicitly] private GodotSharpEditor() { } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index e4932ca217..451ce39f5c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -111,7 +111,7 @@ namespace GodotTools.Ides { MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath) { - if (Utils.OS.IsOSX && editorId == ExternalEditorId.VisualStudioForMac) + if (Utils.OS.IsMacOS && editorId == ExternalEditorId.VisualStudioForMac) { vsForMacInstance = (vsForMacInstance?.IsDisposed ?? true ? null : vsForMacInstance) ?? new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs index 17f3339560..eb34a2d0f7 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs @@ -330,9 +330,10 @@ namespace GodotTools.Ides { DispatchToMainThread(() => { - GodotSharpEditor.Instance.CurrentPlaySettings = new PlaySettings(); + // TODO: Add BuildBeforePlaying flag to PlayRequest + + // Run the game Internal.EditorRunPlay(); - GodotSharpEditor.Instance.CurrentPlaySettings = null; }); return Task.FromResult<Response>(new PlayResponse()); } @@ -341,10 +342,22 @@ namespace GodotTools.Ides { DispatchToMainThread(() => { - GodotSharpEditor.Instance.CurrentPlaySettings = - new PlaySettings(request.DebuggerHost, request.DebuggerPort, request.BuildBeforePlaying ?? true); + // Tell the build callback whether the editor already built the solution or not + GodotSharpEditor.Instance.SkipBuildBeforePlaying = !(request.BuildBeforePlaying ?? true); + + // Pass the debugger agent settings to the player via an environment variables + // TODO: It would be better if this was an argument in EditorRunPlay instead + Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", + "--debugger-agent=transport=dt_socket" + + $",address={request.DebuggerHost}:{request.DebuggerPort}" + + ",server=n"); + + // Run the game Internal.EditorRunPlay(); - GodotSharpEditor.Instance.CurrentPlaySettings = null; + + // Restore normal settings + Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", ""); + GodotSharpEditor.Instance.SkipBuildBeforePlaying = false; }); return Task.FromResult<Response>(new DebugPlayResponse()); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index d6fa2eeba7..fd7bbd5578 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs @@ -26,7 +26,7 @@ namespace GodotTools.Ides.MonoDevelop string command; - if (OS.IsOSX) + if (OS.IsMacOS) { string bundleId = BundleIds[editorId]; @@ -85,7 +85,7 @@ namespace GodotTools.Ides.MonoDevelop public Instance(string solutionFile, EditorId editorId) { - if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX) + if (editorId == EditorId.VisualStudioForMac && !OS.IsMacOS) throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform"); this.solutionFile = solutionFile; @@ -103,7 +103,7 @@ namespace GodotTools.Ides.MonoDevelop static Instance() { - if (OS.IsOSX) + if (OS.IsMacOS) { ExecutableNames = new Dictionary<EditorId, string> { diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs index e22e9af919..94fc5da425 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs @@ -32,7 +32,7 @@ namespace GodotTools.Ides.Rider { return CollectRiderInfosWindows(); } - if (OS.IsOSX) + if (OS.IsMacOS) { return CollectRiderInfosMac(); } @@ -138,7 +138,7 @@ namespace GodotTools.Ides.Rider return GetToolboxRiderRootPath(localAppData); } - if (OS.IsOSX) + if (OS.IsMacOS) { var home = Environment.GetEnvironmentVariable("HOME"); if (string.IsNullOrEmpty(home)) @@ -211,7 +211,7 @@ namespace GodotTools.Ides.Rider { if (OS.IsWindows || OS.IsUnixLike) return "../../build.txt"; - if (OS.IsOSX) + if (OS.IsMacOS) return "Contents/Resources/build.txt"; throw new Exception("Unknown OS."); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index 6c05891f2c..e745966435 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -21,7 +21,7 @@ namespace GodotTools.Utils public static class Names { public const string Windows = "Windows"; - public const string OSX = "OSX"; + public const string MacOS = "macOS"; public const string Linux = "Linux"; public const string FreeBSD = "FreeBSD"; public const string NetBSD = "NetBSD"; @@ -37,7 +37,7 @@ namespace GodotTools.Utils public static class Platforms { public const string Windows = "windows"; - public const string OSX = "osx"; + public const string MacOS = "osx"; public const string LinuxBSD = "linuxbsd"; public const string Server = "server"; public const string UWP = "uwp"; @@ -50,7 +50,7 @@ namespace GodotTools.Utils public static readonly Dictionary<string, string> PlatformNameMap = new Dictionary<string, string> { [Names.Windows] = Platforms.Windows, - [Names.OSX] = Platforms.OSX, + [Names.MacOS] = Platforms.MacOS, [Names.Linux] = Platforms.LinuxBSD, [Names.FreeBSD] = Platforms.LinuxBSD, [Names.NetBSD] = Platforms.LinuxBSD, @@ -77,11 +77,11 @@ namespace GodotTools.Utils new[] {Names.Linux, Names.FreeBSD, Names.NetBSD, Names.BSD}; private static readonly IEnumerable<string> UnixLikePlatforms = - new[] {Names.OSX, Names.Server, Names.Haiku, Names.Android, Names.iOS} + new[] {Names.MacOS, Names.Server, Names.Haiku, Names.Android, Names.iOS} .Concat(LinuxBSDPlatforms).ToArray(); private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows)); - private static readonly Lazy<bool> _isOSX = new Lazy<bool>(() => IsOS(Names.OSX)); + private static readonly Lazy<bool> _isMacOS = new Lazy<bool>(() => IsOS(Names.MacOS)); private static readonly Lazy<bool> _isLinuxBSD = new Lazy<bool>(() => IsAnyOS(LinuxBSDPlatforms)); private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server)); private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP)); @@ -92,7 +92,7 @@ namespace GodotTools.Utils private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms)); public static bool IsWindows => _isWindows.Value || IsUWP; - public static bool IsOSX => _isOSX.Value; + public static bool IsMacOS => _isMacOS.Value; public static bool IsLinuxBSD => _isLinuxBSD.Value; public static bool IsServer => _isServer.Value; public static bool IsUWP => _isUWP.Value; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index a17c371117..ad7e5d4200 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -32,13 +32,13 @@ #if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) -#include "core/engine.h" -#include "core/global_constants.h" +#include "core/config/engine.h" +#include "core/core_constants.h" #include "core/io/compression.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/os.h" -#include "core/ucaps.h" +#include "core/string/ucaps.h" #include "../glue/cs_glue_version.gen.h" #include "../godotsharp_defs.h" @@ -97,7 +97,7 @@ #define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL "::signal_info_to_callable" #define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL "::callable_to_signal_info" -#define BINDINGS_GENERATOR_VERSION UINT32_C(11) +#define BINDINGS_GENERATOR_VERSION UINT32_C(13) const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n"); @@ -185,7 +185,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf return String(); } - DocData *doc = EditorHelp::get_doc_data(); + DocTools *doc = EditorHelp::get_doc_data(); String bbcode = p_bbcode; @@ -1368,7 +1368,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); output.append("." + ctor_method); - output.append("(this);\n" CLOSE_BLOCK_L2); + output.append("(this);\n" INDENT3 "_InitializeGodotScriptInstanceInternals();\n" CLOSE_BLOCK_L2); } else { // Hide the constructor output.append(MEMBER_BEGIN "internal "); @@ -1999,12 +1999,12 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) { #define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ { \ - output.append("\tmono_add_internal_call("); \ + output.append("\tGDMonoUtils::add_internal_call("); \ output.append("\"" BINDINGS_NAMESPACE "."); \ output.append(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \ output.append("::"); \ output.append(m_icall.name); \ - output.append("\", (void*)"); \ + output.append("\", "); \ output.append(m_icall.name); \ output.append(");\n"); \ } @@ -2426,7 +2426,7 @@ bool BindingsGenerator::_arg_default_value_is_assignable_to_type(const Variant & case Variant::VECTOR2: case Variant::RECT2: case Variant::VECTOR3: - case Variant::_RID: + case Variant::RID: case Variant::ARRAY: case Variant::DICTIONARY: case Variant::PACKED_BYTE_ARRAY: @@ -2979,7 +2979,7 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.default_argument = "new %s()"; r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; break; - case Variant::_RID: + case Variant::RID: ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false, "Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_RID) + "'."); @@ -3100,44 +3100,11 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { INSERT_INT_TYPE("sbyte", int8_t, int64_t); INSERT_INT_TYPE("short", int16_t, int64_t); INSERT_INT_TYPE("int", int32_t, int64_t); + INSERT_INT_TYPE("long", int64_t, int64_t); INSERT_INT_TYPE("byte", uint8_t, int64_t); INSERT_INT_TYPE("ushort", uint16_t, int64_t); INSERT_INT_TYPE("uint", uint32_t, int64_t); - - itype = TypeInterface::create_value_type(String("long")); - { - itype.c_out = "\treturn (%0)%1;\n"; - itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; - itype.c_out = "\t*%3 = (%0)%1;\n"; - itype.c_type = "int64_t"; - itype.c_arg_in = "&%s_in"; - } - itype.c_type_in = "int64_t*"; - itype.c_type_out = "int64_t"; - itype.im_type_in = "ref " + itype.name; - itype.im_type_out = "out " + itype.name; - itype.cs_in = "ref %0"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; - itype.ret_as_byref_arg = true; - builtin_types.insert(itype.cname, itype); - - itype = TypeInterface::create_value_type(String("ulong")); - { - itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; - itype.c_out = "\t*%3 = (%0)%1;\n"; - itype.c_type = "int64_t"; - itype.c_arg_in = "&%s_in"; - } - itype.c_type_in = "uint64_t*"; - itype.c_type_out = "uint64_t"; - itype.im_type_in = "ref " + itype.name; - itype.im_type_out = "out " + itype.name; - itype.cs_in = "ref %0"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; - itype.ret_as_byref_arg = true; - builtin_types.insert(itype.cname, itype); + INSERT_INT_TYPE("ulong", uint64_t, int64_t); } // Floating point types @@ -3149,20 +3116,16 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.proxy_name = "float"; { // The expected type for 'float' in ptrcall is 'double' - itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; - itype.c_out = "\t*%3 = (%0)%1;\n"; + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + itype.c_out = "\treturn (%0)%1;\n"; itype.c_type = "double"; - itype.c_type_in = "float*"; + itype.c_type_in = "float"; itype.c_type_out = "float"; itype.c_arg_in = "&%s_in"; } itype.cs_type = itype.proxy_name; - itype.im_type_in = "ref " + itype.proxy_name; - itype.im_type_out = "out " + itype.proxy_name; - itype.cs_in = "ref %0"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; - itype.ret_as_byref_arg = true; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; builtin_types.insert(itype.cname, itype); // double @@ -3171,20 +3134,14 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.cname = itype.name; itype.proxy_name = "double"; { - itype.c_in = "\t%0 %1_in = (%0)*%1;\n"; - itype.c_out = "\t*%3 = (%0)%1;\n"; itype.c_type = "double"; - itype.c_type_in = "double*"; + itype.c_type_in = "double"; itype.c_type_out = "double"; - itype.c_arg_in = "&%s_in"; + itype.c_arg_in = "&%s"; } itype.cs_type = itype.proxy_name; - itype.im_type_in = "ref " + itype.proxy_name; - itype.im_type_out = "out " + itype.proxy_name; - itype.cs_in = "ref %0"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; - itype.ret_as_byref_arg = true; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; builtin_types.insert(itype.cname, itype); } @@ -3399,7 +3356,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { } void BindingsGenerator::_populate_global_constants() { - int global_constants_count = GlobalConstants::get_global_constant_count(); + int global_constants_count = CoreConstants::get_global_constant_count(); if (global_constants_count > 0) { Map<String, DocData::ClassDoc>::Element *match = EditorHelp::get_doc_data()->class_list.find("@GlobalScope"); @@ -3409,7 +3366,7 @@ void BindingsGenerator::_populate_global_constants() { const DocData::ClassDoc &global_scope_doc = match->value(); for (int i = 0; i < global_constants_count; i++) { - String constant_name = GlobalConstants::get_global_constant_name(i); + String constant_name = CoreConstants::get_global_constant_name(i); const DocData::ConstantDoc *const_doc = nullptr; for (int j = 0; j < global_scope_doc.constants.size(); j++) { @@ -3421,8 +3378,8 @@ void BindingsGenerator::_populate_global_constants() { } } - int constant_value = GlobalConstants::get_global_constant_value(i); - StringName enum_name = GlobalConstants::get_global_constant_enum(i); + int constant_value = CoreConstants::get_global_constant_value(i); + StringName enum_name = CoreConstants::get_global_constant_enum(i); ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), constant_value); iconstant.const_doc = const_doc; diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index 90c1c9f3ee..0cc1bb5f46 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -31,20 +31,21 @@ #ifndef BINDINGS_GENERATOR_H #define BINDINGS_GENERATOR_H -#include "core/class_db.h" -#include "core/string_builder.h" -#include "editor/doc_data.h" +#include "core/doc_data.h" +#include "core/object/class_db.h" +#include "core/string/string_builder.h" +#include "editor/doc_tools.h" #include "editor/editor_help.h" #if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED) -#include "core/ustring.h" +#include "core/string/ustring.h" class BindingsGenerator { struct ConstantInterface { String name; String proxy_name; - int value; + int value = 0; const DocData::ConstantDoc *const_doc; ConstantInterface() {} @@ -74,7 +75,7 @@ class BindingsGenerator { struct PropertyInterface { StringName cname; String proxy_name; - int index; + int index = 0; StringName setter; StringName getter; @@ -479,7 +480,7 @@ class BindingsGenerator { String im_type_out; // Return type for the C# method declaration. Also used as companion of [unique_siq] String im_sig; // Signature for the C# method declaration String unique_sig; // Unique signature to avoid duplicates in containers - bool editor_only; + bool editor_only = false; InternalCall() {} diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp index 942c6d26a6..2d37b1306c 100644 --- a/modules/mono/editor/code_completion.cpp +++ b/modules/mono/editor/code_completion.cpp @@ -30,7 +30,7 @@ #include "code_completion.h" -#include "core/project_settings.h" +#include "core/config/project_settings.h" #include "editor/editor_file_system.h" #include "editor/editor_settings.h" #include "scene/gui/control.h" @@ -228,6 +228,18 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr } } } break; + case CompletionKind::THEME_FONT_SIZES: { + Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path()); + Node *base = _try_find_owner_node_in_tree(script); + if (base && Object::cast_to<Control>(base)) { + List<StringName> sn; + Theme::get_default()->get_font_size_list(base->get_class(), &sn); + + for (List<StringName>::Element *E = sn.front(); E; E = E->next()) { + suggestions.push_back(quoted(E->get())); + } + } + } break; case CompletionKind::THEME_STYLES: { Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path()); Node *base = _try_find_owner_node_in_tree(script); @@ -246,5 +258,4 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr return suggestions; } - } // namespace gdmono diff --git a/modules/mono/editor/code_completion.h b/modules/mono/editor/code_completion.h index 77673b766f..e38768612b 100644 --- a/modules/mono/editor/code_completion.h +++ b/modules/mono/editor/code_completion.h @@ -31,8 +31,8 @@ #ifndef CODE_COMPLETION_H #define CODE_COMPLETION_H -#include "core/ustring.h" -#include "core/variant.h" +#include "core/string/ustring.h" +#include "core/variant/variant.h" namespace gdmono { @@ -46,11 +46,11 @@ enum class CompletionKind { THEME_COLORS, THEME_CONSTANTS, THEME_FONTS, + THEME_FONT_SIZES, THEME_STYLES }; PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_script_file); - } // namespace gdmono #endif // CODE_COMPLETION_H diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 68fc372959..f9be19bbe2 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -47,7 +47,6 @@ #include "../godotsharp_dirs.h" #include "../mono_gd/gd_mono_marshal.h" #include "../utils/osx_utils.h" -#include "bindings_generator.h" #include "code_completion.h" #include "godotsharp_export.h" #include "script_class_parser.h" @@ -173,35 +172,6 @@ MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_st 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, MonoString **r_error_str) { *r_error_str = nullptr; @@ -400,75 +370,66 @@ MonoBoolean godot_icall_Utils_OS_UnixFileHasExecutableAccess(MonoString *p_file_ 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); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", godot_icall_GodotSharpDirs_ResDataDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", godot_icall_GodotSharpDirs_ResMetadataDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", godot_icall_GodotSharpDirs_ResAssembliesBaseDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", godot_icall_GodotSharpDirs_ResAssembliesDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", godot_icall_GodotSharpDirs_ResConfigDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", godot_icall_GodotSharpDirs_ResTempDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", godot_icall_GodotSharpDirs_ResTempAssembliesDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", godot_icall_GodotSharpDirs_MonoUserDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", godot_icall_GodotSharpDirs_MonoLogsDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", godot_icall_GodotSharpDirs_MonoSolutionsDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", godot_icall_GodotSharpDirs_BuildLogsDirs); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", godot_icall_GodotSharpDirs_ProjectSlnPath); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", godot_icall_GodotSharpDirs_ProjectCsProjPath); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", godot_icall_GodotSharpDirs_DataEditorToolsDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", godot_icall_GodotSharpDirs_DataMonoEtcDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", godot_icall_GodotSharpDirs_DataMonoLibDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", 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); + GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", godot_icall_EditorProgress_Create); + GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose); + GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step); // ScriptClassParser - mono_add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", (void *)godot_icall_ScriptClassParser_ParseFile); + GDMonoUtils::add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", godot_icall_ScriptClassParser_ParseFile); // ExportPlugin - mono_add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", (void *)godot_icall_ExportPlugin_GetExportedAssemblyDependencies); + GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies); // Internals - mono_add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", (void *)godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); - 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_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_EditorDebuggerNodeReloadScripts", (void *)godot_icall_Internal_EditorDebuggerNodeReloadScripts); - 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); - mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay); - mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop); - mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); - mono_add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", (void *)godot_icall_Internal_CodeCompletionRequest); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", godot_icall_Internal_FullTemplatesDir); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", godot_icall_Internal_SimplifyGodotPath); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", godot_icall_Internal_IsOsxAppBundleInstalled); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", godot_icall_Internal_GodotIs32Bits); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", godot_icall_Internal_GodotIsRealTDouble); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", godot_icall_Internal_GodotMainIteration); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", godot_icall_Internal_GetCoreApiHash); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", godot_icall_Internal_GetEditorApiHash); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", godot_icall_Internal_IsAssembliesReloadingNeeded); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", godot_icall_Internal_ReloadAssemblies); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", godot_icall_Internal_GetScriptsMetadataOrNothing); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); + GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", godot_icall_Internal_CodeCompletionRequest); // Globals - mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale); - mono_add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", (void *)godot_icall_Globals_GlobalDef); - mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", (void *)godot_icall_Globals_EditorDef); - mono_add_internal_call("GodotTools.Internals.Globals::internal_TTR", (void *)godot_icall_Globals_TTR); + GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", godot_icall_Globals_EditorScale); + GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", godot_icall_Globals_GlobalDef); + GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", godot_icall_Globals_EditorDef); + GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_TTR", godot_icall_Globals_TTR); // Utils.OS - mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName); - mono_add_internal_call("GodotTools.Utils.OS::UnixFileHasExecutableAccess", (void *)godot_icall_Utils_OS_UnixFileHasExecutableAccess); + GDMonoUtils::add_internal_call("GodotTools.Utils.OS::GetPlatformName", godot_icall_Utils_OS_GetPlatformName); + GDMonoUtils::add_internal_call("GodotTools.Utils.OS::UnixFileHasExecutableAccess", godot_icall_Utils_OS_UnixFileHasExecutableAccess); } diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index b15e9b060a..b006eed69f 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -32,9 +32,9 @@ #include <mono/metadata/image.h> +#include "core/config/project_settings.h" #include "core/io/file_access_pack.h" #include "core/os/os.h" -#include "core/project_settings.h" #include "../mono_gd/gd_mono.h" #include "../mono_gd/gd_mono_assembly.h" @@ -43,12 +43,22 @@ namespace GodotSharpExport { +MonoAssemblyName *new_mono_assembly_name() { + // Mono has no public API to create an empty MonoAssemblyName and the struct is private. + // As such the only way to create it is with a stub name and then clear it. + + MonoAssemblyName *aname = mono_assembly_name_new("stub"); + CRASH_COND(aname == nullptr); + mono_assembly_name_free(aname); // Frees the string fields, not the struct + return aname; +} + struct AssemblyRefInfo { String name; - uint16_t major; - uint16_t minor; - uint16_t build; - uint16_t revision; + uint16_t major = 0; + uint16_t minor = 0; + uint16_t build = 0; + uint16_t revision = 0; }; AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) { @@ -67,7 +77,7 @@ AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) { }; } -Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) { +Error get_assembly_dependencies(GDMonoAssembly *p_assembly, MonoAssemblyName *reusable_aname, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) { MonoImage *image = p_assembly->get_image(); for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { @@ -79,26 +89,16 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> continue; } - GDMonoAssembly *ref_assembly = nullptr; - - { - MonoAssemblyName *ref_aname = mono_assembly_name_new("A"); // We can't allocate an empty MonoAssemblyName, hence "A" - CRASH_COND(ref_aname == nullptr); - SCOPE_EXIT { - mono_assembly_name_free(ref_aname); - mono_free(ref_aname); - }; - - mono_assembly_get_assemblyref(image, i, ref_aname); + mono_assembly_get_assemblyref(image, i, reusable_aname); - if (!GDMono::get_singleton()->load_assembly(ref_name, ref_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) { - ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); - } - - r_assembly_dependencies[ref_name] = ref_assembly->get_path(); + GDMonoAssembly *ref_assembly = NULL; + if (!GDMono::get_singleton()->load_assembly(ref_name, reusable_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) { + ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); } - Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_assembly_dependencies); + r_assembly_dependencies[ref_name] = ref_assembly->get_path(); + + Error err = get_assembly_dependencies(ref_assembly, reusable_aname, p_search_dirs, r_assembly_dependencies); ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'."); } @@ -130,7 +130,10 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies, ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'."); - Error err = get_assembly_dependencies(assembly, search_dirs, r_assembly_dependencies); + MonoAssemblyName *reusable_aname = new_mono_assembly_name(); + SCOPE_EXIT { mono_free(reusable_aname); }; + + Error err = get_assembly_dependencies(assembly, reusable_aname, search_dirs, r_assembly_dependencies); if (err != OK) { return err; } @@ -138,5 +141,4 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies, return OK; } - } // namespace GodotSharpExport diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h index 9ab57755de..586d4e5a0c 100644 --- a/modules/mono/editor/godotsharp_export.h +++ b/modules/mono/editor/godotsharp_export.h @@ -31,9 +31,9 @@ #ifndef GODOTSHARP_EXPORT_H #define GODOTSHARP_EXPORT_H -#include "core/dictionary.h" -#include "core/error_list.h" -#include "core/ustring.h" +#include "core/error/error_list.h" +#include "core/string/ustring.h" +#include "core/variant/dictionary.h" #include "../mono_gd/gd_mono_header.h" @@ -43,7 +43,6 @@ Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_assembly_dependencies); - } // namespace GodotSharpExport #endif // GODOTSHARP_EXPORT_H diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp index 430c82953e..70940c279e 100644 --- a/modules/mono/editor/script_class_parser.cpp +++ b/modules/mono/editor/script_class_parser.cpp @@ -30,8 +30,8 @@ #include "script_class_parser.h" -#include "core/map.h" #include "core/os/os.h" +#include "core/templates/map.h" #include "../utils/string_utils.h" @@ -151,7 +151,7 @@ ScriptClassParser::Token ScriptClassParser::get_token() { case '"': { bool verbatim = idx != 0 && code[idx - 1] == '@'; - CharType begin_str = code[idx]; + char32_t begin_str = code[idx]; idx++; String tk_string = String(); while (true) { @@ -170,13 +170,13 @@ ScriptClassParser::Token ScriptClassParser::get_token() { } else if (code[idx] == '\\' && !verbatim) { //escaped characters... idx++; - CharType next = code[idx]; + char32_t next = code[idx]; if (next == 0) { error_str = "Unterminated String"; error = true; return TK_ERROR; } - CharType res = 0; + char32_t res = 0; switch (next) { case 'b': @@ -234,7 +234,7 @@ ScriptClassParser::Token ScriptClassParser::get_token() { if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) { //a number - const CharType *rptr; + const char32_t *rptr; double number = String::to_float(&code[idx], &rptr); idx += (rptr - &code[idx]); value = number; diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h index d611e8fb74..deb6061191 100644 --- a/modules/mono/editor/script_class_parser.h +++ b/modules/mono/editor/script_class_parser.h @@ -31,9 +31,9 @@ #ifndef SCRIPT_CLASS_PARSER_H #define SCRIPT_CLASS_PARSER_H -#include "core/ustring.h" -#include "core/variant.h" -#include "core/vector.h" +#include "core/string/ustring.h" +#include "core/templates/vector.h" +#include "core/variant/variant.h" class ScriptClassParser { public: @@ -45,22 +45,22 @@ public: }; String name; - Type type; + Type type = NAMESPACE_DECL; }; struct ClassDecl { String name; String namespace_; Vector<String> base; - bool nested; + bool nested = false; }; private: String code; - int idx; - int line; + int idx = 0; + int line = 0; String error_str; - bool error; + bool error = false; Variant value; Vector<ClassDecl> classes; |