From 4e1fd8d411fab6f2f8b910c9287894b587303c0c Mon Sep 17 00:00:00 2001 From: Jeff Kluge Date: Wed, 11 Aug 2021 12:24:47 -0700 Subject: [PATCH] Preserve folder GUIDs in existing solutions When regenerating a solution, project GUIDs are preserved but folder GUIDs are not. This change preserves the folder GUIDs based on their relative paths. Fixes #238 --- .../SlnFileTests.cs | 8 ++- src/Microsoft.VisualStudio.SlnGen/SlnFile.cs | 54 +++++++++++++++++-- .../SlnFolder.cs | 11 ++-- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.VisualStudio.SlnGen.UnitTests/SlnFileTests.cs b/src/Microsoft.VisualStudio.SlnGen.UnitTests/SlnFileTests.cs index 9810ce62..81198dc3 100644 --- a/src/Microsoft.VisualStudio.SlnGen.UnitTests/SlnFileTests.cs +++ b/src/Microsoft.VisualStudio.SlnGen.UnitTests/SlnFileTests.cs @@ -410,6 +410,12 @@ public void TryParseExistingSolution() [@"test\Microsoft.VisualStudio.SlnGen.UnitTests\Microsoft.VisualStudio.SlnGen.UnitTests.csproj"] = new Guid("B55ACBF0-DC34-44BA-8535-8F81325B6D70"), }; + Dictionary folders = new Dictionary + { + [@"FolderA"] = new Guid("9C915FE4-72A5-4368-8979-32B3983E6041"), + [@"FolderB"] = new Guid("D3A9F802-38CC-4F8D-8DE9-8DF9C8B7EADC"), + }; + Dictionary projectFiles = projects.ToDictionary(i => new FileInfo(Path.Combine(solutionFilePath.DirectoryName!, i.Key)), i => i.Value); foreach (KeyValuePair item in projectFiles) @@ -465,7 +471,7 @@ public void TryParseExistingSolution() solutionGuid.ShouldBe(Guid.Parse("CFFC4187-96EE-4465-B5B3-0BAFD3C14BB6")); - projectGuidsByPath.ShouldBe(projectFiles.Select(i => new KeyValuePair(i.Key.FullName, i.Value))); + projectGuidsByPath.ShouldBe(projectFiles.Select(i => new KeyValuePair(i.Key.FullName, i.Value)).Concat(folders)); } [Fact] diff --git a/src/Microsoft.VisualStudio.SlnGen/SlnFile.cs b/src/Microsoft.VisualStudio.SlnGen/SlnFile.cs index f1ee6b4d..2e84c483 100644 --- a/src/Microsoft.VisualStudio.SlnGen/SlnFile.cs +++ b/src/Microsoft.VisualStudio.SlnGen/SlnFile.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; namespace Microsoft.VisualStudio.SlnGen { @@ -51,6 +52,11 @@ public sealed class SlnFile /// private static readonly string[] ProjectSectionSeparator = { "\", \"" }; + /// + /// A regular expression used to parse the project section. + /// + private static readonly Regex GuidRegex = new (@"(?\{[0-9a-fA-F\-]+\})"); + /// /// The file format version. /// @@ -227,11 +233,41 @@ public static bool TryParseExistingSolution(string path, out Guid solutionGuid, if (projectDetails.Length == 3) { - FileInfo projectFullPath = new FileInfo(Path.Combine(solutionFileDirectory, projectDetails[1].Trim().Trim('\"'))); + Match projectGuidMatch = GuidRegex.Match(projectDetails[2]); + + if (!projectGuidMatch.Groups["Guid"].Success) + { + continue; + } + + string projectGuidString = projectGuidMatch.Groups["Guid"].Value; + + Match projectTypeGuidMatch = GuidRegex.Match(projectDetails[0]); + + if (!projectTypeGuidMatch.Groups["Guid"].Success) + { + continue; + } - if (projectFullPath.Exists && Guid.TryParse(projectDetails[2].Trim('\"'), out Guid projectGuid)) + if (!Guid.TryParse(projectGuidString, out Guid projectGuid) || !Guid.TryParse(projectTypeGuidMatch.Groups["Guid"].Value, out Guid projectTypeGuid)) { - projectGuids[projectFullPath.FullName] = projectGuid; + continue; + } + + string projectPath = projectDetails[1].Trim().Trim('\"'); + + if (projectTypeGuid.Equals(SlnFolder.FolderProjectTypeGuid)) + { + projectGuids[projectPath] = projectGuid; + } + else + { + FileInfo projectFileInfo = new FileInfo(Path.Combine(solutionFileDirectory, projectPath)); + + if (projectFileInfo.Exists) + { + projectGuids[projectFileInfo.FullName] = projectGuid; + } } } @@ -355,7 +391,7 @@ internal void Save(string rootPath, TextWriter writer, bool useFolders, bool col if (SolutionItems.Count > 0) { - writer.WriteLine($@"Project(""{SlnFolder.FolderProjectTypeGuid}"") = ""Solution Items"", ""Solution Items"", ""{Guid.NewGuid().ToSolutionString()}"" "); + writer.WriteLine($@"Project(""{SlnFolder.FolderProjectTypeGuidString}"") = ""Solution Items"", ""Solution Items"", ""{Guid.NewGuid().ToSolutionString()}"" "); writer.WriteLine(" ProjectSection(SolutionItems) = preProject"); foreach (string solutionItem in SolutionItems.Select(i => i.ToRelativePath(rootPath))) { @@ -387,7 +423,15 @@ internal void Save(string rootPath, TextWriter writer, bool useFolders, bool col { foreach (SlnFolder folder in hierarchy.Folders) { - writer.WriteLine($@"Project(""{folder.ProjectTypeGuid}"") = ""{folder.Name}"", ""{(useFolders ? folder.FullPath.ToRelativePath(rootPath) : folder.Name)}"", ""{folder.FolderGuid.ToSolutionString()}"""); + string projectPath = useFolders ? folder.FullPath.ToRelativePath(rootPath) : folder.Name; + + // Try to preserve the folder GUID if a matching relative folder path was parsed from an existing solution + if (ExistingProjectGuids != null && ExistingProjectGuids.TryGetValue(projectPath, out Guid projectGuid)) + { + folder.FolderGuid = projectGuid; + } + + writer.WriteLine($@"Project(""{folder.ProjectTypeGuidString}"") = ""{folder.Name}"", ""{projectPath}"", ""{folder.FolderGuid.ToSolutionString()}"""); writer.WriteLine("EndProject"); } } diff --git a/src/Microsoft.VisualStudio.SlnGen/SlnFolder.cs b/src/Microsoft.VisualStudio.SlnGen/SlnFolder.cs index 609e3611..78b86621 100644 --- a/src/Microsoft.VisualStudio.SlnGen/SlnFolder.cs +++ b/src/Microsoft.VisualStudio.SlnGen/SlnFolder.cs @@ -16,7 +16,12 @@ public sealed class SlnFolder /// /// The project type GUID for a folder. /// - public static readonly string FolderProjectTypeGuid = new Guid(VisualStudioProjectTypeGuids.SolutionFolder).ToSolutionString(); + public static readonly Guid FolderProjectTypeGuid = new (VisualStudioProjectTypeGuids.SolutionFolder); + + /// + /// The project type GUID for a folder as a solution string. + /// + public static readonly string FolderProjectTypeGuidString = FolderProjectTypeGuid.ToSolutionString(); /// /// Initializes a new instance of the class. @@ -32,7 +37,7 @@ public SlnFolder(string path) /// /// Gets the of the folder. /// - public Guid FolderGuid { get; } + public Guid FolderGuid { get; set; } /// /// Gets a of child folders. @@ -62,6 +67,6 @@ public SlnFolder(string path) /// /// Gets the project type GUID of the folder. /// - public string ProjectTypeGuid => FolderProjectTypeGuid; + public string ProjectTypeGuidString => FolderProjectTypeGuidString; } } \ No newline at end of file