diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
index b08efd0964c..157e2bac79c 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
@@ -11,6 +11,7 @@
+
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index dd74a0d5233..e969f31c8bc 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -12,6 +12,8 @@ namespace GodotTools.ProjectEditor
{
public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GeneratedGodotNupkgsVersions.GodotNETSdk}";
+ public static string GodotMinimumRequiredTfm => "net8.0";
+
public static ProjectRootElement GenGameProject(string name)
{
if (name.Length == 0)
@@ -22,7 +24,7 @@ namespace GodotTools.ProjectEditor
root.Sdk = GodotSdkAttrValue;
var mainGroup = root.AddPropertyGroup();
- mainGroup.AddProperty("TargetFramework", "net8.0");
+ mainGroup.AddProperty("TargetFramework", GodotMinimumRequiredTfm);
mainGroup.AddProperty("EnableDynamicLoading", "true");
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
index 794443a69c1..7a07e0a9753 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
@@ -1,7 +1,11 @@
using System;
+using System.Collections.Generic;
using System.Linq;
+using System.Text.RegularExpressions;
using Microsoft.Build.Construction;
+using Microsoft.Build.Evaluation;
using Microsoft.Build.Locator;
+using NuGet.Frameworks;
namespace GodotTools.ProjectEditor
{
@@ -19,8 +23,21 @@ namespace GodotTools.ProjectEditor
}
}
- public static class ProjectUtils
+ public static partial class ProjectUtils
{
+ [GeneratedRegex(@"\s*'\$\(GodotTargetPlatform\)'\s*==\s*'(?[A-z]+)'\s*", RegexOptions.IgnoreCase)]
+ private static partial Regex GodotTargetPlatformConditionRegex();
+
+ private static readonly string[] _platformNames =
+ {
+ "windows",
+ "linuxbsd",
+ "macos",
+ "android",
+ "ios",
+ "web",
+ };
+
public static void MSBuildLocatorRegisterLatest(out Version version, out string path)
{
var instance = MSBuildLocator.QueryVisualStudioInstances()
@@ -36,11 +53,22 @@ namespace GodotTools.ProjectEditor
public static MSBuildProject? Open(string path)
{
- var root = ProjectRootElement.Open(path);
+ var root = ProjectRootElement.Open(path, ProjectCollection.GlobalProjectCollection, preserveFormatting: true);
return root != null ? new MSBuildProject(root) : null;
}
- public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
+ public static void UpgradeProjectIfNeeded(MSBuildProject project, string projectName)
+ {
+ // NOTE: The order in which changes are made to the project is important.
+
+ // Migrate to MSBuild project Sdks style if using the old style.
+ MigrateToProjectSdksStyle(project, projectName);
+
+ EnsureGodotSdkIsUpToDate(project);
+ EnsureTargetFrameworkMatchesMinimumRequirement(project);
+ }
+
+ private static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
{
var origRoot = project.Root;
@@ -64,5 +92,128 @@ namespace GodotTools.ProjectEditor
root.Sdk = godotSdkAttrValue;
project.HasUnsavedChanges = true;
}
+
+ private static void EnsureTargetFrameworkMatchesMinimumRequirement(MSBuildProject project)
+ {
+ var root = project.Root;
+ string minTfmValue = ProjectGenerator.GodotMinimumRequiredTfm;
+ var minTfmVersion = NuGetFramework.Parse(minTfmValue).Version;
+
+ ProjectPropertyGroupElement? mainPropertyGroup = null;
+ ProjectPropertyElement? mainTargetFrameworkProperty = null;
+
+ var propertiesToChange = new List();
+
+ foreach (var propertyGroup in root.PropertyGroups)
+ {
+ bool groupHasCondition = !string.IsNullOrEmpty(propertyGroup.Condition);
+
+ // Check if the property group should be excluded from checking for 'TargetFramework' properties.
+ if (groupHasCondition && !ConditionMatchesGodotPlatform(propertyGroup.Condition))
+ {
+ continue;
+ }
+
+ // Store a reference to the first property group without conditions,
+ // in case we need to add a new 'TargetFramework' property later.
+ if (mainPropertyGroup == null && !groupHasCondition)
+ {
+ mainPropertyGroup = propertyGroup;
+ }
+
+ foreach (var property in propertyGroup.Properties)
+ {
+ // We are looking for 'TargetFramework' properties.
+ if (property.Name != "TargetFramework")
+ {
+ continue;
+ }
+
+ bool propertyHasCondition = !string.IsNullOrEmpty(property.Condition);
+
+ // Check if the property should be excluded.
+ if (propertyHasCondition && !ConditionMatchesGodotPlatform(property.Condition))
+ {
+ continue;
+ }
+
+ if (!groupHasCondition && !propertyHasCondition)
+ {
+ // Store a reference to the 'TargetFramework' that has no conditions
+ // because it applies to all platforms.
+ if (mainTargetFrameworkProperty == null)
+ {
+ mainTargetFrameworkProperty = property;
+ }
+ continue;
+ }
+
+ // If the 'TargetFramework' property is conditional, it may no longer be needed
+ // when the main one is upgraded to the new minimum version.
+ var tfmVersion = NuGetFramework.Parse(property.Value).Version;
+ if (tfmVersion <= minTfmVersion)
+ {
+ propertiesToChange.Add(property);
+ }
+ }
+ }
+
+ if (mainTargetFrameworkProperty == null)
+ {
+ // We haven't found a 'TargetFramework' property without conditions,
+ // we'll just add one in the first property group without conditions.
+ if (mainPropertyGroup == null)
+ {
+ // We also don't have a property group without conditions,
+ // so we'll add a new one to the project.
+ mainPropertyGroup = root.AddPropertyGroup();
+ }
+
+ mainTargetFrameworkProperty = mainPropertyGroup.AddProperty("TargetFramework", minTfmValue);
+ project.HasUnsavedChanges = true;
+ }
+ else
+ {
+ var tfmVersion = NuGetFramework.Parse(mainTargetFrameworkProperty.Value).Version;
+ if (tfmVersion < minTfmVersion)
+ {
+ mainTargetFrameworkProperty.Value = minTfmValue;
+ project.HasUnsavedChanges = true;
+ }
+ }
+
+ var mainTfmVersion = NuGetFramework.Parse(mainTargetFrameworkProperty.Value).Version;
+ foreach (var property in propertiesToChange)
+ {
+ // If the main 'TargetFramework' property targets a version newer than
+ // the minimum required by Godot, we don't want to remove the conditional
+ // 'TargetFramework' properties, only upgrade them to the new minimum.
+ // Otherwise, it can be removed.
+ if (mainTfmVersion > minTfmVersion)
+ {
+ property.Value = minTfmValue;
+ }
+ else
+ {
+ property.Parent.RemoveChild(property);
+ }
+
+ project.HasUnsavedChanges = true;
+ }
+
+ static bool ConditionMatchesGodotPlatform(string condition)
+ {
+ // Check if the condition is checking the 'GodotTargetPlatform' for one of the
+ // Godot platforms with built-in support in the Godot.NET.Sdk.
+ var match = GodotTargetPlatformConditionRegex().Match(condition);
+ if (match.Success)
+ {
+ string platform = match.Groups["platform"].Value;
+ return _platformNames.Contains(platform, StringComparer.OrdinalIgnoreCase);
+ }
+
+ return false;
+ }
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 74e04b46a16..e1dc1be9794 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -439,12 +439,7 @@ namespace GodotTools
var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
?? throw new InvalidOperationException("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, GodotSharpDirs.ProjectAssemblyName);
-
- ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject);
+ ProjectUtils.UpgradeProjectIfNeeded(msbuildProject, GodotSharpDirs.ProjectAssemblyName);
if (msbuildProject.HasUnsavedChanges)
{