11using System ;
2+ using System . Collections . Generic ;
23using System . Linq ;
4+ using System . Text . RegularExpressions ;
35using Microsoft . Build . Construction ;
6+ using Microsoft . Build . Evaluation ;
47using Microsoft . Build . Locator ;
8+ using NuGet . Frameworks ;
59
610namespace GodotTools . ProjectEditor
711{
@@ -19,8 +23,21 @@ public MSBuildProject(ProjectRootElement root)
1923 }
2024 }
2125
22- public static class ProjectUtils
26+ public static partial class ProjectUtils
2327 {
28+ [ GeneratedRegex ( @"\s*'\$\(GodotTargetPlatform\)'\s*==\s*'(?<platform>[A-z]+)'\s*" , RegexOptions . IgnoreCase ) ]
29+ private static partial Regex GodotTargetPlatformConditionRegex ( ) ;
30+
31+ private static readonly string [ ] _platformNames =
32+ {
33+ "windows" ,
34+ "linuxbsd" ,
35+ "macos" ,
36+ "android" ,
37+ "ios" ,
38+ "web" ,
39+ } ;
40+
2441 public static void MSBuildLocatorRegisterLatest ( out Version version , out string path )
2542 {
2643 var instance = MSBuildLocator . QueryVisualStudioInstances ( )
@@ -36,11 +53,22 @@ public static void MSBuildLocatorRegisterMSBuildPath(string msbuildPath)
3653
3754 public static MSBuildProject ? Open ( string path )
3855 {
39- var root = ProjectRootElement . Open ( path ) ;
56+ var root = ProjectRootElement . Open ( path , ProjectCollection . GlobalProjectCollection , preserveFormatting : true ) ;
4057 return root != null ? new MSBuildProject ( root ) : null ;
4158 }
4259
43- public static void MigrateToProjectSdksStyle ( MSBuildProject project , string projectName )
60+ public static void UpgradeProjectIfNeeded ( MSBuildProject project , string projectName )
61+ {
62+ // NOTE: The order in which changes are made to the project is important.
63+
64+ // Migrate to MSBuild project Sdks style if using the old style.
65+ MigrateToProjectSdksStyle ( project , projectName ) ;
66+
67+ EnsureGodotSdkIsUpToDate ( project ) ;
68+ EnsureTargetFrameworkMatchesMinimumRequirement ( project ) ;
69+ }
70+
71+ private static void MigrateToProjectSdksStyle ( MSBuildProject project , string projectName )
4472 {
4573 var origRoot = project . Root ;
4674
@@ -64,5 +92,128 @@ public static void EnsureGodotSdkIsUpToDate(MSBuildProject project)
6492 root . Sdk = godotSdkAttrValue ;
6593 project . HasUnsavedChanges = true ;
6694 }
95+
96+ private static void EnsureTargetFrameworkMatchesMinimumRequirement ( MSBuildProject project )
97+ {
98+ var root = project . Root ;
99+ string minTfmValue = ProjectGenerator . GodotMinimumRequiredTfm ;
100+ var minTfmVersion = NuGetFramework . Parse ( minTfmValue ) . Version ;
101+
102+ ProjectPropertyGroupElement ? mainPropertyGroup = null ;
103+ ProjectPropertyElement ? mainTargetFrameworkProperty = null ;
104+
105+ var propertiesToChange = new List < ProjectPropertyElement > ( ) ;
106+
107+ foreach ( var propertyGroup in root . PropertyGroups )
108+ {
109+ bool groupHasCondition = ! string . IsNullOrEmpty ( propertyGroup . Condition ) ;
110+
111+ // Check if the property group should be excluded from checking for 'TargetFramework' properties.
112+ if ( groupHasCondition && ! ConditionMatchesGodotPlatform ( propertyGroup . Condition ) )
113+ {
114+ continue ;
115+ }
116+
117+ // Store a reference to the first property group without conditions,
118+ // in case we need to add a new 'TargetFramework' property later.
119+ if ( mainPropertyGroup == null && ! groupHasCondition )
120+ {
121+ mainPropertyGroup = propertyGroup ;
122+ }
123+
124+ foreach ( var property in propertyGroup . Properties )
125+ {
126+ // We are looking for 'TargetFramework' properties.
127+ if ( property . Name != "TargetFramework" )
128+ {
129+ continue ;
130+ }
131+
132+ bool propertyHasCondition = ! string . IsNullOrEmpty ( property . Condition ) ;
133+
134+ // Check if the property should be excluded.
135+ if ( propertyHasCondition && ! ConditionMatchesGodotPlatform ( property . Condition ) )
136+ {
137+ continue ;
138+ }
139+
140+ if ( ! groupHasCondition && ! propertyHasCondition )
141+ {
142+ // Store a reference to the 'TargetFramework' that has no conditions
143+ // because it applies to all platforms.
144+ if ( mainTargetFrameworkProperty == null )
145+ {
146+ mainTargetFrameworkProperty = property ;
147+ }
148+ continue ;
149+ }
150+
151+ // If the 'TargetFramework' property is conditional, it may no longer be needed
152+ // when the main one is upgraded to the new minimum version.
153+ var tfmVersion = NuGetFramework . Parse ( property . Value ) . Version ;
154+ if ( tfmVersion <= minTfmVersion )
155+ {
156+ propertiesToChange . Add ( property ) ;
157+ }
158+ }
159+ }
160+
161+ if ( mainTargetFrameworkProperty == null )
162+ {
163+ // We haven't found a 'TargetFramework' property without conditions,
164+ // we'll just add one in the first property group without conditions.
165+ if ( mainPropertyGroup == null )
166+ {
167+ // We also don't have a property group without conditions,
168+ // so we'll add a new one to the project.
169+ mainPropertyGroup = root . AddPropertyGroup ( ) ;
170+ }
171+
172+ mainTargetFrameworkProperty = mainPropertyGroup . AddProperty ( "TargetFramework" , minTfmValue ) ;
173+ project . HasUnsavedChanges = true ;
174+ }
175+ else
176+ {
177+ var tfmVersion = NuGetFramework . Parse ( mainTargetFrameworkProperty . Value ) . Version ;
178+ if ( tfmVersion < minTfmVersion )
179+ {
180+ mainTargetFrameworkProperty . Value = minTfmValue ;
181+ project . HasUnsavedChanges = true ;
182+ }
183+ }
184+
185+ var mainTfmVersion = NuGetFramework . Parse ( mainTargetFrameworkProperty . Value ) . Version ;
186+ foreach ( var property in propertiesToChange )
187+ {
188+ // If the main 'TargetFramework' property targets a version newer than
189+ // the minimum required by Godot, we don't want to remove the conditional
190+ // 'TargetFramework' properties, only upgrade them to the new minimum.
191+ // Otherwise, it can be removed.
192+ if ( mainTfmVersion > minTfmVersion )
193+ {
194+ property . Value = minTfmValue ;
195+ }
196+ else
197+ {
198+ property . Parent . RemoveChild ( property ) ;
199+ }
200+
201+ project . HasUnsavedChanges = true ;
202+ }
203+
204+ static bool ConditionMatchesGodotPlatform ( string condition )
205+ {
206+ // Check if the condition is checking the 'GodotTargetPlatform' for one of the
207+ // Godot platforms with built-in support in the Godot.NET.Sdk.
208+ var match = GodotTargetPlatformConditionRegex ( ) . Match ( condition ) ;
209+ if ( match . Success )
210+ {
211+ string platform = match . Groups [ "platform" ] . Value ;
212+ return _platformNames . Contains ( platform , StringComparer . OrdinalIgnoreCase ) ;
213+ }
214+
215+ return false ;
216+ }
217+ }
67218 }
68219}
0 commit comments