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("")]