|
@@ -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*'(?<platform>[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<ProjectPropertyElement>();
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|