diff --git a/.editorconfig b/.editorconfig index f335026e1e3..49517a5104d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -20,3 +20,7 @@ indent_size = 4 [.travis.yml] indent_style = space indent_size = 2 + +[*.{csproj,props,targets,nuspec}] +indent_style = space +indent_size = 2 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln new file mode 100644 index 00000000000..56c0cb77037 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal 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 new file mode 100644 index 00000000000..81a2172b911 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -0,0 +1,35 @@ + + + netstandard2.0 + + MSBuild .NET Sdk for Godot projects. + Godot Engine contributors + + Godot.NET.Sdk + 3.2.3 + 3.2.3 + https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk + MSBuildSdk + MSBuildSdk + true + + + + Godot.NET.Sdk.nuspec + $(GenerateNuspecDependsOn);SetNuSpecProperties + + + + + + id=$(PackageId); + description=$(Description); + authors=$(Authors); + version=$(PackageVersion); + packagetype=$(PackageType); + tags=$(PackageTags); + projecturl=$(PackageProjectUrl) + + + + 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 new file mode 100644 index 00000000000..5b5cefe80e5 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec @@ -0,0 +1,22 @@ + + + + $id$ + $version$ + $description$ + $authors$ + $authors$ + $projecturl$ + false + MIT + https://licenses.nuget.org/MIT + $tags$ + + + + + + + \ + + 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 new file mode 100644 index 00000000000..2422c5c2fbd --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -0,0 +1,119 @@ + + + + true + + {8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED} + + + + Debug;ExportDebug;ExportRelease + Debug + + $(SolutionDir) + $(MSBuildProjectDirectory) + $([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)')) + + + $(GodotProjectDir).mono\temp\bin\ + $(GodotProjectDir).mono\temp\bin\$(Configuration)\ + + $(GodotProjectDir).mono\temp\obj\$(Configuration)\ + $(GodotProjectDir).mono\temp\obj\ + + + false + + + + + + false + + + + + true + false + + + true + + + + Debug + Release + + + + + x11 + x11 + osx + windows + + + + false + + + + + + GODOT + + + GODOT_WINDOWS;GODOT_PC + GODOT_X11;GODOT_PC + GODOT_OSX;GODOT_MACOS;GODOT_PC + GODOT_SERVER;GODOT_PC + GODOT_UWP;GODOT_PC + GODOT_HAIKU;GODOT_PC + GODOT_ANDROID;GODOT_MOBILE + GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE + GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB + + $(GodotDefineConstants);$(GodotPlatformConstants) + + + + + $(DefineConstants);DEBUG + + $(DefineConstants);TOOLS + + $(GodotDefineConstants);$(DefineConstants) + + + + + + false + $(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll + + + false + $(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll + + + + + + true + 1.0.0 + true + + diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets new file mode 100644 index 00000000000..b9c17bad1e2 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets @@ -0,0 +1,34 @@ + + + + + true + $(GodotProjectTypeGuid);$(DefaultProjectTypeGuid) + + + + + GODOT_REAL_T_IS_DOUBLE;$(DefineConstants) + + + + + + + + + + + + + diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs b/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs index 85760a3705e..e1ccf0454a8 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs @@ -19,7 +19,10 @@ namespace GodotTools.Core } if (attempt > maxAttempts + 1) - return; + { + // Overwrite the oldest one + backupPath = backupPathBase; + } File.Copy(filePath, backupPath, overwrite: true); } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 0ca3177c78e..8ed25ccf3a2 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -7,6 +7,7 @@ + diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs index f93eb9a1faf..ed77076df3d 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs @@ -22,6 +22,37 @@ namespace GodotTools.ProjectEditor return string.Join(".", identifiers); } + /// + /// Skips invalid identifier characters including decimal digit numbers at the start of the identifier. + /// + private static void SkipInvalidCharacters(string source, int startIndex, StringBuilder outputBuilder) + { + for (int i = startIndex; i < source.Length; i++) + { + char @char = source[i]; + + switch (char.GetUnicodeCategory(@char)) + { + case UnicodeCategory.UppercaseLetter: + case UnicodeCategory.LowercaseLetter: + case UnicodeCategory.TitlecaseLetter: + case UnicodeCategory.ModifierLetter: + case UnicodeCategory.LetterNumber: + case UnicodeCategory.OtherLetter: + outputBuilder.Append(@char); + break; + case UnicodeCategory.NonSpacingMark: + case UnicodeCategory.SpacingCombiningMark: + case UnicodeCategory.ConnectorPunctuation: + case UnicodeCategory.DecimalDigitNumber: + // Identifiers may start with underscore + if (outputBuilder.Length > startIndex || @char == '_') + outputBuilder.Append(@char); + break; + } + } + } + public static string SanitizeIdentifier(string identifier, bool allowEmpty) { if (string.IsNullOrEmpty(identifier)) @@ -44,30 +75,7 @@ namespace GodotTools.ProjectEditor startIndex += 1; } - for (int i = startIndex; i < identifier.Length; i++) - { - char @char = identifier[i]; - - switch (Char.GetUnicodeCategory(@char)) - { - case UnicodeCategory.UppercaseLetter: - case UnicodeCategory.LowercaseLetter: - case UnicodeCategory.TitlecaseLetter: - case UnicodeCategory.ModifierLetter: - case UnicodeCategory.LetterNumber: - case UnicodeCategory.OtherLetter: - identifierBuilder.Append(@char); - break; - case UnicodeCategory.NonSpacingMark: - case UnicodeCategory.SpacingCombiningMark: - case UnicodeCategory.ConnectorPunctuation: - case UnicodeCategory.DecimalDigitNumber: - // Identifiers may start with underscore - if (identifierBuilder.Length > startIndex || @char == '_') - identifierBuilder.Append(@char); - break; - } - } + SkipInvalidCharacters(identifier, startIndex, identifierBuilder); if (identifierBuilder.Length == startIndex) { diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs index 14e2ca2c13a..472ad2530cc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs @@ -2,6 +2,7 @@ using GodotTools.Core; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.Build.Construction; using Microsoft.Build.Globbing; @@ -32,6 +33,7 @@ namespace GodotTools.ProjectEditor return null; } + public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false) { string normalizedInclude = Path.GetFullPath(include).NormalizePath(); @@ -113,5 +115,19 @@ namespace GodotTools.ProjectEditor return Guid.Empty; } + + public static bool AreDefaultCompileItemsEnabled(this ProjectRootElement root) + { + var enableDefaultCompileItemsProps = root.PropertyGroups + .Where(g => string.IsNullOrEmpty(g.Condition)) + .SelectMany(g => g.Properties + .Where(p => p.Name == "EnableDefaultCompileItems" && string.IsNullOrEmpty(p.Condition))); + + bool enableDefaultCompileItems = true; + foreach (var prop in enableDefaultCompileItemsProps) + enableDefaultCompileItems = prop.Value.Equals("true", StringComparison.OrdinalIgnoreCase); + + return enableDefaultCompileItems; + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 679d5bb444f..99098ca7bc0 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -1,174 +1,49 @@ -using GodotTools.Core; using System; -using System.Collections.Generic; using System.IO; -using System.Reflection; using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; namespace GodotTools.ProjectEditor { public static class ProjectGenerator { - private const string CoreApiProjectName = "GodotSharp"; - private const string EditorApiProjectName = "GodotSharpEditor"; + public const string GodotSdkVersionToUse = "3.2.3"; - public const string CSharpProjectTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"; - public const string GodotProjectTypeGuid = "{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}"; + public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GodotSdkVersionToUse}"; - public static readonly string GodotDefaultProjectTypeGuids = $"{GodotProjectTypeGuid};{CSharpProjectTypeGuid}"; - - public static string GenGameProject(string dir, string name, IEnumerable compileItems) + public static ProjectRootElement GenGameProject(string name) { - string path = Path.Combine(dir, name + ".csproj"); + if (name.Length == 0) + throw new ArgumentException("Project name is empty", nameof(name)); - ProjectPropertyGroupElement mainGroup; - var root = CreateLibraryProject(name, "Debug", out mainGroup); + var root = ProjectRootElement.Create(NewProjectFileOptions.None); - mainGroup.SetProperty("ProjectTypeGuids", GodotDefaultProjectTypeGuids); - mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)")); - mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj")); - mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)")); - mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'ExportRelease' "; - mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'ExportRelease' "; + root.Sdk = GodotSdkAttrValue; - var debugGroup = root.AddPropertyGroup(); - debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "; - debugGroup.AddProperty("DebugSymbols", "true"); - debugGroup.AddProperty("DebugType", "portable"); - debugGroup.AddProperty("Optimize", "false"); - debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;"); - debugGroup.AddProperty("ErrorReport", "prompt"); - debugGroup.AddProperty("WarningLevel", "4"); - debugGroup.AddProperty("ConsolePause", "false"); + var mainGroup = root.AddPropertyGroup(); + mainGroup.AddProperty("TargetFramework", "net472"); - var coreApiRef = root.AddItem("Reference", CoreApiProjectName); - coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll")); - coreApiRef.AddMetadata("Private", "False"); + string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true); - var editorApiRef = root.AddItem("Reference", EditorApiProjectName); - editorApiRef.Condition = " '$(Configuration)' == 'Debug' "; - editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll")); - editorApiRef.AddMetadata("Private", "False"); - - GenAssemblyInfoFile(root, dir, name); - - foreach (var item in compileItems) - { - root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\")); - } - - root.Save(path); - - return root.GetGuid().ToString().ToUpper(); - } - - private static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null) - { - string propertiesDir = Path.Combine(dir, "Properties"); - if (!Directory.Exists(propertiesDir)) - Directory.CreateDirectory(propertiesDir); - - string usingDirectivesText = string.Empty; - - if (usingDirectives != null) - { - foreach (var usingDirective in usingDirectives) - usingDirectivesText += "\nusing " + usingDirective + ";"; - } - - string assemblyLinesText = string.Empty; - - if (assemblyLines != null) - assemblyLinesText += string.Join("\n", assemblyLines) + "\n"; - - string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText); - - string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs"); - - File.WriteAllText(assemblyInfoFile, content); - - root.AddItem("Compile", assemblyInfoFile.RelativeToPath(dir).Replace("/", "\\")); - } - - public static ProjectRootElement CreateLibraryProject(string name, string defaultConfig, out ProjectPropertyGroupElement mainGroup) - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentException($"{nameof(name)} cannot be empty", nameof(name)); - - var root = ProjectRootElement.Create(); - root.DefaultTargets = "Build"; - - mainGroup = root.AddPropertyGroup(); - mainGroup.AddProperty("Configuration", defaultConfig).Condition = " '$(Configuration)' == '' "; - mainGroup.AddProperty("Platform", "AnyCPU").Condition = " '$(Platform)' == '' "; - mainGroup.AddProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}"); - mainGroup.AddProperty("OutputType", "Library"); - mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)")); - mainGroup.AddProperty("RootNamespace", IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true)); - mainGroup.AddProperty("AssemblyName", name); - mainGroup.AddProperty("TargetFrameworkVersion", "v4.7"); - mainGroup.AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString()); - - var exportDebugGroup = root.AddPropertyGroup(); - exportDebugGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportDebug|AnyCPU' "; - exportDebugGroup.AddProperty("DebugSymbols", "true"); - exportDebugGroup.AddProperty("DebugType", "portable"); - exportDebugGroup.AddProperty("Optimize", "false"); - exportDebugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;"); - exportDebugGroup.AddProperty("ErrorReport", "prompt"); - exportDebugGroup.AddProperty("WarningLevel", "4"); - exportDebugGroup.AddProperty("ConsolePause", "false"); - - var exportReleaseGroup = root.AddPropertyGroup(); - exportReleaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportRelease|AnyCPU' "; - exportReleaseGroup.AddProperty("DebugType", "portable"); - exportReleaseGroup.AddProperty("Optimize", "true"); - exportReleaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;"); - exportReleaseGroup.AddProperty("ErrorReport", "prompt"); - exportReleaseGroup.AddProperty("WarningLevel", "4"); - exportReleaseGroup.AddProperty("ConsolePause", "false"); - - // References - var referenceGroup = root.AddItemGroup(); - referenceGroup.AddItem("Reference", "System"); - var frameworkRefAssembliesItem = referenceGroup.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies"); - - // Use metadata (child nodes) instead of attributes for the PackageReference. - // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build. - frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0"); - frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All"); - - root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\")); + // If the name is not a valid namespace, manually set RootNamespace to a sanitized one. + if (sanitizedName != name) + mainGroup.AddProperty("RootNamespace", sanitizedName); return root; } - private const string AssemblyInfoTemplate = - @"using System.Reflection;{0} + public static string GenAndSaveGameProject(string dir, string name) + { + if (name.Length == 0) + throw new ArgumentException("Project name is empty", nameof(name)); -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. + string path = Path.Combine(dir, name + ".csproj"); -[assembly: AssemblyTitle(""{1}"")] -[assembly: AssemblyDescription("""")] -[assembly: AssemblyConfiguration("""")] -[assembly: AssemblyCompany("""")] -[assembly: AssemblyProduct("""")] -[assembly: AssemblyCopyright("""")] -[assembly: AssemblyTrademark("""")] -[assembly: AssemblyCulture("""")] + var root = GenGameProject(name); -// The assembly version has the format ""{{Major}}.{{Minor}}.{{Build}}.{{Revision}}"". -// The form ""{{Major}}.{{Minor}}.*"" will automatically update the build and revision, -// and ""{{Major}}.{{Minor}}.{{Build}}.*"" will update just the revision. + root.Save(path); -[assembly: AssemblyVersion(""1.0.*"")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("""")] -{2}"; + 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 8774b4ee310..5b04c8b8131 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -1,9 +1,12 @@ +using System; using GodotTools.Core; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; +using System.Xml; +using System.Xml.Linq; +using JetBrains.Annotations; using Microsoft.Build.Construction; using Microsoft.Build.Globbing; @@ -11,7 +14,7 @@ namespace GodotTools.ProjectEditor { public sealed class MSBuildProject { - public ProjectRootElement Root { get; } + internal ProjectRootElement Root { get; set; } public bool HasUnsavedChanges { get; set; } @@ -31,12 +34,20 @@ namespace GodotTools.ProjectEditor return root != null ? new MSBuildProject(root) : null; } + [PublicAPI] public static void AddItemToProjectChecked(string projectPath, string itemType, string include) { var dir = Directory.GetParent(projectPath).FullName; var root = ProjectRootElement.Open(projectPath); Debug.Assert(root != null); + if (root.AreDefaultCompileItemsEnabled()) + { + // No need to add. It's already included automatically by the MSBuild Sdk. + // This assumes the source file is inside the project directory and not manually excluded in the csproj + return; + } + var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\"); if (root.AddItemChecked(itemType, normalizedInclude)) @@ -49,6 +60,13 @@ namespace GodotTools.ProjectEditor var root = ProjectRootElement.Open(projectPath); Debug.Assert(root != null); + if (root.AreDefaultCompileItemsEnabled()) + { + // No need to add. It's already included automatically by the MSBuild Sdk. + // This assumes the source file is inside the project directory and not manually excluded in the csproj + return; + } + var normalizedOldInclude = oldInclude.NormalizePath(); var normalizedNewInclude = newInclude.NormalizePath(); @@ -66,6 +84,13 @@ namespace GodotTools.ProjectEditor var root = ProjectRootElement.Open(projectPath); Debug.Assert(root != null); + if (root.AreDefaultCompileItemsEnabled()) + { + // No need to add. It's already included automatically by the MSBuild Sdk. + // This assumes the source file is inside the project directory and not manually excluded in the csproj + return; + } + var normalizedInclude = include.NormalizePath(); if (root.RemoveItemChecked(itemType, normalizedInclude)) @@ -78,6 +103,13 @@ namespace GodotTools.ProjectEditor var root = ProjectRootElement.Open(projectPath); Debug.Assert(root != null); + if (root.AreDefaultCompileItemsEnabled()) + { + // No need to add. It's already included automatically by the MSBuild Sdk. + // This assumes the source file is inside the project directory and not manually excluded in the csproj + return; + } + bool dirty = false; var oldFolderNormalized = oldFolder.NormalizePath(); @@ -102,6 +134,13 @@ namespace GodotTools.ProjectEditor var root = ProjectRootElement.Open(projectPath); Debug.Assert(root != null); + if (root.AreDefaultCompileItemsEnabled()) + { + // No need to add. It's already included automatically by the MSBuild Sdk. + // This assumes the source file is inside the project directory and not manually excluded in the csproj + return; + } + var folderNormalized = folder.NormalizePath(); var itemsToRemove = root.FindAllItemsInFolder(itemType, folderNormalized).ToList(); @@ -136,6 +175,29 @@ namespace GodotTools.ProjectEditor var root = ProjectRootElement.Open(projectPath); Debug.Assert(root != null); + if (root.AreDefaultCompileItemsEnabled()) + { + var excluded = new List(); + result = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs").ToList(); + + foreach (var item in root.Items) + { + if (string.IsNullOrEmpty(item.Condition)) + continue; + + if (item.ItemType != itemType) + continue; + + string normalizedExclude = item.Exclude.NormalizePath(); + + var glob = MSBuildGlob.Parse(normalizedExclude); + + excluded.AddRange(result.Where(includedFile => glob.IsMatch(includedFile))); + } + + result.RemoveAll(f => excluded.Contains(f)); + } + foreach (var itemGroup in root.ItemGroups) { if (itemGroup.Condition.Length != 0) @@ -150,8 +212,6 @@ namespace GodotTools.ProjectEditor var glob = MSBuildGlob.Parse(normalizedInclude); - // TODO Check somehow if path has no blob to avoid the following loop... - foreach (var existingFile in existingFiles) { if (glob.IsMatch(existingFile)) @@ -165,222 +225,186 @@ namespace GodotTools.ProjectEditor return result.ToArray(); } - public static void EnsureHasProjectTypeGuids(MSBuildProject project) + public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName) { var root = project.Root; - bool found = root.PropertyGroups.Any(pg => - string.IsNullOrEmpty(pg.Condition) && pg.Properties.Any(p => p.Name == "ProjectTypeGuids")); - - if (found) + if (!string.IsNullOrEmpty(root.Sdk)) return; - root.AddProperty("ProjectTypeGuids", ProjectGenerator.GodotDefaultProjectTypeGuids); + root.Sdk = ProjectGenerator.GodotSdkAttrValue; + + root.ToolsVersion = null; + root.DefaultTargets = null; + + root.AddProperty("TargetFramework", "net472"); + + // Remove obsolete properties, items and elements. We're going to be conservative + // here to minimize the chances of introducing breaking changes. As such we will + // only remove elements that could potentially cause issues with the Godot.NET.Sdk. + + void RemoveElements(IEnumerable elements) + { + foreach (var element in elements) + element.Parent.RemoveChild(element); + } + + // Default Configuration + + RemoveElements(root.PropertyGroups.SelectMany(g => g.Properties) + .Where(p => p.Name == "Configuration" && p.Condition.Trim() == "'$(Configuration)' == ''" && p.Value == "Debug")); + + // Default Platform + + RemoveElements(root.PropertyGroups.SelectMany(g => g.Properties) + .Where(p => p.Name == "Platform" && p.Condition.Trim() == "'$(Platform)' == ''" && p.Value == "AnyCPU")); + + // Simple properties + + var yabaiProperties = new[] + { + "OutputPath", + "BaseIntermediateOutputPath", + "IntermediateOutputPath", + "TargetFrameworkVersion", + "ProjectTypeGuids", + "ApiConfiguration" + }; + + RemoveElements(root.PropertyGroups.SelectMany(g => g.Properties) + .Where(p => yabaiProperties.Contains(p.Name))); + + // Configuration dependent properties + + var yabaiPropertiesForConfigs = new[] + { + "DebugSymbols", + "DebugType", + "Optimize", + "DefineConstants", + "ErrorReport", + "WarningLevel", + "ConsolePause" + }; + + foreach (var config in new[] {"ExportDebug", "ExportRelease", "Debug"}) + { + var group = root.PropertyGroups + .First(g => g.Condition.Trim() == $"'$(Configuration)|$(Platform)' == '{config}|AnyCPU'"); + + RemoveElements(group.Properties.Where(p => yabaiPropertiesForConfigs.Contains(p.Name))); + + if (group.Count == 0) + { + // No more children, safe to delete the group + group.Parent.RemoveChild(group); + } + } + + // Godot API References + + var apiAssemblies = new[] {ApiAssemblyNames.Core, ApiAssemblyNames.Editor}; + + RemoveElements(root.ItemGroups.SelectMany(g => g.Items) + .Where(i => i.ItemType == "Reference" && apiAssemblies.Contains(i.Include))); + + // Microsoft.NETFramework.ReferenceAssemblies PackageReference + + RemoveElements(root.ItemGroups.SelectMany(g => g.Items).Where(i => + i.ItemType == "PackageReference" && + i.Include.Equals("Microsoft.NETFramework.ReferenceAssemblies", StringComparison.OrdinalIgnoreCase))); + + // Imports + + var yabaiImports = new[] + { + "$(MSBuildBinPath)/Microsoft.CSharp.targets", + "$(MSBuildBinPath)Microsoft.CSharp.targets" + }; + + RemoveElements(root.Imports.Where(import => yabaiImports.Contains( + import.Project.Replace("\\", "/").Replace("//", "/")))); + + // 'EnableDefaultCompileItems' and 'GenerateAssemblyInfo' are kept enabled by default + // on new projects, but when migrating old projects we disable them to avoid errors. + root.AddProperty("EnableDefaultCompileItems", "false"); + root.AddProperty("GenerateAssemblyInfo", "false"); + + // Older AssemblyInfo.cs cause the following error: + // 'Properties/AssemblyInfo.cs(19,28): error CS8357: + // The specified version string contains wildcards, which are not compatible with determinism. + // Either remove wildcards from the version string, or disable determinism for this compilation.' + // We disable deterministic builds to prevent this. The user can then fix this manually when desired + // by fixing 'AssemblyVersion("1.0.*")' to not use wildcards. + root.AddProperty("Deterministic", "false"); project.HasUnsavedChanges = true; + + var xDoc = XDocument.Parse(root.RawXml); + + if (xDoc.Root == null) + return; // Too bad, we will have to keep the xmlns/namespace and xml declaration + + XElement GetElement(XDocument doc, string name, string value, string parentName) + { + foreach (var node in doc.DescendantNodes()) + { + if (!(node is XElement element)) + continue; + if (element.Name.LocalName.Equals(name) && element.Value == value && + element.Parent != null && element.Parent.Name.LocalName.Equals(parentName)) + { + return element; + } + } + + return null; + } + + // Add comment about Microsoft.NET.Sdk properties disabled during migration + + GetElement(xDoc, name: "EnableDefaultCompileItems", value: "false", parentName: "PropertyGroup") + .AddBeforeSelf(new XComment("The following properties were overriden during migration to prevent errors.\n" + + " Enabling them may require other manual changes to the project and its files.")); + + void RemoveNamespace(XElement element) + { + element.Attributes().Where(x => x.IsNamespaceDeclaration).Remove(); + element.Name = element.Name.LocalName; + + foreach (var node in element.DescendantNodes()) + { + if (node is XElement xElement) + { + // Need to do the same for all children recursively as it adds it to them for some reason... + RemoveNamespace(xElement); + } + } + } + + // Remove xmlns/namespace + RemoveNamespace(xDoc.Root); + + // Remove xml declaration + xDoc.Nodes().FirstOrDefault(node => node.NodeType == XmlNodeType.XmlDeclaration)?.Remove(); + + string projectFullPath = root.FullPath; + + root = ProjectRootElement.Create(xDoc.CreateReader()); + root.FullPath = projectFullPath; + + project.Root = root; } - /// Simple function to make sure the Api assembly references are configured correctly - public static void FixApiHintPath(MSBuildProject project) + public static void EnsureGodotSdkIsUpToDate(MSBuildProject project) { var root = project.Root; + string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue; - void AddPropertyIfNotPresent(string name, string condition, string value) - { - if (root.PropertyGroups - .Any(g => (string.IsNullOrEmpty(g.Condition) || g.Condition.Trim() == condition) && - g.Properties - .Any(p => p.Name == name && - p.Value == value && - (p.Condition.Trim() == condition || g.Condition.Trim() == condition)))) - { - return; - } - - root.AddProperty(name, value).Condition = " " + condition + " "; - project.HasUnsavedChanges = true; - } - - AddPropertyIfNotPresent(name: "ApiConfiguration", - condition: "'$(Configuration)' != 'ExportRelease'", - value: "Debug"); - AddPropertyIfNotPresent(name: "ApiConfiguration", - condition: "'$(Configuration)' == 'ExportRelease'", - value: "Release"); - - void SetReferenceHintPath(string referenceName, string condition, string hintPath) - { - foreach (var itemGroup in root.ItemGroups.Where(g => - g.Condition.Trim() == string.Empty || g.Condition.Trim() == condition)) - { - var references = itemGroup.Items.Where(item => - item.ItemType == "Reference" && - item.Include == referenceName && - (item.Condition.Trim() == condition || itemGroup.Condition.Trim() == condition)); - - var referencesWithHintPath = references.Where(reference => - reference.Metadata.Any(m => m.Name == "HintPath")); - - if (referencesWithHintPath.Any(reference => reference.Metadata - .Any(m => m.Name == "HintPath" && m.Value == hintPath))) - { - // Found a Reference item with the right HintPath - return; - } - - var referenceWithHintPath = referencesWithHintPath.FirstOrDefault(); - if (referenceWithHintPath != null) - { - // Found a Reference item with a wrong HintPath - foreach (var metadata in referenceWithHintPath.Metadata.ToList() - .Where(m => m.Name == "HintPath")) - { - // Safe to remove as we duplicate with ToList() to loop - referenceWithHintPath.RemoveChild(metadata); - } - - referenceWithHintPath.AddMetadata("HintPath", hintPath); - project.HasUnsavedChanges = true; - return; - } - - var referenceWithoutHintPath = references.FirstOrDefault(); - if (referenceWithoutHintPath != null) - { - // Found a Reference item without a HintPath - referenceWithoutHintPath.AddMetadata("HintPath", hintPath); - project.HasUnsavedChanges = true; - return; - } - } - - // Found no Reference item at all. Add it. - root.AddItem("Reference", referenceName).Condition = " " + condition + " "; - project.HasUnsavedChanges = true; - } - - const string coreProjectName = "GodotSharp"; - const string editorProjectName = "GodotSharpEditor"; - - const string coreCondition = ""; - const string editorCondition = "'$(Configuration)' == 'Debug'"; - - var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll"; - var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll"; - - SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath); - SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath); - } - - public static void MigrateFromOldConfigNames(MSBuildProject project) - { - var root = project.Root; - - bool hasGodotProjectGeneratorVersion = false; - bool foundOldConfiguration = false; - - foreach (var propertyGroup in root.PropertyGroups.Where(g => string.IsNullOrEmpty(g.Condition))) - { - if (!hasGodotProjectGeneratorVersion && propertyGroup.Properties.Any(p => p.Name == "GodotProjectGeneratorVersion")) - hasGodotProjectGeneratorVersion = true; - - foreach (var configItem in propertyGroup.Properties - .Where(p => p.Condition.Trim() == "'$(Configuration)' == ''" && p.Value == "Tools")) - { - configItem.Value = "Debug"; - foundOldConfiguration = true; - project.HasUnsavedChanges = true; - } - } - - if (!hasGodotProjectGeneratorVersion) - { - root.PropertyGroups.First(g => string.IsNullOrEmpty(g.Condition))? - .AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString()); - project.HasUnsavedChanges = true; - } - - if (!foundOldConfiguration) - { - var toolsConditions = new[] - { - "'$(Configuration)|$(Platform)' == 'Tools|AnyCPU'", - "'$(Configuration)|$(Platform)' != 'Tools|AnyCPU'", - "'$(Configuration)' == 'Tools'", - "'$(Configuration)' != 'Tools'" - }; - - foundOldConfiguration = root.PropertyGroups - .Any(g => toolsConditions.Any(c => c == g.Condition.Trim())); - } - - if (foundOldConfiguration) - { - void MigrateConfigurationConditions(string oldConfiguration, string newConfiguration) - { - void MigrateConditions(string oldCondition, string newCondition) - { - foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition.Trim() == oldCondition)) - { - propertyGroup.Condition = " " + newCondition + " "; - project.HasUnsavedChanges = true; - } - - foreach (var propertyGroup in root.PropertyGroups) - { - foreach (var prop in propertyGroup.Properties.Where(p => p.Condition.Trim() == oldCondition)) - { - prop.Condition = " " + newCondition + " "; - project.HasUnsavedChanges = true; - } - } - - foreach (var itemGroup in root.ItemGroups.Where(g => g.Condition.Trim() == oldCondition)) - { - itemGroup.Condition = " " + newCondition + " "; - project.HasUnsavedChanges = true; - } - - foreach (var itemGroup in root.ItemGroups) - { - foreach (var item in itemGroup.Items.Where(item => item.Condition.Trim() == oldCondition)) - { - item.Condition = " " + newCondition + " "; - project.HasUnsavedChanges = true; - } - } - } - - foreach (var op in new[] {"==", "!="}) - { - MigrateConditions($"'$(Configuration)|$(Platform)' {op} '{oldConfiguration}|AnyCPU'", $"'$(Configuration)|$(Platform)' {op} '{newConfiguration}|AnyCPU'"); - MigrateConditions($"'$(Configuration)' {op} '{oldConfiguration}'", $"'$(Configuration)' {op} '{newConfiguration}'"); - } - } - - MigrateConfigurationConditions("Debug", "ExportDebug"); - MigrateConfigurationConditions("Release", "ExportRelease"); - MigrateConfigurationConditions("Tools", "Debug"); // Must be last - } - } - - public static void EnsureHasNugetNetFrameworkRefAssemblies(MSBuildProject project) - { - var root = project.Root; - - bool found = root.ItemGroups.Any(g => string.IsNullOrEmpty(g.Condition) && g.Items.Any( - item => item.ItemType == "PackageReference" && item.Include == "Microsoft.NETFramework.ReferenceAssemblies")); - - if (found) + if (!string.IsNullOrEmpty(root.Sdk) && root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase)) return; - var frameworkRefAssembliesItem = root.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies"); - - // Use metadata (child nodes) instead of attributes for the PackageReference. - // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build. - frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0"); - frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All"); - + root.Sdk = godotSdkAttrValue; project.HasUnsavedChanges = true; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs index 472498acfcf..709cae70b7d 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs @@ -24,48 +24,50 @@ namespace GodotTools private ToolButton 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; + int? currentTab = buildTabs.CurrentTab; - bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount(); + if (currentTab < 0 || currentTab >= buildTabs.GetTabCount()) + currentTab = null; for (int i = 0; i < buildTabs.GetChildCount(); i++) - { - var tab = (BuildTab)buildTabs.GetChild(i); - - if (tab == null) - continue; - - string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution); - itemName += " [" + tab.BuildInfo.Configuration + "]"; - - buildTabsList.AddItem(itemName, tab.IconTexture); - - string itemTooltip = "Solution: " + tab.BuildInfo.Solution; - itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration; - itemTooltip += "\nStatus: "; - - if (tab.BuildExited) - itemTooltip += tab.BuildResult == 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(i, itemTooltip); - - if (noCurrentTab || currentTab == i) - { - buildTabsList.Select(i); - _BuildTabsItemSelected(i); - } - } + _UpdateBuildTab(i, currentTab); } public BuildTab GetBuildTabFor(BuildInfo buildInfo) @@ -160,13 +162,7 @@ namespace GodotTools } } - var godotDefines = new[] - { - OS.GetName(), - Internal.GodotIs32Bits() ? "32" : "64" - }; - - bool buildSuccess = BuildManager.BuildProjectBlocking("Debug", godotDefines); + bool buildSuccess = BuildManager.BuildProjectBlocking("Debug"); if (!buildSuccess) return; @@ -272,7 +268,7 @@ namespace GodotTools }; panelTabs.AddChild(panelBuildsTab); - var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill }; + var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill}; panelBuildsTab.AddChild(toolBarHBox); var buildProjectBtn = new Button diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs index d63ccd28c5b..8cf43875afd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs @@ -6,6 +6,7 @@ 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; @@ -152,7 +153,7 @@ namespace GodotTools } } - public static bool BuildProjectBlocking(string config, IEnumerable godotDefines) + public static bool BuildProjectBlocking(string config, [CanBeNull] string platform = null) { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return true; // No solution to build @@ -168,29 +169,18 @@ namespace GodotTools return false; } - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool"); - using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) { pr.Step("Building project solution", 0); var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, new[] {"Build"}, config, restore: true); - bool escapeNeedsDoubleBackslash = buildTool == BuildTool.MsBuildMono || buildTool == BuildTool.DotnetCli; - - // Add Godot defines - string constants = !escapeNeedsDoubleBackslash ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\""; - - foreach (var godotDefine in godotDefines) - constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};"; + // 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()) - constants += "GODOT_REAL_T_IS_DOUBLE;"; - - constants += !escapeNeedsDoubleBackslash ? "\"" : "\\\""; - - buildInfo.CustomProperties.Add(constants); + buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); if (!Build(buildInfo)) { @@ -233,13 +223,7 @@ namespace GodotTools return true; // Requested play from an external editor/IDE which already built the project } - var godotDefines = new[] - { - Godot.OS.GetName(), - Internal.GodotIs32Bits() ? "32" : "64" - }; - - return BuildProjectBlocking("Debug", godotDefines); + return BuildProjectBlocking("Debug"); } public static void Initialize() diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs index 421729cc110..a8afb387282 100644 --- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs +++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs @@ -1,9 +1,9 @@ using Godot; using System; +using System.Linq; using Godot.Collections; using GodotTools.Internals; using GodotTools.ProjectEditor; -using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; using Directory = GodotTools.Utils.Directory; @@ -15,7 +15,7 @@ namespace GodotTools { try { - return ProjectGenerator.GenGameProject(dir, name, compileItems: new string[] { }); + return ProjectGenerator.GenAndSaveGameProject(dir, name); } catch (Exception e) { @@ -24,14 +24,6 @@ namespace GodotTools } } - public static void AddItem(string projectPath, string itemType, string include) - { - if (!(bool)GlobalDef("mono/project/auto_update_project", true)) - return; - - ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include); - } - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static ulong ConvertToTimestamp(this DateTime value) @@ -40,81 +32,77 @@ namespace GodotTools return (ulong)elapsedTime.TotalSeconds; } + private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata) + { + fileMetadata = null; + + var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr); + + if (parseError != Error.Ok) + { + GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}"); + return false; + } + + string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile); + + 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 + ); + + if (firstMatch == null) + return false; // Not found + + fileMetadata = new Dictionary + { + ["modified_time"] = $"{modifiedTime}", + ["class"] = new Dictionary + { + ["namespace"] = firstMatch.Namespace, + ["class_name"] = firstMatch.Name, + ["nested"] = firstMatch.Nested + } + }; + + return true; + } + public static void GenerateScriptsMetadata(string projectPath, string outputPath) { - if (File.Exists(outputPath)) - File.Delete(outputPath); + var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate(); - var oldDict = Internal.GetScriptsMetadataOrNothing(); - var newDict = new Godot.Collections.Dictionary(); - - foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile")) + bool IsUpToDate(string includeFile, ulong modifiedTime) { - string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath(); - - ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp(); - - if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar)) - { - var oldFileDict = (Dictionary)oldFileVar; - - if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime)) - { - if (storedModifiedTime == modifiedTime) - { - // No changes so no need to parse again - newDict[projectIncludeFile] = oldFileDict; - continue; - } - } - } - - Error parseError = ScriptClassParser.ParseFile(projectIncludeFile, out var classes, out string errorStr); - if (parseError != Error.Ok) - { - GD.PushError($"Failed to determine namespace and class for script: {projectIncludeFile}. Parse error: {errorStr ?? parseError.ToString()}"); - continue; - } - - string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile); - - var classDict = new Dictionary(); - - foreach (var classDecl in classes) - { - if (classDecl.BaseCount == 0) - continue; // Does not inherit nor implement anything, so it can't be a script class - - string classCmp = classDecl.Nested ? - classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) : - classDecl.Name; - - if (classCmp != searchName) - continue; - - classDict["namespace"] = classDecl.Namespace; - classDict["class_name"] = classDecl.Name; - classDict["nested"] = classDecl.Nested; - break; - } - - if (classDict.Count == 0) - continue; // Not found - - newDict[projectIncludeFile] = new Dictionary { ["modified_time"] = $"{modifiedTime}", ["class"] = classDict }; + return metadataDict.TryGetValue(includeFile, out var oldFileVar) && + ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string, + out ulong storedModifiedTime) && storedModifiedTime == modifiedTime; } - if (newDict.Count > 0) + var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile") + .Select(path => ("res://" + path).SimplifyGodotPath()) + .ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp()) + .Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value)) + .ToArray(); + + foreach (var pair in outdatedFiles) { - string json = JSON.Print(newDict); + metadataDict.Remove(pair.Key); - string baseDir = outputPath.GetBaseDir(); + string includeFile = pair.Key; - if (!Directory.Exists(baseDir)) - Directory.CreateDirectory(baseDir); - - File.WriteAllText(outputPath, json); + if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata)) + metadataDict[includeFile] = fileMetadata; } + + string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict); + + string baseDir = outputPath.GetBaseDir(); + + if (!Directory.Exists(baseDir)) + Directory.CreateDirectory(baseDir); + + File.WriteAllText(outputPath, json); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index 2ceb4888a23..c98bdef9426 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.CompilerServices; using GodotTools.Core; using GodotTools.Internals; +using JetBrains.Annotations; using static GodotTools.Internals.Globals; using Directory = GodotTools.Utils.Directory; using File = GodotTools.Utils.File; @@ -145,9 +146,7 @@ namespace GodotTools.Export if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return; - string platform = DeterminePlatformFromFeatures(features); - - if (platform == null) + if (!DeterminePlatformFromFeatures(features, out string platform)) throw new NotSupportedException("Target platform not supported"); string outputDir = new FileInfo(path).Directory?.FullName ?? @@ -160,10 +159,7 @@ namespace GodotTools.Export AddFile(scriptsMetadataPath, scriptsMetadataPath); - // Turn export features into defines - var godotDefines = features; - - if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines)) + if (!BuildManager.BuildProjectBlocking(buildConfig, platform)) throw new Exception("Failed to build project"); // Add dependency assemblies @@ -289,6 +285,7 @@ namespace GodotTools.Export } } + [NotNull] private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir) { string target = isDebug ? "release_debug" : "release"; @@ -343,18 +340,19 @@ 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.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform); } - private static string DeterminePlatformFromFeatures(IEnumerable features) + private static bool DeterminePlatformFromFeatures(IEnumerable features, out string platform) { foreach (var feature in features) { - if (OS.PlatformNameMap.TryGetValue(feature, out string platform)) - return platform; + if (OS.PlatformNameMap.TryGetValue(feature, out platform)) + return true; } - return null; + platform = null; + return false; } private static string GetBclProfileDir(string profile) @@ -391,7 +389,7 @@ namespace GodotTools.Export /// private static bool PlatformRequiresCustomBcl(string platform) { - if (new[] { OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform)) + if (new[] {OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform)) return true; // The 'net_4_x' BCL is not compatible between Windows and the other platforms. diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 747db262046..132cd8c477d 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -403,6 +403,37 @@ namespace GodotTools return BuildManager.EditorBuildCallback(); } + private void ApplyNecessaryChangesToSolution() + { + try + { + // Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease + DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath); + + var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath) + ?? throw new Exception("Cannot open C# project"); + + // NOTE: The order in which changes are made to the project is important + + // Migrate to MSBuild project Sdks style if using the old style + ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, ProjectAssemblyName); + + ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject); + + if (msbuildProject.HasUnsavedChanges) + { + // Save a copy of the project before replacing it + FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath); + + msbuildProject.Save(); + } + } + catch (Exception e) + { + GD.PushError(e.ToString()); + } + } + public override void EnablePlugin() { base.EnablePlugin(); @@ -478,42 +509,7 @@ namespace GodotTools if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { - try - { - // Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease - DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath); - - var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath) - ?? throw new Exception("Cannot open C# project"); - - // NOTE: The order in which changes are made to the project is important - - // Migrate csproj from old configuration names to: Debug, ExportDebug and ExportRelease - ProjectUtils.MigrateFromOldConfigNames(msbuildProject); - - // Apply the other fixes only after configurations have been migrated - - // Make sure the existing project has the ProjectTypeGuids property (for VisualStudio) - ProjectUtils.EnsureHasProjectTypeGuids(msbuildProject); - - // Make sure the existing project has Api assembly references configured correctly - ProjectUtils.FixApiHintPath(msbuildProject); - - // Make sure the existing project references the Microsoft.NETFramework.ReferenceAssemblies nuget package - ProjectUtils.EnsureHasNugetNetFrameworkRefAssemblies(msbuildProject); - - if (msbuildProject.HasUnsavedChanges) - { - // Save a copy of the project before replacing it - FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath); - - msbuildProject.Save(); - } - } - catch (Exception e) - { - GD.PushError(e.ToString()); - } + ApplyNecessaryChangesToSolution(); } else { diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs index 7fb087467f4..0f46f76b41e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs @@ -15,6 +15,10 @@ namespace GodotTools.Internals public bool Nested { get; } public int BaseCount { get; } + public string SearchName => Nested ? + Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) : + Name; + public ClassDecl(string name, string @namespace, bool nested, int baseCount) { Name = name; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 9beadb1778c..52217a3ee1f 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -45,7 +45,6 @@ #include "../mono_gd/gd_mono_marshal.h" #include "../utils/path_utils.h" #include "../utils/string_utils.h" -#include "csharp_project.h" #define CS_INDENT " " // 4 whitespaces diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 04ee3d987ae..bab3d7cb0e2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -1,38 +1,16 @@ - - + - Debug - AnyCPU {AEBF0036-DA76-4341-B651-A3F2856AB2FA} - Library bin/$(Configuration) + false Godot - GodotSharp - v4.5 + netstandard2.0 $(OutputPath)/$(AssemblyName).xml - obj + false - - true - portable - false - $(GodotDefineConstants);GODOT;DEBUG; - prompt - 4 - false + + $(DefineConstants);GODOT - - portable - true - $(GodotDefineConstants);GODOT; - prompt - 4 - false - - - - - @@ -82,5 +60,4 @@ Fortunately code completion, go to definition and such still work. --> - diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs index f84e0183f68..da6f2938710 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs @@ -1,27 +1,3 @@ -using System.Reflection; using System.Runtime.CompilerServices; -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("GodotSharp")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] [assembly: InternalsVisibleTo("GodotSharpEditor")] diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index 87859313124..03465a0d5d5 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -1,46 +1,26 @@ - - + - Debug - AnyCPU {8FBEC238-D944-4074-8548-B3B524305905} - Library bin/$(Configuration) + false Godot - GodotSharpEditor - v4.5 + netstandard2.0 $(OutputPath)/$(AssemblyName).xml - obj + false - - true - portable - false - $(GodotDefineConstants);GODOT;DEBUG; - prompt - 4 - false + + $(DefineConstants);GODOT - - portable - true - $(GodotDefineConstants);GODOT; - prompt - 4 - false - - - - - - - - - - False + false - + + diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs deleted file mode 100644 index 3684b7a3cb8..00000000000 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Reflection; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("GodotSharpEditor")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")]