| 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;            }        }    }}
 |