Browse Source

Merge pull request #40595 from neikeq/godot-net-sdk-and-net-standard

C#: Switch games to MSBuild Sdks and .NET Standard
Rémi Verschelde 5 years ago
parent
commit
dc456059a4
25 changed files with 454 additions and 1040 deletions
  1. 4 0
      .editorconfig
  2. 3 8
      modules/mono/csharp_script.cpp
  3. 16 0
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
  4. 35 0
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
  5. 22 0
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
  6. 112 0
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
  7. 17 0
      modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
  8. 4 1
      modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
  9. 32 24
      modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
  10. 0 118
      modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
  11. 22 147
      modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
  12. 27 314
      modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
  13. 34 38
      modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
  14. 7 23
      modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
  15. 52 64
      modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
  16. 11 13
      modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
  17. 32 66
      modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
  18. 4 0
      modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
  19. 0 1
      modules/mono/editor/bindings_generator.cpp
  20. 0 69
      modules/mono/editor/csharp_project.cpp
  21. 0 42
      modules/mono/editor/csharp_project.h
  22. 6 29
      modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
  23. 0 24
      modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
  24. 14 34
      modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
  25. 0 25
      modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs

+ 4 - 0
.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

+ 3 - 8
modules/mono/csharp_script.cpp

@@ -44,7 +44,6 @@
 
 #ifdef TOOLS_ENABLED
 #include "editor/bindings_generator.h"
-#include "editor/csharp_project.h"
 #include "editor/editor_node.h"
 #include "editor/node_dock.h"
 #endif
@@ -3724,13 +3723,9 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r
 
 #ifdef TOOLS_ENABLED
 	if (!FileAccess::exists(p_path)) {
-		// The file does not yet exists, let's assume the user just created this script
-
-		if (_create_project_solution_if_needed()) {
-			CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(),
-					"Compile",
-					ProjectSettings::get_singleton()->globalize_path(p_path));
-		} else {
+		// The file does not yet exist, let's assume the user just created this script. In such
+		// cases we need to check whether the solution and csproj were already created or not.
+		if (!_create_project_solution_if_needed()) {
 			ERR_PRINT("C# project could not be created; cannot add file: '" + p_path + "'.");
 		}
 	}

+ 16 - 0
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

+ 35 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj

@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.Build.NoTargets/2.0.1">
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+
+    <Description>MSBuild .NET Sdk for Godot projects.</Description>
+    <Authors>Godot Engine contributors</Authors>
+
+    <PackageId>Godot.NET.Sdk</PackageId>
+    <Version>4.0.0</Version>
+    <PackageVersion>4.0.0-dev2</PackageVersion>
+    <PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl>
+    <PackageType>MSBuildSdk</PackageType>
+    <PackageTags>MSBuildSdk</PackageTags>
+    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile>
+    <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn>
+  </PropertyGroup>
+
+  <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') ">
+    <PropertyGroup>
+      <NuspecProperties>
+        id=$(PackageId);
+        description=$(Description);
+        authors=$(Authors);
+        version=$(PackageVersion);
+        packagetype=$(PackageType);
+        tags=$(PackageTags);
+        projecturl=$(PackageProjectUrl)
+      </NuspecProperties>
+    </PropertyGroup>
+  </Target>
+</Project>

+ 22 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
+  <metadata>
+    <id>$id$</id>
+    <version>$version$</version>
+    <description>$description$</description>
+    <authors>$authors$</authors>
+    <owners>$authors$</owners>
+    <projectUrl>$projecturl$</projectUrl>
+    <requireLicenseAcceptance>false</requireLicenseAcceptance>
+    <license type="expression">MIT</license>
+    <licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
+    <tags>$tags$</tags>
+    <packageTypes>
+      <packageType name="$packagetype$" />
+    </packageTypes>
+    <repository url="$projecturl$" />
+  </metadata>
+  <files>
+    <file src="Sdk\**" target="Sdk" />\
+  </files>
+</package>

+ 112 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props

@@ -0,0 +1,112 @@
+<Project>
+  <PropertyGroup>
+    <!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. -->
+    <GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk>
+
+    <GodotProjectTypeGuid>{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}</GodotProjectTypeGuid>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <Configurations>Debug;ExportDebug;ExportRelease</Configurations>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+
+    <GodotProjectDir Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)</GodotProjectDir>
+    <GodotProjectDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir>
+    <GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir>
+
+    <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.mono\temp\'. -->
+    <BaseOutputPath>$(GodotProjectDir).mono\temp\bin\</BaseOutputPath>
+    <OutputPath>$(GodotProjectDir).mono\temp\bin\$(Configuration)\</OutputPath>
+    <!--
+    Use custom IntermediateOutputPath and BaseIntermediateOutputPath only if it wasn't already set.
+    Otherwise the old values may have already been changed by MSBuild which can cause problems with NuGet.
+    -->
+    <IntermediateOutputPath Condition=" '$(IntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\$(Configuration)\</IntermediateOutputPath>
+    <BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\</BaseIntermediateOutputPath>
+
+    <!-- Do not append the target framework name to the output path. -->
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+  </PropertyGroup>
+
+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
+
+  <PropertyGroup>
+    <EnableDefaultNoneItems>false</EnableDefaultNoneItems>
+  </PropertyGroup>
+
+  <!--
+  The Microsoft.NET.Sdk only understands of the Debug and Release configurations.
+  We need to set the following properties manually for ExportDebug and ExportRelease.
+  -->
+  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' or '$(Configuration)' == 'ExportDebug' ">
+    <DebugSymbols Condition=" '$(DebugSymbols)' == '' ">true</DebugSymbols>
+    <Optimize Condition=" '$(Optimize)' == '' ">false</Optimize>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)' == 'ExportRelease' ">
+    <Optimize Condition=" '$(Optimize)' == '' ">true</Optimize>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <GodotApiConfiguration Condition=" '$(Configuration)' != 'ExportRelease' ">Debug</GodotApiConfiguration>
+    <GodotApiConfiguration Condition=" '$(Configuration)' == 'ExportRelease' ">Release</GodotApiConfiguration>
+  </PropertyGroup>
+
+  <!-- Auto-detect the target Godot platform if it was not specified. -->
+  <PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' ">
+    <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Linux))' ">linuxbsd</GodotTargetPlatform>
+    <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(FreeBSD))' ">linuxbsd</GodotTargetPlatform>
+    <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(OSX))' ">osx</GodotTargetPlatform>
+    <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Windows))' ">windows</GodotTargetPlatform>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <GodotRealTIsDouble Condition=" '$(GodotRealTIsDouble)' == '' ">false</GodotRealTIsDouble>
+  </PropertyGroup>
+
+  <!-- Godot DefineConstants. -->
+  <PropertyGroup>
+    <!-- Define constant to identify Godot builds. -->
+    <GodotDefineConstants>GODOT</GodotDefineConstants>
+
+    <!--
+    Define constant to determine the target Godot platform. This includes the
+    recognized platform names and the platform category (PC, MOBILE or WEB).
+    -->
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'windows' ">GODOT_WINDOWS;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'linuxbsd' ">GODOT_LINUXBSD;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'osx' ">GODOT_OSX;GODOT_MACOS;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'server' ">GODOT_SERVER;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'uwp' ">GODOT_UWP;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'haiku' ">GODOT_HAIKU;GODOT_PC</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'android' ">GODOT_ANDROID;GODOT_MOBILE</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'iphone' ">GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE</GodotPlatformConstants>
+    <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'javascript' ">GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB</GodotPlatformConstants>
+
+    <GodotDefineConstants>$(GodotDefineConstants);$(GodotPlatformConstants)</GodotDefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!-- ExportDebug also defines DEBUG like Debug does. -->
+    <DefineConstants Condition=" '$(Configuration)' == 'ExportDebug' ">$(DefineConstants);DEBUG</DefineConstants>
+    <!-- Debug defines TOOLS to differenciate between Debug and ExportDebug configurations. -->
+    <DefineConstants Condition=" '$(Configuration)' == 'Debug' ">$(DefineConstants);TOOLS</DefineConstants>
+
+    <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <!--
+    TODO:
+    We should consider a nuget package for reference assemblies. This is difficult because the
+    Godot scripting API is continuaslly breaking backwards compatibility even in patch releases.
+    -->
+    <Reference Include="GodotSharp">
+      <Private>false</Private>
+      <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
+    </Reference>
+    <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
+      <Private>false</Private>
+      <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+</Project>

+ 17 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets

@@ -0,0 +1,17 @@
+<Project>
+  <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
+
+  <PropertyGroup>
+    <EnableGodotProjectTypeGuid Condition=" '$(EnableGodotProjectTypeGuid)' == '' ">true</EnableGodotProjectTypeGuid>
+    <ProjectTypeGuids Condition=" '$(EnableGodotProjectTypeGuid)' == 'true' ">$(GodotProjectTypeGuid);$(DefaultProjectTypeGuid)</ProjectTypeGuids>
+  </PropertyGroup>
+
+  <PropertyGroup>
+    <!--
+    Define constant to determine whether the real_t type in Godot is double precision or not.
+    By default this is false, like the official Godot builds. If someone is using a custom
+    Godot build where real_t is double, they can override the GodotRealTIsDouble property.
+    -->
+    <DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants>
+  </PropertyGroup>
+</Project>

+ 4 - 1
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);
         }

+ 32 - 24
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs

@@ -22,6 +22,37 @@ namespace GodotTools.ProjectEditor
             return string.Join(".", identifiers);
         }
 
+        /// <summary>
+        /// Skips invalid identifier characters including decimal digit numbers at the start of the identifier.
+        /// </summary>
+        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)
             {

+ 0 - 118
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs

@@ -1,118 +0,0 @@
-using GodotTools.Core;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Microsoft.Build.Construction;
-using Microsoft.Build.Globbing;
-
-namespace GodotTools.ProjectEditor
-{
-    public static class ProjectExtensions
-    {
-        public static ProjectItemElement FindItemOrNull(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
-        {
-            string normalizedInclude = include.NormalizePath();
-
-            foreach (var itemGroup in root.ItemGroups)
-            {
-                if (noCondition && itemGroup.Condition.Length != 0)
-                    continue;
-
-                foreach (var item in itemGroup.Items)
-                {
-                    if (item.ItemType != itemType)
-                        continue;
-
-                    //var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
-                    var glob = MSBuildGlob.Parse(item.Include.NormalizePath());
-
-                    if (glob.IsMatch(normalizedInclude))
-                        return item;
-                }
-            }
-
-            return null;
-        }
-        public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
-        {
-            string normalizedInclude = Path.GetFullPath(include).NormalizePath();
-
-            foreach (var itemGroup in root.ItemGroups)
-            {
-                if (noCondition && itemGroup.Condition.Length != 0)
-                    continue;
-
-                foreach (var item in itemGroup.Items)
-                {
-                    if (item.ItemType != itemType)
-                        continue;
-
-                    var glob = MSBuildGlob.Parse(Path.GetFullPath(item.Include).NormalizePath());
-
-                    if (glob.IsMatch(normalizedInclude))
-                        return item;
-                }
-            }
-
-            return null;
-        }
-
-        public static IEnumerable<ProjectItemElement> FindAllItemsInFolder(this ProjectRootElement root, string itemType, string folder)
-        {
-            string absFolderNormalizedWithSep = Path.GetFullPath(folder).NormalizePath() + Path.DirectorySeparatorChar;
-
-            foreach (var itemGroup in root.ItemGroups)
-            {
-                foreach (var item in itemGroup.Items)
-                {
-                    if (item.ItemType != itemType)
-                        continue;
-
-                    string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath();
-
-                    if (absPathNormalized.StartsWith(absFolderNormalizedWithSep))
-                        yield return item;
-                }
-            }
-        }
-
-        public static bool HasItem(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
-        {
-            return root.FindItemOrNull(itemType, include, noCondition) != null;
-        }
-
-        public static bool AddItemChecked(this ProjectRootElement root, string itemType, string include)
-        {
-            if (!root.HasItem(itemType, include, noCondition: true))
-            {
-                root.AddItem(itemType, include);
-                return true;
-            }
-
-            return false;
-        }
-
-        public static bool RemoveItemChecked(this ProjectRootElement root, string itemType, string include)
-        {
-            var item = root.FindItemOrNullAbs(itemType, include);
-            if (item != null)
-            {
-                item.Parent.RemoveChild(item);
-                return true;
-            }
-
-            return false;
-        }
-
-        public static Guid GetGuid(this ProjectRootElement root)
-        {
-            foreach (var property in root.Properties)
-            {
-                if (property.Name == "ProjectGuid")
-                    return Guid.Parse(property.Value);
-            }
-
-            return Guid.Empty;
-        }
-    }
-}

+ 22 - 147
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 = "4.0.0-dev2";
 
-        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<string> compileItems)
-        {
-            string path = Path.Combine(dir, name + ".csproj");
-
-            ProjectPropertyGroupElement mainGroup;
-            var root = CreateLibraryProject(name, "Debug", out mainGroup);
-
-            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' ";
-
-            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 coreApiRef = root.AddItem("Reference", CoreApiProjectName);
-            coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll"));
-            coreApiRef.AddMetadata("Private", "False");
-
-            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)
+        public static ProjectRootElement GenGameProject(string name)
         {
-            string propertiesDir = Path.Combine(dir, "Properties");
-            if (!Directory.Exists(propertiesDir))
-                Directory.CreateDirectory(propertiesDir);
-
-            string usingDirectivesText = string.Empty;
+            if (name.Length == 0)
+                throw new ArgumentException("Project name is empty", nameof(name));
 
-            if (usingDirectives != null)
-            {
-                foreach (var usingDirective in usingDirectives)
-                    usingDirectivesText += "\nusing " + usingDirective + ";";
-            }
+            var root = ProjectRootElement.Create(NewProjectFileOptions.None);
 
-            string assemblyLinesText = string.Empty;
+            root.Sdk = GodotSdkAttrValue;
 
-            if (assemblyLines != null)
-                assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
+            var mainGroup = root.AddPropertyGroup();
+            mainGroup.AddProperty("TargetFramework", "netstandard2.1");
 
-            string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
+            string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
 
-            string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
+            // If the name is not a valid namespace, manually set RootNamespace to a sanitized one.
+            if (sanitizedName != name)
+                mainGroup.AddProperty("RootNamespace", sanitizedName);
 
-            File.WriteAllText(assemblyInfoFile, content);
-
-            root.AddItem("Compile", assemblyInfoFile.RelativeToPath(dir).Replace("/", "\\"));
+            return root;
         }
 
-        public static ProjectRootElement CreateLibraryProject(string name, string defaultConfig, out ProjectPropertyGroupElement mainGroup)
+        public static string GenAndSaveGameProject(string dir, string name)
         {
-            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());
+            if (name.Length == 0)
+                throw new ArgumentException("Project name is empty", nameof(name));
 
-            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");
+            string path = Path.Combine(dir, name + ".csproj");
 
-            // 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");
+            var root = GenGameProject(name);
 
-            root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\"));
+            root.Save(path);
 
-            return root;
+            return Guid.NewGuid().ToString().ToUpper();
         }
-
-        private const string AssemblyInfoTemplate =
-            @"using System.Reflection;{0}
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle(""{1}"")]
-[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("""")]
-{2}";
     }
 }

+ 27 - 314
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs

@@ -1,9 +1,9 @@
+using System;
 using GodotTools.Core;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
-using System.Reflection;
 using Microsoft.Build.Construction;
 using Microsoft.Build.Globbing;
 
@@ -11,7 +11,7 @@ namespace GodotTools.ProjectEditor
 {
     public sealed class MSBuildProject
     {
-        public ProjectRootElement Root { get; }
+        internal ProjectRootElement Root { get; set; }
 
         public bool HasUnsavedChanges { get; set; }
 
@@ -31,91 +31,7 @@ namespace GodotTools.ProjectEditor
             return root != null ? new MSBuildProject(root) : null;
         }
 
-        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);
-
-            var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\");
-
-            if (root.AddItemChecked(itemType, normalizedInclude))
-                root.Save();
-        }
-
-        public static void RenameItemInProjectChecked(string projectPath, string itemType, string oldInclude, string newInclude)
-        {
-            var dir = Directory.GetParent(projectPath).FullName;
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            var normalizedOldInclude = oldInclude.NormalizePath();
-            var normalizedNewInclude = newInclude.NormalizePath();
-
-            var item = root.FindItemOrNullAbs(itemType, normalizedOldInclude);
-
-            if (item == null)
-                return;
-
-            item.Include = normalizedNewInclude.RelativeToPath(dir).Replace("/", "\\");
-            root.Save();
-        }
-
-        public static void RemoveItemFromProjectChecked(string projectPath, string itemType, string include)
-        {
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            var normalizedInclude = include.NormalizePath();
-
-            if (root.RemoveItemChecked(itemType, normalizedInclude))
-                root.Save();
-        }
-
-        public static void RenameItemsToNewFolderInProjectChecked(string projectPath, string itemType, string oldFolder, string newFolder)
-        {
-            var dir = Directory.GetParent(projectPath).FullName;
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            bool dirty = false;
-
-            var oldFolderNormalized = oldFolder.NormalizePath();
-            var newFolderNormalized = newFolder.NormalizePath();
-            string absOldFolderNormalized = Path.GetFullPath(oldFolderNormalized).NormalizePath();
-            string absNewFolderNormalized = Path.GetFullPath(newFolderNormalized).NormalizePath();
-
-            foreach (var item in root.FindAllItemsInFolder(itemType, oldFolderNormalized))
-            {
-                string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath();
-                string absNewIncludeNormalized = absNewFolderNormalized + absPathNormalized.Substring(absOldFolderNormalized.Length);
-                item.Include = absNewIncludeNormalized.RelativeToPath(dir).Replace("/", "\\");
-                dirty = true;
-            }
-
-            if (dirty)
-                root.Save();
-        }
-
-        public static void RemoveItemsInFolderFromProjectChecked(string projectPath, string itemType, string folder)
-        {
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            var folderNormalized = folder.NormalizePath();
-
-            var itemsToRemove = root.FindAllItemsInFolder(itemType, folderNormalized).ToList();
-
-            if (itemsToRemove.Count > 0)
-            {
-                foreach (var item in itemsToRemove)
-                    item.Parent.RemoveChild(item);
-
-                root.Save();
-            }
-        }
-
-        private static string[] GetAllFilesRecursive(string rootDirectory, string mask)
+        private static List<string> GetAllFilesRecursive(string rootDirectory, string mask)
         {
             string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories);
 
@@ -125,262 +41,59 @@ namespace GodotTools.ProjectEditor
                 files[i] = files[i].RelativeToPath(rootDirectory);
             }
 
-            return files;
+            return new List<string>(files);
         }
 
-        public static string[] GetIncludeFiles(string projectPath, string itemType)
+        // NOTE: Assumes auto-including items. Only used by the scripts metadata generator, which will be replaced with source generators in the future.
+        public static IEnumerable<string> GetIncludeFiles(string projectPath, string itemType)
         {
-            var result = new List<string>();
-            var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
+            var excluded = new List<string>();
+            var includedFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
 
             var root = ProjectRootElement.Open(projectPath);
             Debug.Assert(root != null);
 
-            foreach (var itemGroup in root.ItemGroups)
+            foreach (var item in root.Items)
             {
-                if (itemGroup.Condition.Length != 0)
+                if (string.IsNullOrEmpty(item.Condition))
                     continue;
 
-                foreach (var item in itemGroup.Items)
-                {
-                    if (item.ItemType != itemType)
-                        continue;
-
-                    string normalizedInclude = item.Include.NormalizePath();
+                if (item.ItemType != itemType)
+                    continue;
 
-                    var glob = MSBuildGlob.Parse(normalizedInclude);
+                string normalizedExclude = item.Exclude.NormalizePath();
 
-                    // TODO Check somehow if path has no blob to avoid the following loop...
+                var glob = MSBuildGlob.Parse(normalizedExclude);
 
-                    foreach (var existingFile in existingFiles)
-                    {
-                        if (glob.IsMatch(existingFile))
-                        {
-                            result.Add(existingFile);
-                        }
-                    }
-                }
+                excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile)));
             }
 
-            return result.ToArray();
+            includedFiles.RemoveAll(f => excluded.Contains(f));
+
+            return includedFiles;
         }
 
-        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"));
+            var origRoot = project.Root;
 
-            if (found)
+            if (!string.IsNullOrEmpty(origRoot.Sdk))
                 return;
 
-            root.AddProperty("ProjectTypeGuids", ProjectGenerator.GodotDefaultProjectTypeGuids);
-
+            project.Root = ProjectGenerator.GenGameProject(projectName);
+            project.Root.FullPath = origRoot.FullPath;
             project.HasUnsavedChanges = true;
         }
 
-        ///  Simple function to make sure the Api assembly references are configured correctly
-        public static void FixApiHintPath(MSBuildProject project)
-        {
-            var root = project.Root;
-
-            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)
+        public static void EnsureGodotSdkIsUpToDate(MSBuildProject project)
         {
             var root = project.Root;
+            string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue;
 
-            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;
         }
     }

+ 34 - 38
modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs

@@ -24,48 +24,50 @@ namespace GodotTools
         private Button errorsBtn;
         private Button viewLogBtn;
 
-        private void _UpdateBuildTabsList()
+        private void _UpdateBuildTab(int index, int? currentTab)
         {
-            buildTabsList.Clear();
+            var tab = (BuildTab)buildTabs.GetChild(index);
 
-            int currentTab = buildTabs.CurrentTab;
+            string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
+            itemName += " [" + tab.BuildInfo.Configuration + "]";
 
-            bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
+            buildTabsList.AddItem(itemName, tab.IconTexture);
 
-            for (int i = 0; i < buildTabs.GetChildCount(); i++)
-            {
-                var tab = (BuildTab)buildTabs.GetChild(i);
+            string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
+            itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
+            itemTooltip += "\nStatus: ";
 
-                if (tab == null)
-                    continue;
+            if (tab.BuildExited)
+                itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
+            else
+                itemTooltip += "Running";
 
-                string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
-                itemName += " [" + tab.BuildInfo.Configuration + "]";
+            if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
+                itemTooltip += $"\nErrors: {tab.ErrorCount}";
 
-                buildTabsList.AddItem(itemName, tab.IconTexture);
+            itemTooltip += $"\nWarnings: {tab.WarningCount}";
 
-                string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
-                itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
-                itemTooltip += "\nStatus: ";
+            buildTabsList.SetItemTooltip(index, itemTooltip);
 
-                if (tab.BuildExited)
-                    itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
-                else
-                    itemTooltip += "Running";
+            // 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);
+            }
+        }
 
-                if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
-                    itemTooltip += $"\nErrors: {tab.ErrorCount}";
+        private void _UpdateBuildTabsList()
+        {
+            buildTabsList.Clear();
 
-                itemTooltip += $"\nWarnings: {tab.WarningCount}";
+            int? currentTab = buildTabs.CurrentTab;
 
-                buildTabsList.SetItemTooltip(i, itemTooltip);
+            if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
+                currentTab = null;
 
-                if (noCurrentTab || currentTab == i)
-                {
-                    buildTabsList.Select(i);
-                    _BuildTabsItemSelected(i);
-                }
-            }
+            for (int i = 0; i < buildTabs.GetChildCount(); 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
@@ -325,7 +321,7 @@ namespace GodotTools
                 };
                 panelBuildsTab.AddChild(hsc);
 
-                buildTabsList = new ItemList { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
+                buildTabsList = new ItemList {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill};
                 buildTabsList.ItemSelected += _BuildTabsItemSelected;
                 buildTabsList.NothingSelected += _BuildTabsNothingSelected;
                 hsc.AddChild(buildTabsList);

+ 7 - 23
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<string> 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, targets: 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()

+ 52 - 64
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;
         }
 
-        public static void GenerateScriptsMetadata(string projectPath, string outputPath)
+        private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata)
         {
-            if (File.Exists(outputPath))
-                File.Delete(outputPath);
+            fileMetadata = null;
 
-            var oldDict = Internal.GetScriptsMetadataOrNothing();
-            var newDict = new Godot.Collections.Dictionary<string, object>();
+            var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr);
 
-            foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
+            if (parseError != Error.Ok)
             {
-                string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
+                GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}");
+                return false;
+            }
 
-                ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
+            string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile);
 
-                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;
-                        }
-                    }
-                }
+            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
 
-                Error parseError = ScriptClassParser.ParseFile(projectIncludeFile, out var classes, out string errorStr);
-                if (parseError != Error.Ok)
+            fileMetadata = new Dictionary
+            {
+                ["modified_time"] = $"{modifiedTime}",
+                ["class"] = new Dictionary
                 {
-                    GD.PushError($"Failed to determine namespace and class for script: {projectIncludeFile}. Parse error: {errorStr ?? parseError.ToString()}");
-                    continue;
+                    ["namespace"] = firstMatch.Namespace,
+                    ["class_name"] = firstMatch.Name,
+                    ["nested"] = firstMatch.Nested
                 }
+            };
 
-                string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile);
-
-                var classDict = new Dictionary();
+            return true;
+        }
 
-                foreach (var classDecl in classes)
-                {
-                    if (classDecl.BaseCount == 0)
-                        continue; // Does not inherit nor implement anything, so it can't be a script class
+        public static void GenerateScriptsMetadata(string projectPath, string outputPath)
+        {
+            var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate();
 
-                    string classCmp = classDecl.Nested ?
-                        classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
-                        classDecl.Name;
+            bool IsUpToDate(string includeFile, ulong modifiedTime)
+            {
+                return metadataDict.TryGetValue(includeFile, out var oldFileVar) &&
+                       ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string,
+                           out ulong storedModifiedTime) && storedModifiedTime == modifiedTime;
+            }
 
-                    if (classCmp != searchName)
-                        continue;
+            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();
 
-                    classDict["namespace"] = classDecl.Namespace;
-                    classDict["class_name"] = classDecl.Name;
-                    classDict["nested"] = classDecl.Nested;
-                    break;
-                }
+            foreach (var pair in outdatedFiles)
+            {
+                metadataDict.Remove(pair.Key);
 
-                if (classDict.Count == 0)
-                    continue; // Not found
+                string includeFile = pair.Key;
 
-                newDict[projectIncludeFile] = new Dictionary { ["modified_time"] = $"{modifiedTime}", ["class"] = classDict };
+                if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata))
+                    metadataDict[includeFile] = fileMetadata;
             }
 
-            if (newDict.Count > 0)
-            {
-                string json = JSON.Print(newDict);
+            string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict);
 
-                string baseDir = outputPath.GetBaseDir();
+            string baseDir = outputPath.GetBaseDir();
 
-                if (!Directory.Exists(baseDir))
-                    Directory.CreateDirectory(baseDir);
+            if (!Directory.Exists(baseDir))
+                Directory.CreateDirectory(baseDir);
 
-                File.WriteAllText(outputPath, json);
-            }
+            File.WriteAllText(outputPath, json);
         }
     }
 }

+ 11 - 13
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<string> features)
+        private static bool DeterminePlatformFromFeatures(IEnumerable<string> 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
         /// </summary>
         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.

+ 32 - 66
modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs

@@ -175,36 +175,6 @@ namespace GodotTools
                     // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on.
                     aboutDialog.Exclusive = false;
                 }
-
-                var fileSystemDock = GetEditorInterface().GetFileSystemDock();
-
-                fileSystemDock.FilesMoved += (file, newFile) =>
-                {
-                    if (Path.GetExtension(file) == Internal.CSharpLanguageExtension)
-                    {
-                        ProjectUtils.RenameItemInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
-                            ProjectSettings.GlobalizePath(file), ProjectSettings.GlobalizePath(newFile));
-                    }
-                };
-
-                fileSystemDock.FileRemoved += file =>
-                {
-                    if (Path.GetExtension(file) == Internal.CSharpLanguageExtension)
-                        ProjectUtils.RemoveItemFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
-                            ProjectSettings.GlobalizePath(file));
-                };
-
-                fileSystemDock.FolderMoved += (oldFolder, newFolder) =>
-                {
-                    ProjectUtils.RenameItemsToNewFolderInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
-                        ProjectSettings.GlobalizePath(oldFolder), ProjectSettings.GlobalizePath(newFolder));
-                };
-
-                fileSystemDock.FolderRemoved += oldFolder =>
-                {
-                    ProjectUtils.RemoveItemsInFolderFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
-                        ProjectSettings.GlobalizePath(oldFolder));
-                };
             }
         }
 
@@ -389,6 +359,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();
@@ -468,42 +469,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
             {

+ 4 - 0
modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs

@@ -15,6 +15,10 @@ namespace GodotTools.Internals
             public bool Nested { get; }
             public long BaseCount { get; }
 
+            public string SearchName => Nested ?
+                Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
+                Name;
+
             public ClassDecl(string name, string @namespace, bool nested, long baseCount)
             {
                 Name = name;

+ 0 - 1
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
 

+ 0 - 69
modules/mono/editor/csharp_project.cpp

@@ -1,69 +0,0 @@
-/*************************************************************************/
-/*  csharp_project.cpp                                                   */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-#include "csharp_project.h"
-
-#include "core/io/json.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/os/os.h"
-#include "core/project_settings.h"
-
-#include "../csharp_script.h"
-#include "../mono_gd/gd_mono_class.h"
-#include "../mono_gd/gd_mono_marshal.h"
-#include "../utils/string_utils.h"
-#include "script_class_parser.h"
-
-namespace CSharpProject {
-
-void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) {
-	if (!GLOBAL_DEF("mono/project/auto_update_project", true)) {
-		return;
-	}
-
-	GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly();
-
-	GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils");
-
-	Variant project_path = p_project_path;
-	Variant item_type = p_item_type;
-	Variant include = p_include;
-	const Variant *args[3] = { &project_path, &item_type, &include };
-	MonoException *exc = nullptr;
-	klass->get_method("AddItemToProjectChecked", 3)->invoke(nullptr, args, &exc);
-
-	if (exc) {
-		GDMonoUtils::debug_print_unhandled_exception(exc);
-		ERR_FAIL();
-	}
-}
-
-} // namespace CSharpProject

+ 0 - 42
modules/mono/editor/csharp_project.h

@@ -1,42 +0,0 @@
-/*************************************************************************/
-/*  csharp_project.h                                                     */
-/*************************************************************************/
-/*                       This file is part of:                           */
-/*                           GODOT ENGINE                                */
-/*                      https://godotengine.org                          */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
-/*                                                                       */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the       */
-/* "Software"), to deal in the Software without restriction, including   */
-/* without limitation the rights to use, copy, modify, merge, publish,   */
-/* distribute, sublicense, and/or sell copies of the Software, and to    */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions:                                             */
-/*                                                                       */
-/* The above copyright notice and this permission notice shall be        */
-/* included in all copies or substantial portions of the Software.       */
-/*                                                                       */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
-/*************************************************************************/
-
-#ifndef CSHARP_PROJECT_H
-#define CSHARP_PROJECT_H
-
-#include "core/ustring.h"
-
-namespace CSharpProject {
-
-void add_item(const String &p_project_path, const String &p_item_type, const String &p_include);
-
-} // namespace CSharpProject
-
-#endif // CSHARP_PROJECT_H

+ 6 - 29
modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj

@@ -1,38 +1,16 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <ProjectGuid>{AEBF0036-DA76-4341-B651-A3F2856AB2FA}</ProjectGuid>
-    <OutputType>Library</OutputType>
     <OutputPath>bin/$(Configuration)</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
     <RootNamespace>Godot</RootNamespace>
-    <AssemblyName>GodotSharp</AssemblyName>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
-    <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
+    <EnableDefaultItems>false</EnableDefaultItems>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>portable</DebugType>
-    <Optimize>false</Optimize>
-    <DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>portable</DebugType>
-    <Optimize>true</Optimize>
-    <DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
+  <PropertyGroup>
+    <DefineConstants>$(DefineConstants);GODOT</DefineConstants>
   </PropertyGroup>
-  <ItemGroup>
-    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
-    <Reference Include="System" />
-  </ItemGroup>
   <ItemGroup>
     <Compile Include="Core\AABB.cs" />
     <Compile Include="Core\Array.cs" />
@@ -90,5 +68,4 @@
   Fortunately code completion, go to definition and such still work.
   -->
   <Import Project="Generated\GeneratedIncludes.props" />
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
 </Project>

+ 0 - 24
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")]

+ 14 - 34
modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj

@@ -1,46 +1,26 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
     <ProjectGuid>{8FBEC238-D944-4074-8548-B3B524305905}</ProjectGuid>
-    <OutputType>Library</OutputType>
     <OutputPath>bin/$(Configuration)</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
     <RootNamespace>Godot</RootNamespace>
-    <AssemblyName>GodotSharpEditor</AssemblyName>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <TargetFramework>netstandard2.1</TargetFramework>
     <DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
-    <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
+    <EnableDefaultItems>false</EnableDefaultItems>
   </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>portable</DebugType>
-    <Optimize>false</Optimize>
-    <DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>portable</DebugType>
-    <Optimize>true</Optimize>
-    <DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
+  <PropertyGroup>
+    <DefineConstants>$(DefineConstants);GODOT</DefineConstants>
   </PropertyGroup>
-  <ItemGroup>
-    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
-    <Reference Include="System" />
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="Properties\AssemblyInfo.cs" />
-  </ItemGroup>
-  <Import Project="Generated\GeneratedIncludes.props" />
   <ItemGroup>
     <ProjectReference Include="..\GodotSharp\GodotSharp.csproj">
-      <Private>False</Private>
+      <Private>false</Private>
     </ProjectReference>
   </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <!--
+  We import a props file with auto-generated includes. This works well with Rider.
+  However, Visual Studio and MonoDevelop won't list them in the solution explorer.
+  We can't use wildcards as there may be undesired old files still hanging around.
+  Fortunately code completion, go to definition and such still work.
+  -->
+  <Import Project="Generated\GeneratedIncludes.props" />
 </Project>

+ 0 - 25
modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs

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