123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- 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
- {
- public sealed class MSBuildProject
- {
- internal ProjectRootElement Root { get; set; }
- public bool HasUnsavedChanges { get; set; }
- public void Save() => Root.Save();
- public MSBuildProject(ProjectRootElement root)
- {
- Root = root;
- }
- }
- 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()
- .OrderByDescending(x => x.Version)
- .First();
- MSBuildLocator.RegisterInstance(instance);
- version = instance.Version;
- path = instance.MSBuildPath;
- }
- public static void MSBuildLocatorRegisterMSBuildPath(string msbuildPath)
- => MSBuildLocator.RegisterMSBuildPath(msbuildPath);
- public static MSBuildProject? Open(string path)
- {
- var root = ProjectRootElement.Open(path, ProjectCollection.GlobalProjectCollection, preserveFormatting: true);
- return root != null ? new MSBuildProject(root) : null;
- }
- 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;
- if (!string.IsNullOrEmpty(origRoot.Sdk))
- return;
- project.Root = ProjectGenerator.GenGameProject(projectName);
- project.Root.FullPath = origRoot.FullPath;
- project.HasUnsavedChanges = true;
- }
- public static void EnsureGodotSdkIsUpToDate(MSBuildProject project)
- {
- var root = project.Root;
- string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue;
- if (!string.IsNullOrEmpty(root.Sdk) &&
- root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase))
- return;
- 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;
- }
- }
- }
- }
|