Browse Source

Merge pull request #38110 from neikeq/csproj-safer-migration

C#: Save copy of sln and csproj before applying fixes
Ignacio Roldán Etcheverry 5 years ago
parent
commit
3ad9652a5d

+ 27 - 0
modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs

@@ -0,0 +1,27 @@
+using System.IO;
+
+namespace GodotTools.Core
+{
+    public static class FileUtils
+    {
+        public static void SaveBackupCopy(string filePath)
+        {
+            string backupPathBase = filePath + ".old";
+            string backupPath = backupPathBase;
+
+            const int maxAttempts = 5;
+            int attempt = 1;
+
+            while (File.Exists(backupPath) && attempt <= maxAttempts)
+            {
+                backupPath = backupPathBase + "." + (attempt);
+                attempt++;
+            }
+
+            if (attempt > maxAttempts + 1)
+                return;
+
+            File.Copy(filePath, backupPath, overwrite: true);
+        }
+    }
+}

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj

@@ -31,6 +31,7 @@
     <Reference Include="System" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="FileUtils.cs" />
     <Compile Include="ProcessExtensions.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="StringExtensions.cs" />

+ 5 - 0
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs

@@ -153,7 +153,12 @@ EndProject";
             var result = regex.Replace(input,m => dict[m.Value]);
 
             if (result != input)
+            {
+                // Save a copy of the solution before replacing it
+                FileUtils.SaveBackupCopy(slnPath);
+
                 File.WriteAllText(slnPath, result);
+            }
         }
     }
 }

+ 25 - 40
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs

@@ -9,8 +9,28 @@ using Microsoft.Build.Construction;
 
 namespace GodotTools.ProjectEditor
 {
+    public sealed class MSBuildProject
+    {
+        public ProjectRootElement Root { get; }
+
+        public bool HasUnsavedChanges => Root.HasUnsavedChanges;
+
+        public void Save() => Root.Save();
+
+        public MSBuildProject(ProjectRootElement root)
+        {
+            Root = root;
+        }
+    }
+
     public static class ProjectUtils
     {
+        public static MSBuildProject Open(string path)
+        {
+            var root = ProjectRootElement.Open(path);
+            return root != null ? new MSBuildProject(root) : null;
+        }
+
         public static void AddItemToProjectChecked(string projectPath, string itemType, string include)
         {
             var dir = Directory.GetParent(projectPath).FullName;
@@ -43,7 +63,6 @@ namespace GodotTools.ProjectEditor
 
         public static void RemoveItemFromProjectChecked(string projectPath, string itemType, string include)
         {
-            var dir = Directory.GetParent(projectPath).FullName;
             var root = ProjectRootElement.Open(projectPath);
             Debug.Assert(root != null);
 
@@ -59,8 +78,6 @@ namespace GodotTools.ProjectEditor
             var root = ProjectRootElement.Open(projectPath);
             Debug.Assert(root != null);
 
-            bool dirty = false;
-
             var oldFolderNormalized = oldFolder.NormalizePath();
             var newFolderNormalized = newFolder.NormalizePath();
             string absOldFolderNormalized = Path.GetFullPath(oldFolderNormalized).NormalizePath();
@@ -71,10 +88,9 @@ namespace GodotTools.ProjectEditor
                 string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath();
                 string absNewIncludeNormalized = absNewFolderNormalized + absPathNormalized.Substring(absOldFolderNormalized.Length);
                 item.Include = absNewIncludeNormalized.RelativeToPath(dir).Replace("/", "\\");
-                dirty = true;
             }
 
-            if (dirty)
+            if (root.HasUnsavedChanges)
                 root.Save();
         }
 
@@ -150,12 +166,9 @@ namespace GodotTools.ProjectEditor
         }
 
         ///  Simple function to make sure the Api assembly references are configured correctly
-        public static void FixApiHintPath(string projectPath)
+        public static void FixApiHintPath(MSBuildProject project)
         {
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            bool dirty = false;
+            var root = project.Root;
 
             void AddPropertyIfNotPresent(string name, string condition, string value)
             {
@@ -170,7 +183,6 @@ namespace GodotTools.ProjectEditor
                 }
 
                 root.AddProperty(name, value).Condition = " " + condition + " ";
-                dirty = true;
             }
 
             AddPropertyIfNotPresent(name: "ApiConfiguration",
@@ -212,7 +224,6 @@ namespace GodotTools.ProjectEditor
                         }
 
                         referenceWithHintPath.AddMetadata("HintPath", hintPath);
-                        dirty = true;
                         return;
                     }
 
@@ -221,14 +232,12 @@ namespace GodotTools.ProjectEditor
                     {
                         // Found a Reference item without a HintPath
                         referenceWithoutHintPath.AddMetadata("HintPath", hintPath);
-                        dirty = true;
                         return;
                     }
                 }
 
                 // Found no Reference item at all. Add it.
                 root.AddItem("Reference", referenceName).Condition = " " + condition + " ";
-                dirty = true;
             }
 
             const string coreProjectName = "GodotSharp";
@@ -242,17 +251,11 @@ namespace GodotTools.ProjectEditor
 
             SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath);
             SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath);
-
-            if (dirty)
-                root.Save();
         }
 
-        public static void MigrateFromOldConfigNames(string projectPath)
+        public static void MigrateFromOldConfigNames(MSBuildProject project)
         {
-            var root = ProjectRootElement.Open(projectPath);
-            Debug.Assert(root != null);
-
-            bool dirty = false;
+            var root = project.Root;
 
             bool hasGodotProjectGeneratorVersion = false;
             bool foundOldConfiguration = false;
@@ -267,7 +270,6 @@ namespace GodotTools.ProjectEditor
                 {
                     configItem.Value = "Debug";
                     foundOldConfiguration = true;
-                    dirty = true;
                 }
             }
 
@@ -275,7 +277,6 @@ namespace GodotTools.ProjectEditor
             {
                 root.PropertyGroups.First(g => g.Condition == string.Empty)?
                     .AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
-                dirty = true;
             }
 
             if (!foundOldConfiguration)
@@ -299,33 +300,21 @@ namespace GodotTools.ProjectEditor
                     void MigrateConditions(string oldCondition, string newCondition)
                     {
                         foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition.Trim() == oldCondition))
-                        {
                             propertyGroup.Condition = " " + newCondition + " ";
-                            dirty = true;
-                        }
 
                         foreach (var propertyGroup in root.PropertyGroups)
                         {
                             foreach (var prop in propertyGroup.Properties.Where(p => p.Condition.Trim() == oldCondition))
-                            {
                                 prop.Condition = " " + newCondition + " ";
-                                dirty = true;
-                            }
                         }
 
                         foreach (var itemGroup in root.ItemGroups.Where(g => g.Condition.Trim() == oldCondition))
-                        {
                             itemGroup.Condition = " " + newCondition + " ";
-                            dirty = true;
-                        }
 
                         foreach (var itemGroup in root.ItemGroups)
                         {
                             foreach (var item in itemGroup.Items.Where(item => item.Condition.Trim() == oldCondition))
-                            {
                                 item.Condition = " " + newCondition + " ";
-                                dirty = true;
-                            }
                         }
                     }
 
@@ -340,10 +329,6 @@ namespace GodotTools.ProjectEditor
                 MigrateConfigurationConditions("Release", "ExportRelease");
                 MigrateConfigurationConditions("Tools", "Debug"); // Must be last
             }
-
-
-            if (dirty)
-                root.Save();
         }
     }
 }

+ 18 - 3
modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs

@@ -1,4 +1,5 @@
 using Godot;
+using GodotTools.Core;
 using GodotTools.Export;
 using GodotTools.Utils;
 using System;
@@ -442,13 +443,27 @@ namespace GodotTools
                 {
                     // Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease
                     DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath);
+
+                    var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
+                                         ?? throw new Exception("Cannot open C# project");
+
+                    // NOTE: The order in which changes are made to the project is important
+
                     // Migrate csproj from old configuration names to: Debug, ExportDebug and ExportRelease
-                    ProjectUtils.MigrateFromOldConfigNames(GodotSharpDirs.ProjectCsProjPath);
+                    ProjectUtils.MigrateFromOldConfigNames(msbuildProject);
 
-                    // Apply the other fixes after configurations are migrated
+                    // Apply the other fixes only after configurations have been migrated
 
                     // Make sure the existing project has Api assembly references configured correctly
-                    ProjectUtils.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath);
+                    ProjectUtils.FixApiHintPath(msbuildProject);
+
+                    if (msbuildProject.HasUnsavedChanges)
+                    {
+                        // Save a copy of the project before replacing it
+                        FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath);
+
+                        msbuildProject.Save();
+                    }
                 }
                 catch (Exception e)
                 {