From 3d1897d01f866859663b27166f378612677da74f Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 10 Oct 2024 14:19:41 +0200 Subject: [PATCH 01/46] include SolutionPersistence package --- NuGet.config | 1 + eng/Versions.props | 4 ++++ src/Build/Microsoft.Build.csproj | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/NuGet.config b/NuGet.config index 659ab421680..c82f938bd58 100644 --- a/NuGet.config +++ b/NuGet.config @@ -19,6 +19,7 @@ + diff --git a/eng/Versions.props b/eng/Versions.props index 7df9b218345..91741b441c8 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -76,4 +76,8 @@ $(VersionPrefix).$(FileVersion.Split('.')[3]) + + + 0.5.26-beta + diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 24471d364ba..127f2a85480 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -30,6 +30,7 @@ + @@ -39,6 +40,10 @@ + + + + From 86f4847d83c2c486d1139845455e514d05299010 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 10 Oct 2024 15:01:42 +0200 Subject: [PATCH 02/46] add slnx in XMake.ProcessProjectSwitch --- src/MSBuild.UnitTests/XMake_Tests.cs | 59 +++++++++++++++++++++++++++- src/MSBuild/XMake.cs | 4 +- src/Shared/FileUtilities.cs | 9 ++++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/MSBuild.UnitTests/XMake_Tests.cs b/src/MSBuild.UnitTests/XMake_Tests.cs index ee6eb6219fb..c3002f6d502 100644 --- a/src/MSBuild.UnitTests/XMake_Tests.cs +++ b/src/MSBuild.UnitTests/XMake_Tests.cs @@ -1577,8 +1577,10 @@ private void RunPriorityBuildTest(ProcessPriorityClass expectedPrority, params s /// [Theory] [InlineData(new[] { "my.proj", "my.sln", "my.slnf" }, "my.sln")] + [InlineData(new[] { "my.proj", "my.slnx", "my.slnf" }, "my.slnx")] [InlineData(new[] { "abc.proj", "bcd.csproj", "slnf.slnf", "other.slnf" }, "abc.proj")] [InlineData(new[] { "abc.sln", "slnf.slnf", "abc.slnf" }, "abc.sln")] + [InlineData(new[] { "abc.slnx", "slnf.slnf", "abc.slnf" }, "abc.slnx")] [InlineData(new[] { "abc.csproj", "abc.slnf", "not.slnf" }, "abc.csproj")] [InlineData(new[] { "abc.slnf" }, "abc.slnf")] public void TestDefaultBuildWithSolutionFilter(string[] projects, string answer) @@ -1724,11 +1726,21 @@ public void TestProcessProjectSwitch() projectHelper = new IgnoreProjectExtensionsHelper(projects); MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles).ShouldBe("test.sln", StringCompareShould.IgnoreCase); // "Expected test.sln to be only solution found" + projects = new[] { "test.proj", "test.slnx" }; + extensionsToIgnore = new[] { ".vcproj" }; + projectHelper = new IgnoreProjectExtensionsHelper(projects); + MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles).ShouldBe("test.slnx", StringCompareShould.IgnoreCase); // "Expected test.slnx to be only solution found" + projects = new[] { "test.proj", "test.sln", "test.proj~", "test.sln~" }; extensionsToIgnore = Array.Empty(); projectHelper = new IgnoreProjectExtensionsHelper(projects); MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles).ShouldBe("test.sln", StringCompareShould.IgnoreCase); // "Expected test.sln to be only solution found" + projects = new[] { "test.proj", "test.slnx", "test.proj~", "test.sln~" }; + extensionsToIgnore = Array.Empty(); + projectHelper = new IgnoreProjectExtensionsHelper(projects); + MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles).ShouldBe("test.slnx", StringCompareShould.IgnoreCase); // "Expected test.slnx to be only solution found" + projects = new[] { "test.proj" }; extensionsToIgnore = Array.Empty(); projectHelper = new IgnoreProjectExtensionsHelper(projects); @@ -1744,6 +1756,12 @@ public void TestProcessProjectSwitch() projectHelper = new IgnoreProjectExtensionsHelper(projects); MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles).ShouldBe("test.sln", StringCompareShould.IgnoreCase); // "Expected test.sln to be only solution found" + projects = new[] { "test.slnx" }; + extensionsToIgnore = Array.Empty(); + projectHelper = new IgnoreProjectExtensionsHelper(projects); + MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles).ShouldBe("test.slnx", StringCompareShould.IgnoreCase); // "Expected test.slnx to be only solution found" + + projects = new[] { "test.sln", "test.sln~" }; extensionsToIgnore = Array.Empty(); projectHelper = new IgnoreProjectExtensionsHelper(projects); @@ -1796,6 +1814,20 @@ public void TestProcessProjectSwitchSlnProjDifferentNames() }); } /// + /// Test the case where there is a .slnx and a project in the same directory but they have different names + /// + [Fact] + public void TestProcessProjectSwitchSlnxProjDifferentNames() + { + Should.Throw(() => + { + string[] projects = { "test.proj", "Different.slnx" }; + string[] extensionsToIgnore = null; + IgnoreProjectExtensionsHelper projectHelper = new IgnoreProjectExtensionsHelper(projects); + MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles); + }); + } + /// /// Test the case where we have two proj files in the same directory /// [Fact] @@ -1838,6 +1870,31 @@ public void TestProcessProjectSwitchTwoSolutions() }); } /// + /// Test when there are two solutions in the same directory - .sln and .slnx + /// + [Fact] + public void TestProcessProjectSwitchSlnAndSlnx() + { + Should.Throw(() => + { + string[] projects = { "test.slnx", "Different.sln" }; + string[] extensionsToIgnore = null; + IgnoreProjectExtensionsHelper projectHelper = new IgnoreProjectExtensionsHelper(projects); + MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles); + }); + } + [Fact] + public void TestProcessProjectSwitchTwoSlnx() + { + Should.Throw(() => + { + string[] projects = { "test.slnx", "Different.slnx" }; + string[] extensionsToIgnore = null; + IgnoreProjectExtensionsHelper projectHelper = new IgnoreProjectExtensionsHelper(projects); + MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles); + }); + } + /// /// Check the case where there are more than two projects in the directory and one is a proj file /// [Fact] @@ -1897,7 +1954,7 @@ internal string[] GetFiles(string path, string searchPattern) List fileNamesToReturn = new List(); foreach (string file in _directoryFileNameList) { - if (string.Equals(searchPattern, "*.sln", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(searchPattern, "*.sln?", StringComparison.OrdinalIgnoreCase)) { if (FileUtilities.IsSolutionFilename(file)) { diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index d850697a06f..469bd3a5376 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -3552,8 +3552,8 @@ internal static string ProcessProjectSwitch( } } - // Get all files in the current directory that have a sln extension - string[] potentialSolutionFiles = getFiles(projectDirectory ?? ".", "*.sln"); + // Get all files in the current directory that have a sln or slnx extension + string[] potentialSolutionFiles = getFiles(projectDirectory ?? ".", "*.sln?"); List actualSolutionFiles = new List(); List solutionFilterFiles = new List(); if (potentialSolutionFiles != null) diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index d2d6108add8..76dd5ee1f2d 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -1065,7 +1065,9 @@ internal static bool FileOrDirectoryExistsNoThrow(string fullPath, IFileSystem f /// internal static bool IsSolutionFilename(string filename) { - return HasExtension(filename, ".sln") || HasExtension(filename, ".slnf"); + return HasExtension(filename, ".sln") || + HasExtension(filename, ".slnf") || + HasExtension(filename, ".slnx"); } internal static bool IsSolutionFilterFilename(string filename) @@ -1073,6 +1075,11 @@ internal static bool IsSolutionFilterFilename(string filename) return HasExtension(filename, ".slnf"); } + internal static bool IsSolutionXFilename(string filename) + { + return HasExtension(filename, ".slnx"); + } + /// /// Returns true if the specified filename is a VC++ project file, otherwise returns false /// From 8a60719fbd240228022c962e12ddc9d8d87aa55e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 10 Oct 2024 16:34:11 +0200 Subject: [PATCH 03/46] support for .slnx format --- .../Construction/SolutionFile_Tests.cs | 309 ++++++++++++---- .../SolutionFile_NewParser_Tests.cs | 166 +++++++++ .../Construction/SolutionFilter_Tests.cs | 67 ++-- .../Solution/ProjectInSolution.cs | 20 +- .../Construction/Solution/SolutionFile.cs | 340 ++++++++++++++++-- .../Solution/SolutionProjectGenerator.cs | 16 +- src/Build/Instance/ProjectInstance.cs | 103 ++++-- 7 files changed, 846 insertions(+), 175 deletions(-) create mode 100644 src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index 84d703d22e8..4e1377c535d 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -5,11 +5,16 @@ using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; using Microsoft.Build.Construction; using Microsoft.Build.Exceptions; using Microsoft.Build.Shared; +using Microsoft.VisualStudio.SolutionPersistence.Model; +using Microsoft.VisualStudio.SolutionPersistence.Serializer; +using Microsoft.VisualStudio.SolutionPersistence; using Shouldly; using Xunit; +using System.Linq; #nullable disable @@ -59,11 +64,13 @@ public void ParseSolution_VC() /// Test that a project with the C++ project guid and an arbitrary extension is seen as valid -- /// we assume that all C++ projects except .vcproj are MSBuild format. /// - [Fact] - public void ParseSolution_VC2() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ParseSolution_VC2(bool convertToSlnx) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}') = 'Project name.myvctype', 'Relative path\to\Project name.myvctype', '{0ABED153-9451-483C-8140-9E8D7306B216}' @@ -83,13 +90,18 @@ public void ParseSolution_VC2() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, convertToSlnx); - Assert.Equal("Project name.myvctype", solution.ProjectsInOrder[0].ProjectName); + string expectedProjectName = convertToSlnx ? "Project name" : "Project name.myvctype"; + Assert.Equal(expectedProjectName, solution.ProjectsInOrder[0].ProjectName); Assert.Equal("Relative path\\to\\Project name.myvctype", solution.ProjectsInOrder[0].RelativePath); - Assert.Equal("{0ABED153-9451-483C-8140-9E8D7306B216}", solution.ProjectsInOrder[0].ProjectGuid); + if (!convertToSlnx) + { + // When converting to SLNX, the project GUID is not preserved. + Assert.Equal("{0ABED153-9451-483C-8140-9E8D7306B216}", solution.ProjectsInOrder[0].ProjectGuid); + } } /// @@ -280,11 +292,13 @@ public void ParseSolutionFileWithDescriptionInformation() /// /// Tests the parsing of a very basic .SLN file with three independent projects. /// - [Fact] - public void BasicSolution() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void BasicSolution(bool convertToSlnx) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{F184B08F-C81C-45F6-A57F-5ABD9991F28F}') = 'ConsoleApplication1', 'ConsoleApplication1\ConsoleApplication1.vbproj', '{AB3413A6-D689-486D-B7F0-A095371B3F13}' @@ -316,34 +330,40 @@ public void BasicSolution() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, convertToSlnx); Assert.Equal(3, solution.ProjectsInOrder.Count); - Assert.Equal("ConsoleApplication1", solution.ProjectsInOrder[0].ProjectName); - Assert.Equal(@"ConsoleApplication1\ConsoleApplication1.vbproj", solution.ProjectsInOrder[0].RelativePath); - Assert.Equal("{AB3413A6-D689-486D-B7F0-A095371B3F13}", solution.ProjectsInOrder[0].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[0].Dependencies); - Assert.Null(solution.ProjectsInOrder[0].ParentProjectGuid); + // When converting to slnx, the order of the projects is not preserved. + ProjectInSolution consoleApplication1 = solution.ProjectsInOrder.First(p => p.ProjectName == "ConsoleApplication1"); + Assert.Equal(@"ConsoleApplication1\ConsoleApplication1.vbproj", consoleApplication1.RelativePath); + Assert.Empty(consoleApplication1.Dependencies); + Assert.Null(consoleApplication1.ParentProjectGuid); - Assert.Equal("vbClassLibrary", solution.ProjectsInOrder[1].ProjectName); - Assert.Equal(@"vbClassLibrary\vbClassLibrary.vbproj", solution.ProjectsInOrder[1].RelativePath); - Assert.Equal("{BA333A76-4511-47B8-8DF4-CA51C303AD0B}", solution.ProjectsInOrder[1].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[1].Dependencies); - Assert.Null(solution.ProjectsInOrder[1].ParentProjectGuid); + ProjectInSolution vbClassLibrary = solution.ProjectsInOrder.First(p => p.ProjectName == "vbClassLibrary"); + Assert.Equal(@"vbClassLibrary\vbClassLibrary.vbproj", vbClassLibrary.RelativePath); + Assert.Empty(vbClassLibrary.Dependencies); + Assert.Null(vbClassLibrary.ParentProjectGuid); - Assert.Equal("ClassLibrary1", solution.ProjectsInOrder[2].ProjectName); - Assert.Equal(@"ClassLibrary1\ClassLibrary1.csproj", solution.ProjectsInOrder[2].RelativePath); - Assert.Equal("{DEBCE986-61B9-435E-8018-44B9EF751655}", solution.ProjectsInOrder[2].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[2].Dependencies); - Assert.Null(solution.ProjectsInOrder[2].ParentProjectGuid); + ProjectInSolution classLibrary1 = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary1"); + Assert.Equal(@"ClassLibrary1\ClassLibrary1.csproj", classLibrary1.RelativePath); + Assert.Empty(classLibrary1.Dependencies); + Assert.Null(classLibrary1.ParentProjectGuid); + + if (!convertToSlnx) + { + Assert.Equal("{AB3413A6-D689-486D-B7F0-A095371B3F13}", consoleApplication1.ProjectGuid); + Assert.Equal("{BA333A76-4511-47B8-8DF4-CA51C303AD0B}", vbClassLibrary.ProjectGuid); + Assert.Equal("{DEBCE986-61B9-435E-8018-44B9EF751655}", classLibrary1.ProjectGuid); + } } /// /// Exercises solution folders, and makes sure that samely named projects in different /// solution folders will get correctly uniquified. + /// For the new parser, solution folders are not included to ProjectsInOrder or ProjectsByGuid. /// [Fact] public void SolutionFolders() @@ -420,6 +440,78 @@ public void SolutionFolders() Assert.Equal("{2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B}", solution.ProjectsInOrder[4].ParentProjectGuid); } + /// + /// Exercises solution folders, and makes sure that samely named projects in different + /// solution folders will get correctly uniquified. + /// For the new parser, solution folders are not included to ProjectsInOrder or ProjectsByGuid. + /// + [Fact] + public void SolutionFoldersSlnx() + { + string solutionFileContents = + """ + Microsoft Visual Studio Solution File, Format Version 9.00 + # Visual Studio 2005 + Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{34E0D07D-CF8F-459D-9449-C4188D8C5564}' + EndProject + Project('{2150E333-8FDC-42A3-9474-1A3956D46DE8}') = 'MySlnFolder', 'MySlnFolder', '{E0F97730-25D2-418A-A7BD-02CAFDC6E470}' + EndProject + Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'MyPhysicalFolder\ClassLibrary1\ClassLibrary1.csproj', '{A5EE8128-B08E-4533-86C5-E46714981680}' + EndProject + Project('{2150E333-8FDC-42A3-9474-1A3956D46DE8}') = 'MySubSlnFolder', 'MySubSlnFolder', '{2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B}' + EndProject + Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary2', 'ClassLibrary2\ClassLibrary2.csproj', '{6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}' + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Release|Any CPU.Build.0 = Release|Any CPU + {A5EE8128-B08E-4533-86C5-E46714981680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5EE8128-B08E-4533-86C5-E46714981680}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5EE8128-B08E-4533-86C5-E46714981680}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5EE8128-B08E-4533-86C5-E46714981680}.Release|Any CPU.Build.0 = Release|Any CPU + {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A5EE8128-B08E-4533-86C5-E46714981680} = {E0F97730-25D2-418A-A7BD-02CAFDC6E470} + {2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B} = {E0F97730-25D2-418A-A7BD-02CAFDC6E470} + {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4} = {2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B} + EndGlobalSection + EndGlobal + """; + + SolutionFile solution = ParseSolutionHelper(solutionFileContents, true); + + Assert.Equal(3, solution.ProjectsInOrder.Count); + + var classLibrary1 = solution.ProjectsInOrder.First(p => p.RelativePath == @"ClassLibrary1\ClassLibrary1.csproj"); + Assert.Empty(classLibrary1.Dependencies); + Assert.Null(classLibrary1.ParentProjectGuid); + + var myPhysicalFolderClassLibrary1 = solution.ProjectsInOrder.First(p => p.RelativePath == @"MyPhysicalFolder\ClassLibrary1\ClassLibrary1.csproj"); + Assert.Empty(myPhysicalFolderClassLibrary1.Dependencies); + + var classLibrary2 = solution.ProjectsInOrder.First(p => p.RelativePath == @"ClassLibrary2\ClassLibrary2.csproj"); + Assert.Empty(classLibrary2.Dependencies); + + // When converting to slnx, the guids are not preserved. + // try at list assert not null + Assert.NotNull(myPhysicalFolderClassLibrary1.ParentProjectGuid); + Assert.NotNull(classLibrary2.ParentProjectGuid); + } + /// /// Exercises shared projects. /// @@ -556,13 +648,15 @@ public void MissingNestedProject() /// /// Verifies that hand-coded project-to-project dependencies listed in the .SLN file - /// are correctly recognized by our solution parser. + /// are correctly recognized by the solution parser. /// - [Fact] - public void SolutionDependencies() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SolutionDependencies(bool convertToSlnx) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{05A5AD00-71B5-4612-AF2F-9EA9121C4111}' @@ -601,27 +695,29 @@ public void SolutionDependencies() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, convertToSlnx); Assert.Equal(3, solution.ProjectsInOrder.Count); - Assert.Equal(@"ClassLibrary1\ClassLibrary1.csproj", solution.ProjectsInOrder[0].RelativePath); - Assert.Equal("{05A5AD00-71B5-4612-AF2F-9EA9121C4111}", solution.ProjectsInOrder[0].ProjectGuid); - Assert.Single(solution.ProjectsInOrder[0].Dependencies); - Assert.Equal("{FAB4EE06-6E01-495A-8926-5514599E3DD9}", (string)solution.ProjectsInOrder[0].Dependencies[0]); + var classLibrary1 = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary1"); + var classLibrary2 = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary2"); + var classLibrary3 = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary3"); + + Assert.Equal(@"ClassLibrary1\ClassLibrary1.csproj", classLibrary1.RelativePath); + Assert.Single(classLibrary1.Dependencies); + Assert.Equal(classLibrary3.ProjectGuid, classLibrary1.Dependencies[0]); Assert.Null(solution.ProjectsInOrder[0].ParentProjectGuid); - Assert.Equal(@"ClassLibrary2\ClassLibrary2.csproj", solution.ProjectsInOrder[1].RelativePath); - Assert.Equal("{7F316407-AE3E-4F26-BE61-2C50D30DA158}", solution.ProjectsInOrder[1].ProjectGuid); - Assert.Equal(2, solution.ProjectsInOrder[1].Dependencies.Count); - Assert.Equal("{FAB4EE06-6E01-495A-8926-5514599E3DD9}", (string)solution.ProjectsInOrder[1].Dependencies[0]); - Assert.Equal("{05A5AD00-71B5-4612-AF2F-9EA9121C4111}", (string)solution.ProjectsInOrder[1].Dependencies[1]); + Assert.Equal(@"ClassLibrary2\ClassLibrary2.csproj", classLibrary2.RelativePath); + Assert.Equal(2, classLibrary2.Dependencies.Count); + // When converting to SLNX, the projects dependencies order is not preserved. + Assert.Contains(classLibrary3.ProjectGuid, classLibrary2.Dependencies); + Assert.Contains(classLibrary1.ProjectGuid, classLibrary2.Dependencies); Assert.Null(solution.ProjectsInOrder[1].ParentProjectGuid); Assert.Equal(@"ClassLibrary3\ClassLibrary3.csproj", solution.ProjectsInOrder[2].RelativePath); - Assert.Equal("{FAB4EE06-6E01-495A-8926-5514599E3DD9}", solution.ProjectsInOrder[2].ProjectGuid); Assert.Empty(solution.ProjectsInOrder[2].Dependencies); Assert.Null(solution.ProjectsInOrder[2].ParentProjectGuid); } @@ -629,11 +725,13 @@ public void SolutionDependencies() /// /// Make sure the solution configurations get parsed correctly for a simple mixed C#/VC solution /// - [Fact] - public void ParseSolutionConfigurations() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ParseSolutionConfigurations(bool convertToSlnx) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' @@ -678,9 +776,9 @@ public void ParseSolutionConfigurations() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, convertToSlnx); Assert.Equal(7, solution.SolutionConfigurations.Count); @@ -704,11 +802,13 @@ public void ParseSolutionConfigurations() /// /// Make sure the solution configurations get parsed correctly for a simple C# application /// - [Fact] - public void ParseSolutionConfigurationsNoMixedPlatform() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ParseSolutionConfigurationsNoMixedPlatform(bool convertToSlnx) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' @@ -733,14 +833,14 @@ public void ParseSolutionConfigurationsNoMixedPlatform() {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|ARM.ActiveCfg = Release|Any CPU {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|ARM.Build.0 = Release|Any CPU {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|x86.ActiveCfg = Release|Any CPU - EndGlobalSection + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, convertToSlnx); Assert.Equal(6, solution.SolutionConfigurations.Count); @@ -839,15 +939,18 @@ public void ParseInvalidSolutionConfigurations3() ParseSolutionHelper(solutionFileContents); }); } + /// /// Make sure the project configurations in solution configurations get parsed correctly /// for a simple mixed C#/VC solution /// - [Fact] - public void ParseProjectConfigurationsInSolutionConfigurations1() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ParseProjectConfigurationsInSolutionConfigurations1(bool convertToSlnx) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' @@ -889,12 +992,12 @@ public void ParseProjectConfigurationsInSolutionConfigurations1() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, convertToSlnx); - ProjectInSolution csharpProject = (ProjectInSolution)solution.ProjectsByGuid["{6185CC21-BE89-448A-B3C0-D1C27112E595}"]; - ProjectInSolution vcProject = (ProjectInSolution)solution.ProjectsByGuid["{A6F99D27-47B9-4EA4-BFC9-25157CBDC281}"]; + ProjectInSolution csharpProject = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary1"); + ProjectInSolution vcProject = solution.ProjectsInOrder.First(p => p.ProjectName == "MainApp"); Assert.Equal(6, csharpProject.ProjectConfigurations.Count); @@ -998,6 +1101,65 @@ public void ParseProjectConfigurationsInSolutionConfigurations2() Assert.Equal(".NET", solution.GetDefaultPlatformName()); // "Default solution platform" } + [Fact] + public void ParseProjectConfigurationsInSolutionConfigurationsSlnx() + { + string solutionFileContents = + """ + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio Version 17 + VisualStudioVersion = 17.11.35111.106 + MinimumVisualStudioVersion = 10.0.40219.1 + Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""WinFormsApp1"", ""WinFormsApp1\WinFormsApp1.csproj"", ""{3B592A6A-6215-4675-9237-7FEB36BDB4F1}"" + EndProject + Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ClassLibrary1"", ""ClassLibrary1\ClassLibrary1.csproj"", ""{C25056E0-405C-4476-9B22-839264A8530C}"" + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3B592A6A-6215-4675-9237-7FEB36BDB4F1}.Debug|Win32.ActiveCfg = Debug|x86 + {3B592A6A-6215-4675-9237-7FEB36BDB4F1}.Debug|Win32.Build.0 = Debug|x86 + {3B592A6A-6215-4675-9237-7FEB36BDB4F1}.Release|Win32.ActiveCfg = Release|x86 + {3B592A6A-6215-4675-9237-7FEB36BDB4F1}.Release|Win32.Build.0 = Release|x86 + {C25056E0-405C-4476-9B22-839264A8530C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C25056E0-405C-4476-9B22-839264A8530C}.Release|Any CPU.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {AA62B7C4-C703-4DBC-A7AD-D183666ECC20} + EndGlobalSection + EndGlobal + """; + + SolutionFile solution = ParseSolutionHelper(solutionFileContents, true); + + ProjectInSolution winFormsApp1 = solution.ProjectsInOrder.First(p => p.ProjectName == "WinFormsApp1"); + ProjectInSolution classLibrary1 = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary1"); + + Assert.Equal(2, winFormsApp1.ProjectConfigurations.Count); + + Assert.Equal("Debug|x86", winFormsApp1.ProjectConfigurations["Debug|Win32"].FullName); + Assert.True(winFormsApp1.ProjectConfigurations["Debug|Win32"].IncludeInBuild); + + Assert.Equal("Release|x86", winFormsApp1.ProjectConfigurations["Release|Win32"].FullName); + Assert.True(winFormsApp1.ProjectConfigurations["Debug|Win32"].IncludeInBuild); + + Assert.Equal(2, classLibrary1.ProjectConfigurations.Count); + + Assert.Equal("Debug|AnyCPU", classLibrary1.ProjectConfigurations["Debug|Any CPU"].FullName); + Assert.False(classLibrary1.ProjectConfigurations["Debug|Any CPU"].IncludeInBuild); + + Assert.Equal("Release|AnyCPU", classLibrary1.ProjectConfigurations["Release|Any CPU"].FullName); + Assert.False(classLibrary1.ProjectConfigurations["Release|Any CPU"].IncludeInBuild); + } + /// /// Parse solution file with comments /// @@ -1053,22 +1215,37 @@ public void ParseSolutionWithComments() /// /// Helper method to create a SolutionFile object, and call it to parse the SLN file - /// represented by the string contents passed in. + /// represented by the string contents passed in. Optionally can convert the SLN to SLNX and then parse the solution. /// - private static SolutionFile ParseSolutionHelper(string solutionFileContents) + private static SolutionFile ParseSolutionHelper(string solutionFileContents, bool convertToSlnx = false) { solutionFileContents = solutionFileContents.Replace('\'', '"'); string solutionPath = FileUtilities.GetTemporaryFileName(".sln"); - + string slnxPath = solutionPath + "x"; try { File.WriteAllText(solutionPath, solutionFileContents); - SolutionFile sp = SolutionFile.Parse(solutionPath); - return sp; + if (convertToSlnx) + { + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath); + SolutionModel solutionModel = serializer.OpenAsync(solutionPath, CancellationToken.None).Result; + SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); + + SolutionFile slnx = SolutionFile.Parse(slnxPath); + return slnx; + } + + SolutionFile sln = SolutionFile.Parse(solutionPath); + return sln; } finally { File.Delete(solutionPath); + + if (convertToSlnx) + { + File.Delete(slnxPath); + } } } } diff --git a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs new file mode 100644 index 00000000000..db5dd71db6c --- /dev/null +++ b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using Microsoft.Build.Construction; +using Microsoft.Build.Exceptions; +using Microsoft.Build.Shared; +using Microsoft.VisualStudio.SolutionPersistence; +using Microsoft.VisualStudio.SolutionPersistence.Model; +using Microsoft.VisualStudio.SolutionPersistence.Serializer; +using Shouldly; +using Xunit; +using Xunit.Abstractions; + +#nullable disable + +namespace Microsoft.Build.UnitTests.Construction +{ + public class SolutionFile_NewParser_Tests + { + public ITestOutputHelper TestOutputHelper { get; } + + public SolutionFile_NewParser_Tests(ITestOutputHelper testOutputHelper) + { + TestOutputHelper = testOutputHelper; + } + + /// + /// Tests to see that all the data/properties are correctly parsed out of a Venus + /// project in a .SLN. This can be checked only here because of AspNetConfigurations protection level. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ProjectWithWebsiteProperties(bool convertToSlnx) + { + string solutionFileContents = + """ + Microsoft Visual Studio Solution File, Format Version 9.00 + # Visual Studio 2005 + Project(`{E24C65DC-7377-472B-9ABA-BC803B73C61A}`) = `C:\WebSites\WebApplication3\`, `C:\WebSites\WebApplication3\`, `{464FD0B9-E335-4677-BE1E-6B2F982F4D86}` + ProjectSection(WebsiteProperties) = preProject + ProjectReferences = `{FD705688-88D1-4C22-9BFF-86235D89C2FC}|CSCla;ssLibra;ry1.dll;{F0726D09-042B-4A7A-8A01-6BED2422BD5D}|VCClassLibrary1.dll;` + Frontpage = false + Debug.AspNetCompiler.VirtualPath = `/publishfirst` + Debug.AspNetCompiler.PhysicalPath = `..\rajeev\temp\websites\myfirstwebsite\` + Debug.AspNetCompiler.TargetPath = `..\rajeev\temp\publishfirst\` + Debug.AspNetCompiler.ForceOverwrite = `true` + Debug.AspNetCompiler.Updateable = `false` + Debug.AspNetCompiler.Debug = `true` + Debug.AspNetCompiler.KeyFile = `debugkeyfile.snk` + Debug.AspNetCompiler.KeyContainer = `12345.container` + Debug.AspNetCompiler.DelaySign = `true` + Debug.AspNetCompiler.AllowPartiallyTrustedCallers = `false` + Debug.AspNetCompiler.FixedNames = `debugfixednames` + Release.AspNetCompiler.VirtualPath = `/publishfirst_release` + Release.AspNetCompiler.PhysicalPath = `..\rajeev\temp\websites\myfirstwebsite_release\` + Release.AspNetCompiler.TargetPath = `..\rajeev\temp\publishfirst_release\` + Release.AspNetCompiler.ForceOverwrite = `true` + Release.AspNetCompiler.Updateable = `true` + Release.AspNetCompiler.Debug = `false` + VWDPort = 63496 + EndProjectSection + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|.NET = Debug|.NET + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {464FD0B9-E335-4677-BE1E-6B2F982F4D86}.Debug|.NET.ActiveCfg = Debug|.NET + {464FD0B9-E335-4677-BE1E-6B2F982F4D86}.Debug|.NET.Build.0 = Debug|.NET + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + """; + + SolutionFile solution = ParseSolutionHelper(solutionFileContents.Replace('`', '"'), convertToSlnx); + + solution.ProjectsInOrder.ShouldHaveSingleItem(); + + solution.ProjectsInOrder[0].ProjectType.ShouldBe(SolutionProjectType.WebProject); + solution.ProjectsInOrder[0].ProjectName.ShouldBe(@"C:\WebSites\WebApplication3\"); + // TODO: try set Relative path with a port http://localhost:8080/WebSites/WebApplication3/ + solution.ProjectsInOrder[0].RelativePath.ShouldBe(@"C:\WebSites\WebApplication3\"); + solution.ProjectsInOrder[0].Dependencies.Count.ShouldBe(2); + solution.ProjectsInOrder[0].ParentProjectGuid.ShouldBeNull(); + solution.ProjectsInOrder[0].GetUniqueProjectName().ShouldBe(@"C:\WebSites\WebApplication3\"); + + Hashtable aspNetCompilerParameters = solution.ProjectsInOrder[0].AspNetConfigurations; + AspNetCompilerParameters debugAspNetCompilerParameters = (AspNetCompilerParameters)aspNetCompilerParameters["Debug"]; + AspNetCompilerParameters releaseAspNetCompilerParameters = (AspNetCompilerParameters)aspNetCompilerParameters["Release"]; + + debugAspNetCompilerParameters.aspNetVirtualPath.ShouldBe(@"/publishfirst"); + debugAspNetCompilerParameters.aspNetPhysicalPath.ShouldBe(@"..\rajeev\temp\websites\myfirstwebsite\"); + debugAspNetCompilerParameters.aspNetTargetPath.ShouldBe(@"..\rajeev\temp\publishfirst\"); + debugAspNetCompilerParameters.aspNetForce.ShouldBe(@"true"); + debugAspNetCompilerParameters.aspNetUpdateable.ShouldBe(@"false"); + debugAspNetCompilerParameters.aspNetDebug.ShouldBe(@"true"); + debugAspNetCompilerParameters.aspNetKeyFile.ShouldBe(@"debugkeyfile.snk"); + debugAspNetCompilerParameters.aspNetKeyContainer.ShouldBe(@"12345.container"); + debugAspNetCompilerParameters.aspNetDelaySign.ShouldBe(@"true"); + debugAspNetCompilerParameters.aspNetAPTCA.ShouldBe(@"false"); + debugAspNetCompilerParameters.aspNetFixedNames.ShouldBe(@"debugfixednames"); + + releaseAspNetCompilerParameters.aspNetVirtualPath.ShouldBe(@"/publishfirst_release"); + releaseAspNetCompilerParameters.aspNetPhysicalPath.ShouldBe(@"..\rajeev\temp\websites\myfirstwebsite_release\"); + releaseAspNetCompilerParameters.aspNetTargetPath.ShouldBe(@"..\rajeev\temp\publishfirst_release\"); + releaseAspNetCompilerParameters.aspNetForce.ShouldBe(@"true"); + releaseAspNetCompilerParameters.aspNetUpdateable.ShouldBe(@"true"); + releaseAspNetCompilerParameters.aspNetDebug.ShouldBe(@"false"); + releaseAspNetCompilerParameters.aspNetKeyFile.ShouldBe(""); + releaseAspNetCompilerParameters.aspNetKeyContainer.ShouldBe(""); + releaseAspNetCompilerParameters.aspNetDelaySign.ShouldBe(""); + releaseAspNetCompilerParameters.aspNetAPTCA.ShouldBe(""); + releaseAspNetCompilerParameters.aspNetFixedNames.ShouldBe(""); + + List aspNetProjectReferences = solution.ProjectsInOrder[0].ProjectReferences; + aspNetProjectReferences.Count.ShouldBe(2); + aspNetProjectReferences[0].ShouldBe("{FD705688-88D1-4C22-9BFF-86235D89C2FC}"); + aspNetProjectReferences[1].ShouldBe("{F0726D09-042B-4A7A-8A01-6BED2422BD5D}"); + } + + /// + /// Helper method to create a SolutionFile object, and call it to parse the SLN file + /// represented by the string contents passed in. Optionally can convert the SLN to SLNX and then parse the solution. + /// + internal static SolutionFile ParseSolutionHelper(string solutionFileContents, bool convertToSlnx = false) + { + solutionFileContents = solutionFileContents.Replace('\'', '"'); + string solutionPath = FileUtilities.GetTemporaryFileName(".sln"); + string slnxPath = solutionPath + "x"; + try + { + File.WriteAllText(solutionPath, solutionFileContents); + if (convertToSlnx) + { + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath); + SolutionModel solutionModel = serializer.OpenAsync(solutionPath, CancellationToken.None).Result; + SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); + + SolutionFile slnx = new SolutionFile { FullPath = slnxPath }; + slnx.ParseUsingNewParser(); + return slnx; + } + + SolutionFile sln = SolutionFile.Parse(solutionPath); + return sln; + } + finally + { + File.Delete(solutionPath); + + if (convertToSlnx) + { + File.Delete(slnxPath); + } + } + } + } +} diff --git a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs index 400c3f6af52..43a074de6ad 100644 --- a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; @@ -13,6 +14,9 @@ using Microsoft.Build.Framework; using Microsoft.Build.Graph; using Microsoft.Build.UnitTests; +using Microsoft.VisualStudio.SolutionPersistence.Model; +using Microsoft.VisualStudio.SolutionPersistence.Serializer; +using Microsoft.VisualStudio.SolutionPersistence; using Shouldly; using Xunit; using Xunit.Abstractions; @@ -215,8 +219,10 @@ public void InvalidSolutionFilters(string slnfValue, string exceptionReason) /// /// Test that a solution filter file is parsed correctly, and it can accurately respond as to whether a project should be filtered out. /// - [Fact] - public void ParseSolutionFilter() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ParseSolutionFilter(bool convertToSlnx) { using (TestEnvironment testEnvironment = TestEnvironment.Create()) { @@ -229,35 +235,35 @@ public void ParseSolutionFilter() // The important part of this .sln is that it has references to each of the four projects we just created. TransientTestFile sln = testEnvironment.CreateFile(folder, "Microsoft.Build.Dev.sln", @" - Microsoft Visual Studio Solution File, Format Version 12.00 - # Visual Studio 15 - VisualStudioVersion = 15.0.27004.2009 - MinimumVisualStudioVersion = 10.0.40219.1 - Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""Microsoft.Build"", """ + Path.Combine("src", Path.GetFileName(microsoftBuild.Path)) + @""", ""{69BE05E2-CBDA-4D27-9733-44E12B0F5627}"" - EndProject - Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""MSBuild"", """ + Path.Combine("src", Path.GetFileName(msbuild.Path)) + @""", ""{6F92CA55-1D15-4F34-B1FE-56C0B7EB455E}"" - EndProject - Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""Microsoft.Build.CommandLine.UnitTests"", """ + Path.Combine("src", Path.GetFileName(commandLineUnitTests.Path)) + @""", ""{0ADDBC02-0076-4159-B351-2BF33FAA46B2}"" - EndProject - Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""Microsoft.Build.Tasks.UnitTests"", """ + Path.Combine("src", Path.GetFileName(tasksUnitTests.Path)) + @""", ""{CF999BDE-02B3-431B-95E6-E88D621D9CBF}"" - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - EndGlobalSection - EndGlobal +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2009 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""Microsoft.Build"", """ + Path.Combine("src", Path.GetFileName(microsoftBuild.Path)) + @""", ""{69BE05E2-CBDA-4D27-9733-44E12B0F5627}"" +EndProject +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""MSBuild"", """ + Path.Combine("src", Path.GetFileName(msbuild.Path)) + @""", ""{6F92CA55-1D15-4F34-B1FE-56C0B7EB455E}"" +EndProject +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""Microsoft.Build.CommandLine.UnitTests"", """ + Path.Combine("src", Path.GetFileName(commandLineUnitTests.Path)) + @""", ""{0ADDBC02-0076-4159-B351-2BF33FAA46B2}"" +EndProject +Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""Microsoft.Build.Tasks.UnitTests"", """ + Path.Combine("src", Path.GetFileName(tasksUnitTests.Path)) + @""", ""{CF999BDE-02B3-431B-95E6-E88D621D9CBF}"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution +EndGlobalSection +GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE +EndGlobalSection +GlobalSection(ExtensibilityGlobals) = postSolution +EndGlobalSection +EndGlobal "); TransientTestFile slnf = testEnvironment.CreateFile(folder, "Dev.slnf", @" { ""solution"": { - ""path"": """ + sln.Path.Replace("\\", "\\\\") + @""", + ""path"": """ + (convertToSlnx ? ConvertToSlnx(sln.Path) : sln.Path).Replace("\\", "\\\\") + @""", ""projects"": [ """ + Path.Combine("src", Path.GetFileName(microsoftBuild.Path)!).Replace("\\", "\\\\") + @""", """ + Path.Combine("src", Path.GetFileName(tasksUnitTests.Path)!).Replace("\\", "\\\\") + @""" @@ -276,6 +282,15 @@ public void ParseSolutionFilter() } } + private static string ConvertToSlnx(string slnPath) + { + string slnxPath = slnPath + "x"; + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(slnPath); + SolutionModel solutionModel = serializer!.OpenAsync(slnPath, CancellationToken.None).Result; + SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); + return slnxPath; + } + private ILoggingService CreateMockLoggingService() { ILoggingService loggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 0); diff --git a/src/Build/Construction/Solution/ProjectInSolution.cs b/src/Build/Construction/Solution/ProjectInSolution.cs index a73df401565..1343cf51914 100644 --- a/src/Build/Construction/Solution/ProjectInSolution.cs +++ b/src/Build/Construction/Solution/ProjectInSolution.cs @@ -406,13 +406,18 @@ internal string GetUniqueProjectName() if (ParentProjectGuid != null) { - if (!ParentSolution.ProjectsByGuid.TryGetValue(ParentProjectGuid, out ProjectInSolution proj)) + ProjectInSolution proj = null; + ProjectInSolution solutionFolder = null; + + // For the new parser, solution folders are not saved in ProjectsByGuid but in the SolutionFoldersByGuid. + if (!ParentSolution.ProjectsByGuid.TryGetValue(ParentProjectGuid, out proj) && + !ParentSolution.SolutionFoldersByGuid.TryGetValue(ParentProjectGuid, out solutionFolder)) { - ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj != null, "SubCategoryForSolutionParsingErrors", + ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj != null || solutionFolder != null, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(ParentSolution.FullPath), "SolutionParseNestedProjectErrorWithNameAndGuid", ProjectName, ProjectGuid, ParentProjectGuid); } - uniqueName = proj.GetUniqueProjectName() + "\\"; + uniqueName = (proj != null ? proj.GetUniqueProjectName() : solutionFolder.GetUniqueProjectName()) + "\\"; } // Now tack on our own project name, and cache it in the ProjectInSolution object for future quick access. @@ -442,16 +447,19 @@ internal string GetOriginalProjectName() // If this project has a parent SLN folder, first get the full project name for the SLN folder, // and tack on trailing backslash. string projectName = String.Empty; + ProjectInSolution proj = null; + ProjectInSolution solutionFolder = null; if (ParentProjectGuid != null) { - if (!ParentSolution.ProjectsByGuid.TryGetValue(ParentProjectGuid, out ProjectInSolution parent)) + if (!ParentSolution.ProjectsByGuid.TryGetValue(ParentProjectGuid, out proj) && + !ParentSolution.SolutionFoldersByGuid.TryGetValue(ParentProjectGuid, out solutionFolder)) { - ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(parent != null, "SubCategoryForSolutionParsingErrors", + ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj != null || solutionFolder != null, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(ParentSolution.FullPath), "SolutionParseNestedProjectErrorWithNameAndGuid", ProjectName, ProjectGuid, ParentProjectGuid); } - projectName = parent.GetOriginalProjectName() + "\\"; + projectName = (proj != null ? proj.GetOriginalProjectName() : solutionFolder.GetOriginalProjectName()) + "\\"; } // Now tack on our own project name, and cache it in the ProjectInSolution object for future quick access. diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 4676638ed9f..475ddb9df67 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -6,14 +6,20 @@ using System.Collections.ObjectModel; using System.Globalization; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; +using System.Threading; using System.Xml; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; +using Microsoft.VisualStudio.SolutionPersistence; +using Microsoft.VisualStudio.SolutionPersistence.Model; +using Microsoft.VisualStudio.SolutionPersistence.Serializer; using BuildEventFileInfo = Microsoft.Build.Shared.BuildEventFileInfo; using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; using ExceptionUtilities = Microsoft.Build.Shared.ExceptionHandling; @@ -92,13 +98,16 @@ public sealed class SolutionFile // conversion, or in preparation for actually building the solution? // The list of projects in this SLN, keyed by the project GUID. - private Dictionary _projects; + private Dictionary _projectsByGuid; + + // The list of solution folders in this SLN, keyed by the folder's GUID. + private Dictionary _solutionFoldersByGuid; // The list of projects in the SLN, in order of their appearance in the SLN. private List _projectsInOrder; // The list of solution configurations in the solution - private List _solutionConfigurations; + private Dictionary _solutionConfigurationsByFullName; // cached default configuration name for GetDefaultConfigurationName private string _defaultConfigurationName; @@ -147,13 +156,15 @@ internal SolutionFile() internal List SolutionParserErrorCodes { get; } = new List(); /// - /// Returns the actual major version of the parsed solution file + /// Returns the actual major version of the parsed solution file. /// + /// This will return 0 for the new parser because Version is not available. internal int Version { get; private set; } /// - /// Returns Visual Studio major version + /// Returns Visual Studio major version. /// + /// This might not be available for the new parser and returns -1. internal int VisualStudioVersion { get @@ -180,16 +191,24 @@ internal int VisualStudioVersion /// internal bool ContainsWebDeploymentProjects { get; private set; } + internal bool UseNewParser => ShouldUseNewParser(_solutionFile); + + internal static bool ShouldUseNewParser(string solutionFile) => FileUtilities.IsSolutionXFilename(solutionFile); + /// /// All projects in this solution, in the order they appeared in the solution file /// + /// Solution folders are no longer for the new parser. public IReadOnlyList ProjectsInOrder => _projectsInOrder.AsReadOnly(); /// /// The collection of projects in this solution, accessible by their guids as a /// string in "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" form /// - public IReadOnlyDictionary ProjectsByGuid => new ReadOnlyDictionary(_projects); + /// Solution folders are no longer included for the new parser. + public IReadOnlyDictionary ProjectsByGuid => new ReadOnlyDictionary(_projectsByGuid); + + internal IReadOnlyDictionary SolutionFoldersByGuid => new ReadOnlyDictionary(_solutionFoldersByGuid); /// /// This is the read/write accessor for the solution file which we will parse. This @@ -239,7 +258,7 @@ internal string SolutionFileDirectory /// /// The list of all full solution configurations (configuration + platform) in this solution /// - public IReadOnlyList SolutionConfigurations => _solutionConfigurations.AsReadOnly(); + public IReadOnlyList SolutionConfigurations => _solutionConfigurationsByFullName.Values.ToList().AsReadOnly(); #endregion @@ -257,11 +276,233 @@ internal bool ProjectShouldBuild(string projectFile) /// public static SolutionFile Parse(string solutionFile) { - var parser = new SolutionFile { FullPath = solutionFile }; - parser.ParseSolutionFile(); - return parser; + var solution = new SolutionFile { FullPath = solutionFile }; + + if (solution.UseNewParser) + { + solution.ParseUsingNewParser(); + } + else + { + // Parse the solution file using the old parser + solution.ParseSolutionFile(); + } + + return solution; + } + + /// + /// Parses .sln, .slnx and .slnf files using Microsoft.VisualStudio.SolutionPersistence. + /// + internal void ParseUsingNewParser() + { + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(FullPath); + + if (serializer != null) + { + try + { + SolutionModel solutionModel = serializer.OpenAsync(FullPath, CancellationToken.None).Result; + ReadSolutionModel(solutionModel); + } + catch (AggregateException aggregateException) + { + ProjectFileErrorUtilities.ThrowInvalidProjectFile( + new BuildEventFileInfo(FullPath), + $"InvalidProjectFile", + string.Join(" ", aggregateException.InnerExceptions.Select(inner => inner.Message))); + } + catch (Exception ex) + { + ProjectFileErrorUtilities.ThrowInvalidProjectFile( + new BuildEventFileInfo(FullPath), + $"InvalidProjectFile", + ex.ToString()); + } + } + else if (serializer == null) + { + ProjectFileErrorUtilities.ThrowInvalidProjectFile( + new BuildEventFileInfo(FullPath), + $"InvalidProjectFile", + $"No solution serializer was found for {FullPath}"); + } + } + + /// + /// Maps to . + /// + /// + private void ReadSolutionModel(SolutionModel solutionModel) + { + ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(_solutionFile), "ParseSolutionFile() got a null solution file!"); + ErrorUtilities.VerifyThrowInternalRooted(_solutionFile); + + _projectsByGuid = new Dictionary(StringComparer.OrdinalIgnoreCase); + _solutionFoldersByGuid = new Dictionary(StringComparer.OrdinalIgnoreCase); + _projectsInOrder = new List(); + ContainsWebProjects = false; + Version = 0; + _currentLineNumber = 0; + _solutionConfigurationsByFullName = new Dictionary(); + _defaultConfigurationName = null; + _defaultPlatformName = null; + + _currentVisualStudioVersion = solutionModel.VisualStudioProperties.Version; + + ReadProjects(solutionModel); + + // We need to save the solution folders in order to cache the unique project names and check for duplicates. + ReadSolutionFolders(solutionModel); + + if (_solutionFilter != null) + { + ValidateProjectsInSolutionFilter(); + } + + CacheUniqueProjectNamesAndCheckForDuplicates(); + } + + private void ReadProjects(SolutionModel solutionModel) + { + foreach (SolutionProjectModel projectModel in solutionModel.SolutionProjects) + { + var proj = new ProjectInSolution(this) + { + ProjectName = GetProjectName(projectModel), + RelativePath = projectModel.FilePath, + ProjectGuid = ToProjectGuidFormat(projectModel.Id), + }; + + // If the project name is empty the new parser throws an error. + + // Validate project relative path + ValidateProjectRelativePath(proj); + + SetProjectType(proj, ToProjectGuidFormat(projectModel.TypeId)); + + SetProjectDependencies(proj, projectModel); + + SetWebsiteProperties(proj, projectModel); + + // Note: This is corresponds to GlobalSection(NestedProjects) section in sln files. + if (projectModel.Parent != null) + { + proj.ParentProjectGuid = ToProjectGuidFormat(projectModel.Parent.Id); + } + + SetProjectConfigurations(proj, projectModel, solutionModel.BuildTypes, solutionModel.Platforms); + + // Add the project to the collection + AddProjectToSolution(proj); + + // If the project is an etp project then parse the etp project file + // to get the projects contained in it. + if (IsEtpProjectFile(proj.RelativePath)) + { + ParseEtpProject(proj); + } + } + } + + private string GetProjectName(SolutionProjectModel projectModel) + => !string.IsNullOrEmpty(projectModel.DisplayName) ? projectModel.DisplayName : projectModel.ActualDisplayName; + + /// + /// Returns a string from Guid in the format "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}". + /// + private static string ToProjectGuidFormat(Guid id) => id.ToString("B").ToUpper(); + + private void SetProjectDependencies(ProjectInSolution proj, SolutionProjectModel projectModel) + { + if (projectModel.Dependencies == null) + { + return; + } + + foreach (var dependency in projectModel.Dependencies) + { + proj.AddDependency(ToProjectGuidFormat(dependency.Id)); + } + } + + private void SetWebsiteProperties(ProjectInSolution proj, SolutionProjectModel projectModel) + { + SolutionPropertyBag websiteProperties = projectModel?.Properties.FirstOrDefault(p => p.Id == "WebsiteProperties"); + + if (websiteProperties is null) + { + return; + } + + foreach (var property in websiteProperties) + { + ParseAspNetCompilerProperty(proj, property.Key, property.Value); + } } + private void SetProjectConfigurations( + ProjectInSolution proj, + SolutionProjectModel projectModel, + IReadOnlyList buildTypes, + IReadOnlyList platforms) + { + foreach (string solutionBuildType in buildTypes) + { + foreach (string solutionPlatform in platforms) + { + // isBuild represents Build.0. The "Build.0" entry tells us whether to build the project configuration in the given solution configuration + // _ argument represents Deploy.0 which we do not use in the old parser + (string projectBuildType, string projectPlatform, bool isBuild, bool _) = projectModel.GetProjectConfiguration(solutionBuildType, solutionPlatform); + + if (projectBuildType == null || projectPlatform == null) + { + continue; + } + + var projectConfiguration = new ProjectConfigurationInSolution( + projectBuildType, + projectPlatform, + isBuild); + + string configurationName = SolutionConfigurationInSolution.ComputeFullName(solutionBuildType, solutionPlatform); + + proj.SetProjectConfiguration(configurationName, projectConfiguration); + + // There are no solution configurations in the new parser. Instead we collect them from each project's configurations. + AddSolutionConfiguration(solutionBuildType, solutionPlatform); + } + } + } + + private void ReadSolutionFolders(SolutionModel solutionModel) + { + foreach (SolutionFolderModel solutionFolderModel in solutionModel.SolutionFolders) + { + var proj = new ProjectInSolution(this) + { + ProjectName = GetSolutionFolderName(solutionFolderModel), + ProjectGuid = ToProjectGuidFormat(solutionFolderModel.Id), + ProjectType = SolutionProjectType.SolutionFolder, + }; + + // If the project name is empty the new parser throws an error. + + if (solutionFolderModel.Parent != null) + { + proj.ParentProjectGuid = ToProjectGuidFormat(solutionFolderModel.Parent.Id); + } + + if (!string.IsNullOrEmpty(proj.ProjectGuid)) + { + _solutionFoldersByGuid[proj.ProjectGuid] = proj; + } + } + } + + private string GetSolutionFolderName(SolutionFolderModel solutionFolderModel) + => !string.IsNullOrEmpty(solutionFolderModel.Name) ? solutionFolderModel.Name : solutionFolderModel.ActualDisplayName; + /// /// Returns "true" if it's a project that's expected to be buildable, or false if it's /// not (e.g. a solution folder) @@ -432,7 +673,12 @@ internal static string ParseSolutionFromSolutionFilter(string solutionFilterFile /// internal void AddSolutionConfiguration(string configurationName, string platformName) { - _solutionConfigurations.Add(new SolutionConfigurationInSolution(configurationName, platformName)); + var solutionConfiguration = new SolutionConfigurationInSolution(configurationName, platformName); + + if (!_solutionConfigurationsByFullName.ContainsKey(solutionConfiguration.FullName)) + { + _solutionConfigurationsByFullName[solutionConfiguration.FullName] = solutionConfiguration; + } } /// @@ -497,12 +743,13 @@ internal void ParseSolutionFile() /// internal void ParseSolution() { - _projects = new Dictionary(StringComparer.OrdinalIgnoreCase); + _projectsByGuid = new Dictionary(StringComparer.OrdinalIgnoreCase); + _solutionFoldersByGuid = new Dictionary(StringComparer.OrdinalIgnoreCase); _projectsInOrder = new List(); ContainsWebProjects = false; Version = 0; _currentLineNumber = 0; - _solutionConfigurations = new List(); + _solutionConfigurationsByFullName = new Dictionary(); _defaultConfigurationName = null; _defaultPlatformName = null; @@ -543,24 +790,7 @@ internal void ParseSolution() if (_solutionFilter != null) { - HashSet projectPaths = new HashSet(_projectsInOrder.Count, _pathComparer); - foreach (ProjectInSolution project in _projectsInOrder) - { - projectPaths.Add(FileUtilities.FixFilePath(project.RelativePath)); - } - foreach (string project in _solutionFilter) - { - if (!projectPaths.Contains(project)) - { - ProjectFileErrorUtilities.ThrowInvalidProjectFile( - "SubCategoryForSolutionParsingErrors", - new BuildEventFileInfo(FileUtilities.GetFullPath(project, Path.GetDirectoryName(_solutionFile))), - "SolutionFilterFilterContainsProjectNotInSolution", - _solutionFilterFile, - project, - _solutionFile); - } - } + ValidateProjectsInSolutionFilter(); } if (rawProjectConfigurationsEntries != null) @@ -568,13 +798,18 @@ internal void ParseSolution() ProcessProjectConfigurationSection(rawProjectConfigurationsEntries); } + CacheUniqueProjectNamesAndCheckForDuplicates(); + } + + private void CacheUniqueProjectNamesAndCheckForDuplicates() + { // Cache the unique name of each project, and check that we don't have any duplicates. var projectsByUniqueName = new Dictionary(StringComparer.OrdinalIgnoreCase); var projectsByOriginalName = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (ProjectInSolution proj in _projectsInOrder) { - // Find the unique name for the project. This method also caches the unique name, + // Find the unique name for the project. This method also caches the unique name, // so it doesn't have to be recomputed later. string uniqueName = proj.GetUniqueProjectName(); @@ -645,7 +880,31 @@ internal void ParseSolution() "SolutionParseDuplicateProject", uniqueNameExists ? uniqueName : proj.ProjectName); } - } // ParseSolutionFile() + } + + private void ValidateProjectsInSolutionFilter() + { + HashSet projectPaths = new HashSet(_projectsInOrder.Count, _pathComparer); + + foreach (ProjectInSolution project in _projectsInOrder) + { + projectPaths.Add(FileUtilities.FixFilePath(project.RelativePath)); + } + + foreach (string project in _solutionFilter) + { + if (!projectPaths.Contains(project)) + { + ProjectFileErrorUtilities.ThrowInvalidProjectFile( + "SubCategoryForSolutionParsingErrors", + new BuildEventFileInfo(FileUtilities.GetFullPath(project, Path.GetDirectoryName(_solutionFile))), + "SolutionFilterFilterContainsProjectNotInSolution", + _solutionFilterFile, + project, + _solutionFile); + } + } + } /// /// This method searches the first two lines of the solution file opened by the specified @@ -1000,7 +1259,7 @@ private void AddProjectToSolution(ProjectInSolution proj) { if (!String.IsNullOrEmpty(proj.ProjectGuid)) { - _projects[proj.ProjectGuid] = proj; + _projectsByGuid[proj.ProjectGuid] = proj; } _projectsInOrder.Add(proj); } @@ -1264,6 +1523,11 @@ internal void ParseFirstProjectLine( // Validate project relative path ValidateProjectRelativePath(proj); + SetProjectType(proj, projectTypeGuid); + } + + private void SetProjectType(ProjectInSolution proj, string projectTypeGuid) + { // Figure out what type of project this is. if ((String.Equals(projectTypeGuid, vbProjectGuid, StringComparison.OrdinalIgnoreCase)) || (String.Equals(projectTypeGuid, csProjectGuid, StringComparison.OrdinalIgnoreCase)) || @@ -1347,7 +1611,7 @@ internal void ParseNestedProjects() string projectGuid = match.Groups["PROPERTYNAME"].Value.Trim(); string parentProjectGuid = match.Groups["PROPERTYVALUE"].Value.Trim(); - if (!_projects.TryGetValue(projectGuid, out ProjectInSolution proj)) + if (!_projectsByGuid.TryGetValue(projectGuid, out ProjectInSolution proj)) { ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj != null, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseNestedProjectUndefinedError", projectGuid, parentProjectGuid); @@ -1407,7 +1671,7 @@ internal void ParseSolutionConfigurations() var (configuration, platform) = ParseConfigurationName(fullConfigurationName, FullPath, _currentLineNumber, str); - _solutionConfigurations.Add(new SolutionConfigurationInSolution(configuration, platform)); + AddSolutionConfiguration(configuration, platform); } while (true); } @@ -1495,7 +1759,7 @@ internal void ProcessProjectConfigurationSection(Dictionary rawP // Solution folders don't have configurations if (project.ProjectType != SolutionProjectType.SolutionFolder) { - foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionConfigurations) + foreach (SolutionConfigurationInSolution solutionConfiguration in _solutionConfigurationsByFullName.Values) { // The "ActiveCfg" entry defines the active project configuration in the given solution configuration // This entry must be present for every possible solution configuration/project combination. @@ -1610,7 +1874,7 @@ public string GetDefaultPlatformName() /// internal string GetProjectUniqueNameByGuid(string projectGuid) { - if (_projects.TryGetValue(projectGuid, out ProjectInSolution proj)) + if (_projectsByGuid.TryGetValue(projectGuid, out ProjectInSolution proj)) { return proj.GetUniqueProjectName(); } @@ -1626,7 +1890,7 @@ internal string GetProjectUniqueNameByGuid(string projectGuid) /// internal string GetProjectRelativePathByGuid(string projectGuid) { - if (_projects.TryGetValue(projectGuid, out ProjectInSolution proj)) + if (_projectsByGuid.TryGetValue(projectGuid, out ProjectInSolution proj)) { return proj.RelativePath; } diff --git a/src/Build/Construction/Solution/SolutionProjectGenerator.cs b/src/Build/Construction/Solution/SolutionProjectGenerator.cs index 1cbb076827b..760fcb390f3 100644 --- a/src/Build/Construction/Solution/SolutionProjectGenerator.cs +++ b/src/Build/Construction/Solution/SolutionProjectGenerator.cs @@ -691,12 +691,16 @@ internal static bool WouldProjectBuild(SolutionFile solutionFile, string selecte /// private ProjectInstance[] Generate() { - // Validate against our minimum for upgradable projects - ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile( - _solutionFile.Version >= SolutionFile.slnFileMinVersion, - "SubCategoryForSolutionParsingErrors", - new BuildEventFileInfo(_solutionFile.FullPath), - "SolutionParseUpgradeNeeded"); + // The Version is not available in the new parser. + if (!_solutionFile.UseNewParser) + { + // Validate against our minimum for upgradable projects + ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile( + _solutionFile.Version >= SolutionFile.slnFileMinVersion, + "SubCategoryForSolutionParsingErrors", + new BuildEventFileInfo(_solutionFile.FullPath), + "SolutionParseUpgradeNeeded"); + } // This is needed in order to make decisions about tools versions such as whether to put a // ToolsVersion parameter on task tags and what MSBuildToolsPath to use when diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index 1419a53c6db..1eacb69a5d0 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -2574,45 +2574,82 @@ internal static ProjectInstance[] LoadSolutionForBuild( // we should be generating a 4.0+ or a 3.5-style wrapper project based on the version of the solution. else { - string solutionFile = projectFile; - if (FileUtilities.IsSolutionFilterFilename(projectFile)) - { - solutionFile = SolutionFile.ParseSolutionFromSolutionFilter(projectFile, out _); - } - SolutionFile.GetSolutionFileAndVisualStudioMajorVersions(solutionFile, out int solutionVersion, out int visualStudioVersion); + projectInstances = CalculateToolsVersionAndGenerateSolutionWrapper( + projectFile, + buildParameters, + loggingService, + projectBuildEventContext, + globalProperties, + isExplicitlyLoaded, + targetNames, + sdkResolverService, + submissionId); + } + + return projectInstances; + } + + private static ProjectInstance[] CalculateToolsVersionAndGenerateSolutionWrapper( + string projectFile, + BuildParameters buildParameters, + ILoggingService loggingService, + BuildEventContext projectBuildEventContext, + Dictionary globalProperties, + bool isExplicitlyLoaded, + IReadOnlyCollection targetNames, + ISdkResolverService sdkResolverService, + int submissionId) + { + string solutionFileName = projectFile; + + if (FileUtilities.IsSolutionFilterFilename(projectFile)) + { + solutionFileName = SolutionFile.ParseSolutionFromSolutionFilter(projectFile, out _); + } - // If we get to this point, it's because it's a valid version. Map the solution version - // to the equivalent MSBuild ToolsVersion, and unless it's Dev10 or newer, spawn the old - // engine to generate the solution wrapper. - if (solutionVersion <= 9) /* Whidbey or before */ + if (SolutionFile.ShouldUseNewParser(solutionFileName)) + { + // For the new parser we use Current tools version. + return GenerateSolutionWrapper(projectFile, globalProperties, "Current", loggingService, projectBuildEventContext, targetNames, sdkResolverService, submissionId); + } + + // For the old parser we try to make a best-effort guess based on the version of the solution. + string toolsVersion = null; + ProjectInstance[] projectInstances = null; + + SolutionFile.GetSolutionFileAndVisualStudioMajorVersions(solutionFileName, out int solutionVersion, out int visualStudioVersion); + + // If we get to this point, it's because it's a valid version. Map the solution version + // to the equivalent MSBuild ToolsVersion, and unless it's Dev10 or newer, spawn the old + // engine to generate the solution wrapper. + if (solutionVersion <= 9) /* Whidbey or before */ + { + loggingService.LogComment(projectBuildEventContext, MessageImportance.Low, "OldWrapperGeneratedOldSolutionVersion", "2.0", solutionVersion); + projectInstances = GenerateSolutionWrapperUsingOldOM(projectFile, globalProperties, "2.0", buildParameters.ProjectRootElementCache, buildParameters, loggingService, projectBuildEventContext, isExplicitlyLoaded, sdkResolverService, submissionId); + } + else if (solutionVersion == 10) /* Orcas */ + { + loggingService.LogComment(projectBuildEventContext, MessageImportance.Low, "OldWrapperGeneratedOldSolutionVersion", "3.5", solutionVersion); + projectInstances = GenerateSolutionWrapperUsingOldOM(projectFile, globalProperties, "3.5", buildParameters.ProjectRootElementCache, buildParameters, loggingService, projectBuildEventContext, isExplicitlyLoaded, sdkResolverService, submissionId); + } + else + { + if ((solutionVersion == 11) || (solutionVersion == 12 && visualStudioVersion == 0)) /* Dev 10 and Dev 11 */ { - loggingService.LogComment(projectBuildEventContext, MessageImportance.Low, "OldWrapperGeneratedOldSolutionVersion", "2.0", solutionVersion); - projectInstances = GenerateSolutionWrapperUsingOldOM(projectFile, globalProperties, "2.0", buildParameters.ProjectRootElementCache, buildParameters, loggingService, projectBuildEventContext, isExplicitlyLoaded, sdkResolverService, submissionId); + toolsVersion = "4.0"; } - else if (solutionVersion == 10) /* Orcas */ + else /* Dev 12 and above */ { - loggingService.LogComment(projectBuildEventContext, MessageImportance.Low, "OldWrapperGeneratedOldSolutionVersion", "3.5", solutionVersion); - projectInstances = GenerateSolutionWrapperUsingOldOM(projectFile, globalProperties, "3.5", buildParameters.ProjectRootElementCache, buildParameters, loggingService, projectBuildEventContext, isExplicitlyLoaded, sdkResolverService, submissionId); + toolsVersion = visualStudioVersion.ToString(CultureInfo.InvariantCulture) + ".0"; } - else - { - if ((solutionVersion == 11) || (solutionVersion == 12 && visualStudioVersion == 0)) /* Dev 10 and Dev 11 */ - { - toolsVersion = "4.0"; - } - else /* Dev 12 and above */ - { - toolsVersion = visualStudioVersion.ToString(CultureInfo.InvariantCulture) + ".0"; - } - string toolsVersionToUse = Utilities.GenerateToolsVersionToUse( - explicitToolsVersion: null, - toolsVersionFromProject: FileUtilities.IsSolutionFilterFilename(projectFile) ? "Current" : toolsVersion, - getToolset: buildParameters.GetToolset, - defaultToolsVersion: Constants.defaultSolutionWrapperProjectToolsVersion, - usingDifferentToolsVersionFromProjectFile: out _); - projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersionToUse, loggingService, projectBuildEventContext, targetNames, sdkResolverService, submissionId); - } + string toolsVersionToUse = Utilities.GenerateToolsVersionToUse( + explicitToolsVersion: null, + toolsVersionFromProject: FileUtilities.IsSolutionFilterFilename(projectFile) ? "Current" : toolsVersion, + getToolset: buildParameters.GetToolset, + defaultToolsVersion: Constants.defaultSolutionWrapperProjectToolsVersion, + usingDifferentToolsVersionFromProjectFile: out _); + projectInstances = GenerateSolutionWrapper(projectFile, globalProperties, toolsVersionToUse, loggingService, projectBuildEventContext, targetNames, sdkResolverService, submissionId); } return projectInstances; From e734a1bd46afb0ea10623a0d6ada12b79b7dc316 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 10 Oct 2024 20:05:09 +0200 Subject: [PATCH 04/46] small fix --- NuGet.config | 1 - src/Build.UnitTests/Construction/SolutionFilter_Tests.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/NuGet.config b/NuGet.config index c82f938bd58..659ab421680 100644 --- a/NuGet.config +++ b/NuGet.config @@ -19,7 +19,6 @@ - diff --git a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs index 43a074de6ad..b5e169aa70d 100644 --- a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs @@ -285,7 +285,7 @@ public void ParseSolutionFilter(bool convertToSlnx) private static string ConvertToSlnx(string slnPath) { string slnxPath = slnPath + "x"; - ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(slnPath); + ISolutionSerializer? serializer = SolutionSerializers.GetSerializerByMoniker(slnPath); SolutionModel solutionModel = serializer!.OpenAsync(slnPath, CancellationToken.None).Result; SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); return slnxPath; From 32bc09f06c580cd521fa8df93b879a5c5915e0b6 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 10 Oct 2024 20:09:58 +0200 Subject: [PATCH 05/46] update package --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 91741b441c8..a87b4aa149e 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -78,6 +78,6 @@ - 0.5.26-beta + 1.0.4 From 9fd5e8cdeb366bfd45a967b688f5bb6d22f44c24 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 11 Oct 2024 14:49:30 +0200 Subject: [PATCH 06/46] fix comment --- src/MSBuild/XMake.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 469bd3a5376..6ced0b3e006 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -3552,7 +3552,7 @@ internal static string ProcessProjectSwitch( } } - // Get all files in the current directory that have a sln or slnx extension + // Get all files in the current directory that have a sln-like extension string[] potentialSolutionFiles = getFiles(projectDirectory ?? ".", "*.sln?"); List actualSolutionFiles = new List(); List solutionFilterFiles = new List(); From 221fb65b09784fa5fca88afda58d730d427ee2ef Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada <114938397+surayya-MS@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:27:33 +0200 Subject: [PATCH 07/46] Apply suggestions from code review commit suggestions Co-authored-by: Rainer Sigwald Co-authored-by: Jan Krivanek --- src/Build.UnitTests/Construction/SolutionFilter_Tests.cs | 4 ++-- src/Build/Construction/Solution/SolutionFile.cs | 4 ++-- src/MSBuild.UnitTests/XMake_Tests.cs | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs index b5e169aa70d..e173c47c640 100644 --- a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs @@ -285,8 +285,8 @@ public void ParseSolutionFilter(bool convertToSlnx) private static string ConvertToSlnx(string slnPath) { string slnxPath = slnPath + "x"; - ISolutionSerializer? serializer = SolutionSerializers.GetSerializerByMoniker(slnPath); - SolutionModel solutionModel = serializer!.OpenAsync(slnPath, CancellationToken.None).Result; + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(slnPath).ShouldNotBeNull(); + SolutionModel solutionModel = serializer.OpenAsync(slnPath, CancellationToken.None).Result; SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); return slnxPath; } diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 475ddb9df67..3121cb0bbb5 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -335,7 +335,7 @@ internal void ParseUsingNewParser() /// private void ReadSolutionModel(SolutionModel solutionModel) { - ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(_solutionFile), "ParseSolutionFile() got a null solution file!"); + ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(_solutionFile), "ParseSolutionFile() got a null or empty solution file."); ErrorUtilities.VerifyThrowInternalRooted(_solutionFile); _projectsByGuid = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -409,7 +409,7 @@ private string GetProjectName(SolutionProjectModel projectModel) => !string.IsNullOrEmpty(projectModel.DisplayName) ? projectModel.DisplayName : projectModel.ActualDisplayName; /// - /// Returns a string from Guid in the format "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}". + /// Returns a string from Guid in the format that the old MSBuild solution parser returned. /// private static string ToProjectGuidFormat(Guid id) => id.ToString("B").ToUpper(); diff --git a/src/MSBuild.UnitTests/XMake_Tests.cs b/src/MSBuild.UnitTests/XMake_Tests.cs index c3002f6d502..56b3811f249 100644 --- a/src/MSBuild.UnitTests/XMake_Tests.cs +++ b/src/MSBuild.UnitTests/XMake_Tests.cs @@ -1819,10 +1819,11 @@ public void TestProcessProjectSwitchSlnProjDifferentNames() [Fact] public void TestProcessProjectSwitchSlnxProjDifferentNames() { + string[] projects = { "test.proj", "Different.slnx" }; + string[] extensionsToIgnore = null; + Should.Throw(() => { - string[] projects = { "test.proj", "Different.slnx" }; - string[] extensionsToIgnore = null; IgnoreProjectExtensionsHelper projectHelper = new IgnoreProjectExtensionsHelper(projects); MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles); }); From 62ed51578b236b41c764e92ed480bcb93cc57631 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 11 Oct 2024 15:28:15 +0200 Subject: [PATCH 08/46] fix error message --- src/Build/Construction/Solution/SolutionFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 475ddb9df67..68fefb4044f 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -335,7 +335,7 @@ internal void ParseUsingNewParser() /// private void ReadSolutionModel(SolutionModel solutionModel) { - ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(_solutionFile), "ParseSolutionFile() got a null solution file!"); + ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(_solutionFile), "ReadSolutionModel() got a null solution file!"); ErrorUtilities.VerifyThrowInternalRooted(_solutionFile); _projectsByGuid = new Dictionary(StringComparer.OrdinalIgnoreCase); From 9c2002fd00e52744de87fa51998ac2d99c3d36fa Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 11 Oct 2024 15:28:25 +0200 Subject: [PATCH 09/46] move package version --- eng/dependabot/Packages.props | 2 ++ src/Build/Microsoft.Build.csproj | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/eng/dependabot/Packages.props b/eng/dependabot/Packages.props index 1672382b7c3..4aab28833bb 100644 --- a/eng/dependabot/Packages.props +++ b/eng/dependabot/Packages.props @@ -60,6 +60,8 @@ + + diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index 127f2a85480..9a39ec6bad7 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -39,10 +39,6 @@ - - - - From 94130b93e5ae82332b17243fa6cb9a050f9ff17e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 11 Oct 2024 15:52:27 +0200 Subject: [PATCH 10/46] remove comment --- src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs index db5dd71db6c..1aa7ab49834 100644 --- a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs @@ -86,7 +86,6 @@ public void ProjectWithWebsiteProperties(bool convertToSlnx) solution.ProjectsInOrder[0].ProjectType.ShouldBe(SolutionProjectType.WebProject); solution.ProjectsInOrder[0].ProjectName.ShouldBe(@"C:\WebSites\WebApplication3\"); - // TODO: try set Relative path with a port http://localhost:8080/WebSites/WebApplication3/ solution.ProjectsInOrder[0].RelativePath.ShouldBe(@"C:\WebSites\WebApplication3\"); solution.ProjectsInOrder[0].Dependencies.Count.ShouldBe(2); solution.ProjectsInOrder[0].ParentProjectGuid.ShouldBeNull(); From 09a5a7b6b036605abee3e4226ea25653d5cb0f2a Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 11 Oct 2024 15:57:28 +0200 Subject: [PATCH 11/46] small fix --- src/Build/Construction/Solution/SolutionFile.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index fb2c9f9d757..dd9497fe1be 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -305,13 +305,6 @@ internal void ParseUsingNewParser() SolutionModel solutionModel = serializer.OpenAsync(FullPath, CancellationToken.None).Result; ReadSolutionModel(solutionModel); } - catch (AggregateException aggregateException) - { - ProjectFileErrorUtilities.ThrowInvalidProjectFile( - new BuildEventFileInfo(FullPath), - $"InvalidProjectFile", - string.Join(" ", aggregateException.InnerExceptions.Select(inner => inner.Message))); - } catch (Exception ex) { ProjectFileErrorUtilities.ThrowInvalidProjectFile( From 63be4f4700b20ae5c8c82d94537e1b3f75fbd581 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 11 Oct 2024 16:49:03 +0200 Subject: [PATCH 12/46] small change in xmake tests --- src/MSBuild.UnitTests/XMake_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MSBuild.UnitTests/XMake_Tests.cs b/src/MSBuild.UnitTests/XMake_Tests.cs index 56b3811f249..6d2eddab546 100644 --- a/src/MSBuild.UnitTests/XMake_Tests.cs +++ b/src/MSBuild.UnitTests/XMake_Tests.cs @@ -1819,7 +1819,7 @@ public void TestProcessProjectSwitchSlnProjDifferentNames() [Fact] public void TestProcessProjectSwitchSlnxProjDifferentNames() { - string[] projects = { "test.proj", "Different.slnx" }; + string[] projects = ["test.proj", "Different.slnx"]; string[] extensionsToIgnore = null; Should.Throw(() => From 73b84435047ad8b5b66ad5ebc484dc01a1be426d Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 11 Oct 2024 16:51:57 +0200 Subject: [PATCH 13/46] apply same suggestion to the other xmake tests --- src/MSBuild.UnitTests/XMake_Tests.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/MSBuild.UnitTests/XMake_Tests.cs b/src/MSBuild.UnitTests/XMake_Tests.cs index 6d2eddab546..fb588b1615b 100644 --- a/src/MSBuild.UnitTests/XMake_Tests.cs +++ b/src/MSBuild.UnitTests/XMake_Tests.cs @@ -1876,10 +1876,11 @@ public void TestProcessProjectSwitchTwoSolutions() [Fact] public void TestProcessProjectSwitchSlnAndSlnx() { + string[] projects = ["test.slnx", "Different.sln"]; + string[] extensionsToIgnore = null; + Should.Throw(() => - { - string[] projects = { "test.slnx", "Different.sln" }; - string[] extensionsToIgnore = null; + { IgnoreProjectExtensionsHelper projectHelper = new IgnoreProjectExtensionsHelper(projects); MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles); }); @@ -1887,10 +1888,11 @@ public void TestProcessProjectSwitchSlnAndSlnx() [Fact] public void TestProcessProjectSwitchTwoSlnx() { + string[] projects = ["test.slnx", "Different.slnx"]; + string[] extensionsToIgnore = null; + Should.Throw(() => { - string[] projects = { "test.slnx", "Different.slnx" }; - string[] extensionsToIgnore = null; IgnoreProjectExtensionsHelper projectHelper = new IgnoreProjectExtensionsHelper(projects); MSBuildApp.ProcessProjectSwitch(Array.Empty(), extensionsToIgnore, projectHelper.GetFiles); }); From 1bd2a25da9a9526a757562fa3ce260d0a6f38419 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Fri, 11 Oct 2024 17:18:02 +0200 Subject: [PATCH 14/46] refactor ParseSolutionHelper --- .../Construction/SolutionFile_Tests.cs | 39 +++++++---------- .../SolutionFile_NewParser_Tests.cs | 42 ++++++++----------- 2 files changed, 32 insertions(+), 49 deletions(-) diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index 4e1377c535d..64588c540b4 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -1220,33 +1220,24 @@ public void ParseSolutionWithComments() private static SolutionFile ParseSolutionHelper(string solutionFileContents, bool convertToSlnx = false) { solutionFileContents = solutionFileContents.Replace('\'', '"'); - string solutionPath = FileUtilities.GetTemporaryFileName(".sln"); - string slnxPath = solutionPath + "x"; - try - { - File.WriteAllText(solutionPath, solutionFileContents); - if (convertToSlnx) - { - ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath); - SolutionModel solutionModel = serializer.OpenAsync(solutionPath, CancellationToken.None).Result; - SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); - - SolutionFile slnx = SolutionFile.Parse(slnxPath); - return slnx; - } - - SolutionFile sln = SolutionFile.Parse(solutionPath); - return sln; - } - finally + + using (TestEnvironment testEnvironment = TestEnvironment.Create()) { - File.Delete(solutionPath); + TransientTestFile sln = testEnvironment.CreateFile(FileUtilities.GetTemporaryFileName(".sln"), solutionFileContents); - if (convertToSlnx) - { - File.Delete(slnxPath); - } + string solutionPath = convertToSlnx ? ConvertToSlnx(sln.Path) : sln.Path; + + return SolutionFile.Parse(solutionPath); } } + + private static string ConvertToSlnx(string slnPath) + { + string slnxPath = slnPath + "x"; + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(slnPath).ShouldNotBeNull(); + SolutionModel solutionModel = serializer.OpenAsync(slnPath, CancellationToken.None).Result; + SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); + return slnxPath; + } } } diff --git a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs index 1aa7ab49834..6db6e939e56 100644 --- a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs @@ -132,34 +132,26 @@ public void ProjectWithWebsiteProperties(bool convertToSlnx) internal static SolutionFile ParseSolutionHelper(string solutionFileContents, bool convertToSlnx = false) { solutionFileContents = solutionFileContents.Replace('\'', '"'); - string solutionPath = FileUtilities.GetTemporaryFileName(".sln"); - string slnxPath = solutionPath + "x"; - try - { - File.WriteAllText(solutionPath, solutionFileContents); - if (convertToSlnx) - { - ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath); - SolutionModel solutionModel = serializer.OpenAsync(solutionPath, CancellationToken.None).Result; - SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); - - SolutionFile slnx = new SolutionFile { FullPath = slnxPath }; - slnx.ParseUsingNewParser(); - return slnx; - } - - SolutionFile sln = SolutionFile.Parse(solutionPath); - return sln; - } - finally + + using (TestEnvironment testEnvironment = TestEnvironment.Create()) { - File.Delete(solutionPath); + TransientTestFile sln = testEnvironment.CreateFile(FileUtilities.GetTemporaryFileName(".sln"), solutionFileContents); + + string solutionPath = convertToSlnx ? ConvertToSlnx(sln.Path) : sln.Path; - if (convertToSlnx) - { - File.Delete(slnxPath); - } + SolutionFile solutionFile = new SolutionFile { FullPath = solutionPath }; + solutionFile.ParseUsingNewParser(); + return solutionFile; } } + + private static string ConvertToSlnx(string slnPath) + { + string slnxPath = slnPath + "x"; + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(slnPath).ShouldNotBeNull(); + SolutionModel solutionModel = serializer.OpenAsync(slnPath, CancellationToken.None).Result; + SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); + return slnxPath; + } } } From 7a9ff3ac3e8dc14b3602769479032767079d7a65 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 14 Oct 2024 16:01:50 +0200 Subject: [PATCH 15/46] rename to Solution_OldParser_Tests; move old parser specific tests to Solution_OldParser_Tests --- .../Construction/SolutionFile_Tests.cs | 414 ------------------ ...sts.cs => SolutionFile_OldParser_Tests.cs} | 131 +++++- .../SolutionProjectGenerator_Tests.cs | 54 +-- 3 files changed, 156 insertions(+), 443 deletions(-) rename src/Build.UnitTests/Construction/{SolutionFile_Tests.cs => SolutionFile_OldParser_Tests.cs} (95%) diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index 64588c540b4..70eb1417ae4 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -104,42 +104,6 @@ public void ParseSolution_VC2(bool convertToSlnx) } } - /// - /// A slightly more complicated test where there is some different whitespace. - /// - [Fact] - public void ParseSolutionWithDifferentSpacing() - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project(' { Project GUID} ') = ' Project name ', ' Relative path to project file ' , ' {0ABED153-9451-483C-8140-9E8D7306B216} ' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|AnyCPU = Debug|AnyCPU - Release|AnyCPU = Release|AnyCPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.ActiveCfg = Debug|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.Build.0 = Debug|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.ActiveCfg = Release|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.Build.0 = Release|AnyCPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - EndGlobal - "; - - SolutionFile solution = ParseSolutionHelper(solutionFileContents); - - Assert.Equal("Project name", solution.ProjectsInOrder[0].ProjectName); - Assert.Equal("Relative path to project file", solution.ProjectsInOrder[0].RelativePath); - Assert.Equal("{0ABED153-9451-483C-8140-9E8D7306B216}", solution.ProjectsInOrder[0].ProjectGuid); - } - /// /// Solution with an empty project name. This is somewhat malformed, but we should /// still behave reasonably instead of crashing. @@ -177,118 +141,6 @@ public void ParseSolution_EmptyProjectName() Assert.Equal("{0ABED153-9451-483C-8140-9E8D7306B216}", solution.ProjectsInOrder[0].ProjectGuid); } - /// - /// Test some characters that are valid in a file name but that also could be - /// considered a delimiter by a parser. Does quoting work for special characters? - /// - [Fact] - public void ParseSolutionWhereProjectNameHasSpecialCharacters() - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{Project GUID}') = 'MyProject,(=IsGreat)', 'Relative path to project file' , '{0ABED153-9451-483C-8140-9E8D7306B216}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|AnyCPU = Debug|AnyCPU - Release|AnyCPU = Release|AnyCPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.ActiveCfg = Debug|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.Build.0 = Debug|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.ActiveCfg = Release|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.Build.0 = Release|AnyCPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - EndGlobal - "; - - SolutionFile solution = ParseSolutionHelper(solutionFileContents); - - Assert.Equal("MyProject,(=IsGreat)", solution.ProjectsInOrder[0].ProjectName); - Assert.Equal("Relative path to project file", solution.ProjectsInOrder[0].RelativePath); - Assert.Equal("{0ABED153-9451-483C-8140-9E8D7306B216}", solution.ProjectsInOrder[0].ProjectGuid); - } - - /// - /// Ensure that a bogus version stamp in the .SLN file results in an - /// InvalidProjectFileException. - /// - [Fact] - public void BadVersionStamp() - { - Assert.Throws(() => - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version a.b - # Visual Studio 2005 - "; - - ParseSolutionHelper(solutionFileContents); - }); - } - /// - /// Expected version numbers less than 7 to cause an invalid project file exception. - /// - [Fact] - public void VersionTooLow() - { - Assert.Throws(() => - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 6.0 - # Visual Studio 2005 - "; - - ParseSolutionHelper(solutionFileContents); - }); - } - /// - /// Test to parse a very basic .sln file to validate that description property in a solution file - /// is properly handled. - /// - [Fact] - public void ParseSolutionFileWithDescriptionInformation() - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'AnyProject', 'AnyProject\AnyProject.csproj', '{2CAB0FBD-15D8-458B-8E63-1B5B840E9798}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - Description = Some description of this solution - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2CAB0FBD-15D8-458B-8E63-1B5B840E9798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2CAB0FBD-15D8-458B-8E63-1B5B840E9798}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2CAB0FBD-15D8-458B-8E63-1B5B840E9798}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2CAB0FBD-15D8-458B-8E63-1B5B840E9798}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - EndGlobal - "; - try - { - ParseSolutionHelper(solutionFileContents); - } - catch (Exception ex) - { - Assert.Fail("Failed to parse solution containing description information. Error: " + ex.Message); - } - } - /// /// Tests the parsing of a very basic .SLN file with three independent projects. /// @@ -512,140 +364,6 @@ public void SolutionFoldersSlnx() Assert.NotNull(classLibrary2.ParentProjectGuid); } - /// - /// Exercises shared projects. - /// - [Fact] - public void SharedProjects() - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 12.00 - # Visual Studio 15 - VisualStudioVersion = 15.0.27610.1 - MinimumVisualStudioVersion = 10.0.40219.1 - Project('{D954291E-2A0B-460D-934E-DC6B0785DB48}') = 'SharedProject1', 'SharedProject1\SharedProject1.shproj', '{14686F51-D0C2-4832-BBAA-6FBAEC676995}' - EndProject - Project('{D954291E-2A0B-460D-934E-DC6B0785DB48}') = 'SharedProject2', 'SharedProject2\SharedProject2.shproj', '{BAE750E8-4656-4947-B06B-3961E1051DF7}' - EndProject - Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{3A0EC360-A42A-417F-BDEF-619682CF6119}' - EndProject - Project('{F184B08F-C81C-45F6-A57F-5ABD9991F28F}') = 'ClassLibrary2', 'ClassLibrary2\ClassLibrary2.vbproj', '{6DEF6DE8-FBF0-4240-B469-282DEE87899C}' - EndProject - Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - SharedProject1\SharedProject1.projitems*{14686f51-d0c2-4832-bbaa-6fbaec676995}*SharedItemsImports = 13 - SharedProject1\SharedProject1.projitems*{3a0ec360-a42a-417f-bdef-619682cf6119}*SharedItemsImports = 4 - SharedProject2\SharedProject2.projitems*{6def6de8-fbf0-4240-b469-282dee87899c}*SharedItemsImports = 4 - SharedProject2\SharedProject2.projitems*{bae750e8-4656-4947-b06b-3961e1051df7}*SharedItemsImports = 13 - EndGlobalSection - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3A0EC360-A42A-417F-BDEF-619682CF6119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A0EC360-A42A-417F-BDEF-619682CF6119}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A0EC360-A42A-417F-BDEF-619682CF6119}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A0EC360-A42A-417F-BDEF-619682CF6119}.Release|Any CPU.Build.0 = Release|Any CPU - {6DEF6DE8-FBF0-4240-B469-282DEE87899C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6DEF6DE8-FBF0-4240-B469-282DEE87899C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6DEF6DE8-FBF0-4240-B469-282DEE87899C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6DEF6DE8-FBF0-4240-B469-282DEE87899C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1B671EF6-A62A-4497-8351-3EE8679CA86F} - EndGlobalSection - EndGlobal - "; - - SolutionFile solution = ParseSolutionHelper(solutionFileContents); - - Assert.Equal(4, solution.ProjectsInOrder.Count); - - Assert.Equal(@"SharedProject1\SharedProject1.shproj", solution.ProjectsInOrder[0].RelativePath); - Assert.Equal("{14686F51-D0C2-4832-BBAA-6FBAEC676995}", solution.ProjectsInOrder[0].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[0].Dependencies); - Assert.Null(solution.ProjectsInOrder[0].ParentProjectGuid); - - Assert.Equal(@"SharedProject2\SharedProject2.shproj", solution.ProjectsInOrder[1].RelativePath); - Assert.Equal("{BAE750E8-4656-4947-B06B-3961E1051DF7}", solution.ProjectsInOrder[1].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[1].Dependencies); - Assert.Null(solution.ProjectsInOrder[1].ParentProjectGuid); - - Assert.Equal(@"ClassLibrary1\ClassLibrary1.csproj", solution.ProjectsInOrder[2].RelativePath); - Assert.Equal("{3A0EC360-A42A-417F-BDEF-619682CF6119}", solution.ProjectsInOrder[2].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[2].Dependencies); - Assert.Null(solution.ProjectsInOrder[2].ParentProjectGuid); - - Assert.Equal(@"ClassLibrary2\ClassLibrary2.vbproj", solution.ProjectsInOrder[3].RelativePath); - Assert.Equal("{6DEF6DE8-FBF0-4240-B469-282DEE87899C}", solution.ProjectsInOrder[3].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[3].Dependencies); - Assert.Null(solution.ProjectsInOrder[3].ParentProjectGuid); - } - - /// - /// Tests situation where there's a nonexistent project listed in the solution folders. We should - /// error with a useful message. - /// - [Fact] - public void MissingNestedProject() - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{2150E333-8FDC-42A3-9474-1A3956D46DE8}') = 'MySlnFolder', 'MySlnFolder', '{E0F97730-25D2-418A-A7BD-02CAFDC6E470}' - EndProject - Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'MyPhysicalFolder\ClassLibrary1\ClassLibrary1.csproj', '{A5EE8128-B08E-4533-86C5-E46714981680}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Debug|Any CPU.Build.0 = Debug|Any CPU - {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Release|Any CPU.ActiveCfg = Release|Any CPU - {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Release|Any CPU.Build.0 = Release|Any CPU - {A5EE8128-B08E-4533-86C5-E46714981680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5EE8128-B08E-4533-86C5-E46714981680}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5EE8128-B08E-4533-86C5-E46714981680}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5EE8128-B08E-4533-86C5-E46714981680}.Release|Any CPU.Build.0 = Release|Any CPU - {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {A5EE8128-B08E-4533-86C5-E46714981680} = {E0F97730-25D2-418A-A7BD-02CAFDC6E470} - {2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B} = {E0F97730-25D2-418A-A7BD-02CAFDC6E470} - EndGlobalSection - EndGlobal - "; - - try - { - ParseSolutionHelper(solutionFileContents); - } - catch (InvalidProjectFileException e) - { - Assert.Equal("MSB5023", e.ErrorCode); - Assert.Contains("{2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B}", e.Message); - return; - } - - // Should not get here - Assert.Fail(); - } - /// /// Verifies that hand-coded project-to-project dependencies listed in the .SLN file /// are correctly recognized by the solution parser. @@ -861,85 +579,6 @@ public void ParseSolutionConfigurationsNoMixedPlatform(bool convertToSlnx) Assert.Equal("Any CPU", solution.GetDefaultPlatformName()); // "Default solution platform" } - /// - /// Test some invalid cases for solution configuration parsing. - /// There can be only one '=' character in a sln cfg entry, separating two identical names - /// - [Fact] - public void ParseInvalidSolutionConfigurations1() - { - Assert.Throws(() => - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any=CPU = Debug|Any=CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - EndGlobal - "; - - ParseSolutionHelper(solutionFileContents); - }); - } - /// - /// Test some invalid cases for solution configuration parsing - /// There can be only one '=' character in a sln cfg entry, separating two identical names - /// - [Fact] - public void ParseInvalidSolutionConfigurations2() - { - Assert.Throws(() => - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Something|Else - Release|Any CPU = Release|Any CPU - EndGlobalSection - EndGlobal - "; - - ParseSolutionHelper(solutionFileContents); - }); - } - /// - /// Test some invalid cases for solution configuration parsing - /// Solution configurations must include the platform part - /// - [Fact] - public void ParseInvalidSolutionConfigurations3() - { - Assert.Throws(() => - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug = Debug - Release|Any CPU = Release|Any CPU - EndGlobalSection - EndGlobal - "; - - ParseSolutionHelper(solutionFileContents); - }); - } - /// /// Make sure the project configurations in solution configurations get parsed correctly /// for a simple mixed C#/VC solution @@ -1160,59 +799,6 @@ public void ParseProjectConfigurationsInSolutionConfigurationsSlnx() Assert.False(classLibrary1.ProjectConfigurations["Release|Any CPU"].IncludeInBuild); } - /// - /// Parse solution file with comments - /// - [Fact] - public void ParseSolutionWithComments() - { - const string solutionFileContent = @" - Microsoft Visual Studio Solution File, Format Version 12.00 - # Visual Studio Version 16 - VisualStudioVersion = 16.0.29123.89 - MinimumVisualStudioVersion = 10.0.40219.1 - Project('{9A19103F-16F7-4668-BE54-9A1E7A4F7556}') = 'SlnCommentTest', 'SlnCommentTest.csproj', '{00000000-0000-0000-FFFF-FFFFFFFFFFFF}' - EndProject - Project('{2150E333-8FDC-42A3-9474-1A3956D46DE8}') = 'Solution Items', 'Solution Items', '{054DED3B-B890-4652-B449-839F581E5D86}' - ProjectSection(SolutionItems) = preProject - SlnFile.txt = SlnFile.txt - EndProjectSection - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {00000000-0000-0000-FFFF-FFFFFFFFFFFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00000000-0000-0000-FFFF-FFFFFFFFFFFF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {00000000-0000-0000-FFFF-FFFFFFFFFFFF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {00000000-0000-0000-FFFF-FFFFFFFFFFFF}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {FFFFFFFF-FFFF-FFFF-0000-000000000000} - EndGlobalSection - EndGlobal - "; - - StringBuilder stringBuilder = new StringBuilder(); - - // Put comment between all lines - const string comment = "\t# comment"; - string[] lines = solutionFileContent.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); - for (int i = 0; i < lines.Length; i++) - { - stringBuilder.AppendLine(comment); - stringBuilder.AppendLine(lines[i]); - } - stringBuilder.AppendLine(comment); - - Should.NotThrow(() => ParseSolutionHelper(stringBuilder.ToString())); - } - /// /// Helper method to create a SolutionFile object, and call it to parse the SLN file /// represented by the string contents passed in. Optionally can convert the SLN to SLNX and then parse the solution. diff --git a/src/Build.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.UnitTests/Construction/SolutionFile_OldParser_Tests.cs similarity index 95% rename from src/Build.UnitTests/Construction/SolutionFile_Tests.cs rename to src/Build.UnitTests/Construction/SolutionFile_OldParser_Tests.cs index 79bb93a4703..ab51906b222 100644 --- a/src/Build.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFile_OldParser_Tests.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Text; using Microsoft.Build.Construction; using Microsoft.Build.Exceptions; using Microsoft.Build.Shared; @@ -16,11 +17,11 @@ namespace Microsoft.Build.UnitTests.Construction { - public class SolutionFile_Tests + public class SolutionFile_OldParser_Tests { public ITestOutputHelper TestOutputHelper { get; } - public SolutionFile_Tests(ITestOutputHelper testOutputHelper) + public SolutionFile_OldParser_Tests(ITestOutputHelper testOutputHelper) { TestOutputHelper = testOutputHelper; } @@ -104,6 +105,42 @@ public void ParseFirstProjectLineWithDifferentSpacing() proj.ProjectGuid.ShouldBe("Unique name-GUID"); } + /// + /// A slightly more complicated test where there is some different whitespace. + /// + [Fact] + public void ParseSolutionWithDifferentSpacing() + { + string solutionFileContents = + @" + Microsoft Visual Studio Solution File, Format Version 9.00 + # Visual Studio 2005 + Project(' { Project GUID} ') = ' Project name ', ' Relative path to project file ' , ' {0ABED153-9451-483C-8140-9E8D7306B216} ' + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|AnyCPU = Debug|AnyCPU + Release|AnyCPU = Release|AnyCPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.ActiveCfg = Debug|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.Build.0 = Debug|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.ActiveCfg = Release|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.Build.0 = Release|AnyCPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + "; + + SolutionFile solution = ParseSolutionHelper(solutionFileContents); + + Assert.Equal("Project name", solution.ProjectsInOrder[0].ProjectName); + Assert.Equal("Relative path to project file", solution.ProjectsInOrder[0].RelativePath); + Assert.Equal("{0ABED153-9451-483C-8140-9E8D7306B216}", solution.ProjectsInOrder[0].ProjectGuid); + } + /// /// First project line with an empty project name. This is somewhat malformed, but we should /// still behave reasonably instead of crashing. @@ -687,6 +724,43 @@ public void ParseFirstProjectLineWhereProjectNameHasSpecialCharacters() proj.ProjectGuid.ShouldBe("Unique name-GUID"); } + /// + /// Test some characters that are valid in a file name but that also could be + /// considered a delimiter by a parser. Does quoting work for special characters? + /// + [Fact] + public void ParseSolutionWhereProjectNameHasSpecialCharacters() + { + string solutionFileContents = + @" + Microsoft Visual Studio Solution File, Format Version 9.00 + # Visual Studio 2005 + Project('{Project GUID}') = 'MyProject,(=IsGreat)', 'Relative path to project file' , '{0ABED153-9451-483C-8140-9E8D7306B216}' + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|AnyCPU = Debug|AnyCPU + Release|AnyCPU = Release|AnyCPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.ActiveCfg = Debug|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.Build.0 = Debug|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.ActiveCfg = Release|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.Build.0 = Release|AnyCPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + "; + + SolutionFile solution = ParseSolutionHelper(solutionFileContents); + + Assert.Equal("MyProject,(=IsGreat)", solution.ProjectsInOrder[0].ProjectName); + Assert.Equal("Relative path to project file", solution.ProjectsInOrder[0].RelativePath); + Assert.Equal("{0ABED153-9451-483C-8140-9E8D7306B216}", solution.ProjectsInOrder[0].ProjectGuid); + } + /// /// Test some characters that are valid in a file name but that also could be /// considered a delimiter by a parser. Does quoting work for special characters? @@ -2355,5 +2429,58 @@ public void ParseSolutionWithParentedPaths() solution.ProjectsInOrder[0].AbsolutePath.ShouldBe(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(solution.FullPath)!, expectedRelativePath))); solution.ProjectsInOrder[0].ProjectGuid.ShouldBe("{0ABED153-9451-483C-8140-9E8D7306B216}"); } + + /// + /// Parse solution file with comments + /// + [Fact] + public void ParseSolutionWithComments() + { + const string solutionFileContent = @" + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio Version 16 + VisualStudioVersion = 16.0.29123.89 + MinimumVisualStudioVersion = 10.0.40219.1 + Project('{9A19103F-16F7-4668-BE54-9A1E7A4F7556}') = 'SlnCommentTest', 'SlnCommentTest.csproj', '{00000000-0000-0000-FFFF-FFFFFFFFFFFF}' + EndProject + Project('{2150E333-8FDC-42A3-9474-1A3956D46DE8}') = 'Solution Items', 'Solution Items', '{054DED3B-B890-4652-B449-839F581E5D86}' + ProjectSection(SolutionItems) = preProject + SlnFile.txt = SlnFile.txt + EndProjectSection + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {00000000-0000-0000-FFFF-FFFFFFFFFFFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00000000-0000-0000-FFFF-FFFFFFFFFFFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00000000-0000-0000-FFFF-FFFFFFFFFFFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00000000-0000-0000-FFFF-FFFFFFFFFFFF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FFFFFFFF-FFFF-FFFF-0000-000000000000} + EndGlobalSection + EndGlobal + "; + + StringBuilder stringBuilder = new StringBuilder(); + + // Put comment between all lines + const string comment = "\t# comment"; + string[] lines = solutionFileContent.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < lines.Length; i++) + { + stringBuilder.AppendLine(comment); + stringBuilder.AppendLine(lines[i]); + } + stringBuilder.AppendLine(comment); + + Should.NotThrow(() => ParseSolutionHelper(stringBuilder.ToString())); + } } } diff --git a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs index 8e28604557b..1da28433edb 100644 --- a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs @@ -355,7 +355,7 @@ public void EmitToolsVersionAttributeToInMemoryProject9() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, "3.5", _buildEventContext, CreateMockLoggingService()); @@ -390,7 +390,7 @@ public void EmitToolsVersionAttributeToInMemoryProject10() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, "3.5", _buildEventContext, CreateMockLoggingService()); @@ -419,7 +419,7 @@ public void DefaultSubToolsetIfSolutionVersionSubToolsetDoesntExist() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, _buildEventContext, CreateMockLoggingService()); @@ -461,7 +461,7 @@ public void SubToolsetSetBySolutionVersion() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, _buildEventContext, CreateMockLoggingService()); @@ -496,7 +496,7 @@ public void SolutionBasedSubToolsetVersionOverriddenByEnvironment() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, _buildEventContext, CreateMockLoggingService()); @@ -756,7 +756,7 @@ public void SolutionWithMissingDependencies() EndGlobal ".Replace("`", "\""); - SolutionFile sp = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile sp = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(sp, null, null, _buildEventContext, CreateMockLoggingService()); }); } @@ -819,7 +819,7 @@ public void SolutionConfigurationWithDependencies() EndGlobal ".Replace("`", "\""); - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectRootElement projectXml = ProjectRootElement.Create(); @@ -1065,7 +1065,7 @@ public void TestAddPropertyGroupForSolutionConfiguration() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectRootElement projectXml = ProjectRootElement.Create(); @@ -1133,7 +1133,7 @@ public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionSe EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectRootElement projectXml = ProjectRootElement.Create(); @@ -1176,7 +1176,7 @@ public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionNo EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectRootElement projectXml = ProjectRootElement.Create(); @@ -1235,7 +1235,7 @@ public void Regress751742_SkipNonexistentProjects() "; // We're not passing in a /tv:xx switch, so the solution project will have tools version 2.0 - var solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + var solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); var instance = SolutionProjectGenerator.Generate(solution, null, ObjectModelHelpers.MSBuildDefaultToolsVersion, _buildEventContext, CreateMockLoggingService())[0]; @@ -1301,7 +1301,7 @@ public void ToolsVersionOverrideShouldBeSpecifiedOnMSBuildTaskInvocations() "; // We're not passing in a /tv:xx switch, so the solution project will have tools version 2.0 - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, ObjectModelHelpers.MSBuildDefaultToolsVersion, _buildEventContext, CreateMockLoggingService()); @@ -1375,7 +1375,7 @@ public void SolutionWithDependenciesHasCorrectToolsVersionInMetaprojs() "; // We're not passing in a /tv:xx switch, so the solution project will have tools version 2.0 - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); string[] solutionToolsVersions = { "4.0", ObjectModelHelpers.MSBuildDefaultToolsVersion }; @@ -1434,7 +1434,7 @@ public void ToolsVersionOverrideCausesToolsetRedirect() EndGlobalSection EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); bool caughtException = false; try @@ -1480,7 +1480,7 @@ public void TestDisambiguateProjectTargetName() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, BuildEventContext.Invalid, CreateMockLoggingService()); @@ -1552,7 +1552,7 @@ public void TestConfigurationPlatformDefaults1() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); // These used to exist on the engine, but now need to be passed in explicitly IDictionary globalProperties = new Dictionary(); @@ -1588,7 +1588,7 @@ public void TestConfigurationPlatformDefaults2() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, BuildEventContext.Invalid, CreateMockLoggingService()); @@ -1787,7 +1787,7 @@ public void TestPredictSolutionConfigurationName() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); IDictionary globalProperties = new Dictionary(); @@ -1832,7 +1832,7 @@ public void SolutionGeneratorEscapingProjectFilePaths() EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); // Creating a ProjectRootElement shouldn't affect the ProjectCollection at all Assert.Empty(ProjectCollection.GlobalProjectCollection.LoadedProjects); @@ -1887,7 +1887,7 @@ public void SolutionGeneratorCanEmitSolutions() { Environment.SetEnvironmentVariable("MSBuildEmitSolution", "1"); - solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); // Creating a ProjectRootElement shouldn't affect the ProjectCollection at all Assert.Empty(ProjectCollection.GlobalProjectCollection.LoadedProjects); @@ -1969,7 +1969,7 @@ public void TestSkipInvalidConfigurationsCase() globalProperties["Configuration"] = "Nonexistent"; globalProperties["SkipInvalidConfigurations"] = "true"; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionContents.Replace('\'', '"')); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionContents.Replace('\'', '"')); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, globalProperties, null, BuildEventContext.Invalid, CreateMockLoggingService()); ProjectInstance msbuildProject = instances[0]; @@ -2226,7 +2226,7 @@ public void TestTargetFrameworkVersionGreaterThan4() globalProperties["Configuration"] = "Release"; globalProperties["SkipInvalidConfigurations"] = "true"; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents.Replace('\'', '"')); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents.Replace('\'', '"')); using ProjectCollection collection = new ProjectCollection(); collection.RegisterLogger(logger); @@ -2259,7 +2259,7 @@ public void TestTargetFrameworkVersionGreaterThan4() [Fact] public void CustomTargetNamesAreInInMetaproj() { - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper( + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper( @" Microsoft Visual Studio Solution File, Format Version 14.00 # Visual Studio 2015 @@ -2309,7 +2309,7 @@ public void DisambiguatedTargetNamesAreInInMetaproj() { foreach(string projectName in ProjectInSolution.projectNamesToDisambiguate) { - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper( + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper( $$""" Microsoft Visual Studio Solution File, Format Version 14.00 # Visual Studio 2015 @@ -2353,7 +2353,7 @@ public void DisambiguatedTargetNamesAreInInMetaproj() [InlineData(true)] public void IllegalUserTargetNamesDoNotThrow(bool forceCaseDifference) { - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper( + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper( @" Microsoft Visual Studio Solution File, Format Version 14.00 # Visual Studio 2015 @@ -2653,7 +2653,7 @@ public void AbsolutePathWorksForUnsupportedPaths(string relativePath) Project(""{{E24C65DC-7377-472B-9ABA-BC803B73C61A}}"") = ""WebSite1"", ""{relativePath}"", ""{{{{96E0707C-2E9C-4704-946F-FA583147737F}}}}"" EndProject"; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInSolution projectInSolution = solution.ProjectsInOrder.ShouldHaveSingleItem(); @@ -2729,7 +2729,7 @@ private ProjectInstance CreateVenusSolutionProject(IDictionary g EndGlobal "; - SolutionFile solution = SolutionFile_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, globalProperties, toolsVersion, BuildEventContext.Invalid, CreateMockLoggingService()); From 8e40124da6ccd24244f3f76e0e84273a432ced5d Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 14 Oct 2024 16:04:06 +0200 Subject: [PATCH 16/46] refactor SolutionFile_Tests --- .../Construction/SolutionFile_Tests.cs | 234 +++++------------- 1 file changed, 57 insertions(+), 177 deletions(-) diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index 70eb1417ae4..d133e45c3e4 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -2,18 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; -using System.Threading; using Microsoft.Build.Construction; using Microsoft.Build.Exceptions; using Microsoft.Build.Shared; -using Microsoft.VisualStudio.SolutionPersistence.Model; using Microsoft.VisualStudio.SolutionPersistence.Serializer; using Microsoft.VisualStudio.SolutionPersistence; using Shouldly; using Xunit; +using Microsoft.VisualStudio.SolutionPersistence.Model; +using System.Threading; using System.Linq; #nullable disable @@ -34,7 +35,7 @@ public void ParseSolution_VC() Assert.Throws(() => { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}') = 'Project name.vcproj', 'Relative path\to\Project name.vcproj', '{0ABED153-9451-483C-8140-9E8D7306B216}' @@ -54,7 +55,7 @@ public void ParseSolution_VC() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; ParseSolutionHelper(solutionFileContents); Assert.Fail("Should not get here"); @@ -111,8 +112,10 @@ public void ParseSolution_VC2(bool convertToSlnx) [Fact] public void ParseSolution_EmptyProjectName() { - string solutionFileContents = - @" + Assert.Throws(() => + { + string solutionFileContents = + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{Project GUID}') = '', 'src\.proj', '{0ABED153-9451-483C-8140-9E8D7306B216}' @@ -132,13 +135,10 @@ public void ParseSolution_EmptyProjectName() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; - - SolutionFile solution = ParseSolutionHelper(solutionFileContents); + """; - Assert.StartsWith("EmptyProjectName", solution.ProjectsInOrder[0].ProjectName); - Assert.Equal("src\\.proj", solution.ProjectsInOrder[0].RelativePath); - Assert.Equal("{0ABED153-9451-483C-8140-9E8D7306B216}", solution.ProjectsInOrder[0].ProjectGuid); + SolutionFile solution = ParseSolutionHelper(solutionFileContents); + }); } /// @@ -217,88 +217,10 @@ public void BasicSolution(bool convertToSlnx) /// solution folders will get correctly uniquified. /// For the new parser, solution folders are not included to ProjectsInOrder or ProjectsByGuid. /// - [Fact] - public void SolutionFolders() - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{34E0D07D-CF8F-459D-9449-C4188D8C5564}' - EndProject - Project('{2150E333-8FDC-42A3-9474-1A3956D46DE8}') = 'MySlnFolder', 'MySlnFolder', '{E0F97730-25D2-418A-A7BD-02CAFDC6E470}' - EndProject - Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'MyPhysicalFolder\ClassLibrary1\ClassLibrary1.csproj', '{A5EE8128-B08E-4533-86C5-E46714981680}' - EndProject - Project('{2150E333-8FDC-42A3-9474-1A3956D46DE8}') = 'MySubSlnFolder', 'MySubSlnFolder', '{2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B}' - EndProject - Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary2', 'ClassLibrary2\ClassLibrary2.csproj', '{6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Debug|Any CPU.Build.0 = Debug|Any CPU - {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Release|Any CPU.ActiveCfg = Release|Any CPU - {34E0D07D-CF8F-459D-9449-C4188D8C5564}.Release|Any CPU.Build.0 = Release|Any CPU - {A5EE8128-B08E-4533-86C5-E46714981680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A5EE8128-B08E-4533-86C5-E46714981680}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A5EE8128-B08E-4533-86C5-E46714981680}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A5EE8128-B08E-4533-86C5-E46714981680}.Release|Any CPU.Build.0 = Release|Any CPU - {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {A5EE8128-B08E-4533-86C5-E46714981680} = {E0F97730-25D2-418A-A7BD-02CAFDC6E470} - {2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B} = {E0F97730-25D2-418A-A7BD-02CAFDC6E470} - {6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4} = {2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B} - EndGlobalSection - EndGlobal - "; - - SolutionFile solution = ParseSolutionHelper(solutionFileContents); - - Assert.Equal(5, solution.ProjectsInOrder.Count); - - Assert.Equal(@"ClassLibrary1\ClassLibrary1.csproj", solution.ProjectsInOrder[0].RelativePath); - Assert.Equal("{34E0D07D-CF8F-459D-9449-C4188D8C5564}", solution.ProjectsInOrder[0].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[0].Dependencies); - Assert.Null(solution.ProjectsInOrder[0].ParentProjectGuid); - - Assert.Equal("{E0F97730-25D2-418A-A7BD-02CAFDC6E470}", solution.ProjectsInOrder[1].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[1].Dependencies); - Assert.Null(solution.ProjectsInOrder[1].ParentProjectGuid); - - Assert.Equal(@"MyPhysicalFolder\ClassLibrary1\ClassLibrary1.csproj", solution.ProjectsInOrder[2].RelativePath); - Assert.Equal("{A5EE8128-B08E-4533-86C5-E46714981680}", solution.ProjectsInOrder[2].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[2].Dependencies); - Assert.Equal("{E0F97730-25D2-418A-A7BD-02CAFDC6E470}", solution.ProjectsInOrder[2].ParentProjectGuid); - - Assert.Equal("{2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B}", solution.ProjectsInOrder[3].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[3].Dependencies); - Assert.Equal("{E0F97730-25D2-418A-A7BD-02CAFDC6E470}", solution.ProjectsInOrder[3].ParentProjectGuid); - - Assert.Equal(@"ClassLibrary2\ClassLibrary2.csproj", solution.ProjectsInOrder[4].RelativePath); - Assert.Equal("{6DB98C35-FDCC-4818-B5D4-1F0A385FDFD4}", solution.ProjectsInOrder[4].ProjectGuid); - Assert.Empty(solution.ProjectsInOrder[4].Dependencies); - Assert.Equal("{2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B}", solution.ProjectsInOrder[4].ParentProjectGuid); - } - - /// - /// Exercises solution folders, and makes sure that samely named projects in different - /// solution folders will get correctly uniquified. - /// For the new parser, solution folders are not included to ProjectsInOrder or ProjectsByGuid. - /// - [Fact] - public void SolutionFoldersSlnx() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SolutionFolders(bool convertToSlnx) { string solutionFileContents = """ @@ -344,7 +266,7 @@ public void SolutionFoldersSlnx() EndGlobal """; - SolutionFile solution = ParseSolutionHelper(solutionFileContents, true); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, convertToSlnx); Assert.Equal(3, solution.ProjectsInOrder.Count); @@ -359,9 +281,17 @@ public void SolutionFoldersSlnx() Assert.Empty(classLibrary2.Dependencies); // When converting to slnx, the guids are not preserved. - // try at list assert not null - Assert.NotNull(myPhysicalFolderClassLibrary1.ParentProjectGuid); - Assert.NotNull(classLibrary2.ParentProjectGuid); + if (!convertToSlnx) + { + Assert.Equal("{E0F97730-25D2-418A-A7BD-02CAFDC6E470}", myPhysicalFolderClassLibrary1.ParentProjectGuid); + Assert.Equal("{2AE8D6C4-FB43-430C-8AEB-15E5EEDAAE4B}", classLibrary2.ParentProjectGuid); + } + else + { + // try at list assert not null + Assert.NotNull(myPhysicalFolderClassLibrary1.ParentProjectGuid); + Assert.NotNull(classLibrary2.ParentProjectGuid); + } } /// @@ -679,69 +609,10 @@ public void ParseProjectConfigurationsInSolutionConfigurations1(bool convertToSl Assert.True(vcProject.ProjectConfigurations["Release|Win32"].IncludeInBuild); } - /// - /// Make sure the project configurations in solution configurations get parsed correctly - /// for a more tricky solution - /// - [Fact] - public void ParseProjectConfigurationsInSolutionConfigurations2() - { - string solutionFileContents = - @" - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{E24C65DC-7377-472B-9ABA-BC803B73C61A}') = 'C:\solutions\WebSite1\', '..\WebSite1\', '{E8E75132-67E4-4D6F-9CAE-8DA4C883F418}' - EndProject - Project('{E24C65DC-7377-472B-9ABA-BC803B73C61A}') = 'C:\solutions\WebSite2\', '..\WebSite2\', '{E8E75132-67E4-4D6F-9CAE-8DA4C883F419}' - EndProject - Project('{2150E333-8FDC-42A3-9474-1A3956D46DE8}') = 'NewFolder1', 'NewFolder1', '{54D20FFE-84BE-4066-A51E-B25D040A4235}' - EndProject - Project('{2150E333-8FDC-42A3-9474-1A3956D46DE8}') = 'NewFolder2', 'NewFolder2', '{D2633E4D-46FF-4C4E-8340-4BC7CDF78615}' - EndProject - Project('{8BC9CEB9-8B4A-11D0-8D11-00A0C91BC942}') = 'MSBuild.exe', '..\..\dd\binaries.x86dbg\bin\i386\MSBuild.exe', '{25FD9E7C-F37E-48E0-9A7C-607FE4AACCC0}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|.NET = Debug|.NET - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E8E75132-67E4-4D6F-9CAE-8DA4C883F418}.Debug|.NET.ActiveCfg = Debug|.NET - {E8E75132-67E4-4D6F-9CAE-8DA4C883F418}.Debug|.NET.Build.0 = Debug|.NET - {25FD9E7C-F37E-48E0-9A7C-607FE4AACCC0}.Debug|.NET.ActiveCfg = Debug - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {25FD9E7C-F37E-48E0-9A7C-607FE4AACCC0} = {D2633E4D-46FF-4C4E-8340-4BC7CDF78615} - EndGlobalSection - EndGlobal - "; - - SolutionFile solution = ParseSolutionHelper(solutionFileContents); - - ProjectInSolution webProject = (ProjectInSolution)solution.ProjectsByGuid["{E8E75132-67E4-4D6F-9CAE-8DA4C883F418}"]; - ProjectInSolution exeProject = (ProjectInSolution)solution.ProjectsByGuid["{25FD9E7C-F37E-48E0-9A7C-607FE4AACCC0}"]; - ProjectInSolution missingWebProject = (ProjectInSolution)solution.ProjectsByGuid["{E8E75132-67E4-4D6F-9CAE-8DA4C883F419}"]; - - Assert.Single(webProject.ProjectConfigurations); - - Assert.Equal("Debug|.NET", webProject.ProjectConfigurations["Debug|.NET"].FullName); - Assert.True(webProject.ProjectConfigurations["Debug|.NET"].IncludeInBuild); - - Assert.Single(exeProject.ProjectConfigurations); - - Assert.Equal("Debug", exeProject.ProjectConfigurations["Debug|.NET"].FullName); - Assert.False(exeProject.ProjectConfigurations["Debug|.NET"].IncludeInBuild); - - Assert.Empty(missingWebProject.ProjectConfigurations); - - Assert.Equal("Debug", solution.GetDefaultConfigurationName()); // "Default solution configuration" - Assert.Equal(".NET", solution.GetDefaultPlatformName()); // "Default solution platform" - } - - [Fact] - public void ParseProjectConfigurationsInSolutionConfigurationsSlnx() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ParseProjectConfigurationsInSolutionConfigurations2(bool convertToSlnx) { string solutionFileContents = """ @@ -777,7 +648,7 @@ public void ParseProjectConfigurationsInSolutionConfigurationsSlnx() EndGlobal """; - SolutionFile solution = ParseSolutionHelper(solutionFileContents, true); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, convertToSlnx); ProjectInSolution winFormsApp1 = solution.ProjectsInOrder.First(p => p.ProjectName == "WinFormsApp1"); ProjectInSolution classLibrary1 = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary1"); @@ -806,24 +677,33 @@ public void ParseProjectConfigurationsInSolutionConfigurationsSlnx() private static SolutionFile ParseSolutionHelper(string solutionFileContents, bool convertToSlnx = false) { solutionFileContents = solutionFileContents.Replace('\'', '"'); - - using (TestEnvironment testEnvironment = TestEnvironment.Create()) + string solutionPath = FileUtilities.GetTemporaryFileName(".sln"); + string slnxPath = solutionPath + "x"; + try { - TransientTestFile sln = testEnvironment.CreateFile(FileUtilities.GetTemporaryFileName(".sln"), solutionFileContents); - - string solutionPath = convertToSlnx ? ConvertToSlnx(sln.Path) : sln.Path; - - return SolutionFile.Parse(solutionPath); + File.WriteAllText(solutionPath, solutionFileContents); + if (convertToSlnx) + { + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath); + SolutionModel solutionModel = serializer.OpenAsync(solutionPath, CancellationToken.None).Result; + SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); + + SolutionFile slnx = SolutionFile.Parse(slnxPath); + return slnx; + } + + SolutionFile sln = SolutionFile.Parse(solutionPath); + return sln; } - } + finally + { + File.Delete(solutionPath); - private static string ConvertToSlnx(string slnPath) - { - string slnxPath = slnPath + "x"; - ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(slnPath).ShouldNotBeNull(); - SolutionModel solutionModel = serializer.OpenAsync(slnPath, CancellationToken.None).Result; - SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); - return slnxPath; + if (convertToSlnx) + { + File.Delete(slnxPath); + } + } } } } From b4ba54b0af77559c2d25ddd3119fdf58034c48b0 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 14 Oct 2024 16:09:34 +0200 Subject: [PATCH 17/46] refactor and use old and new parser in SolutionProjectGenerator_Tests; --- .../SolutionProjectGenerator_Tests.cs | 1253 +++++++++-------- 1 file changed, 677 insertions(+), 576 deletions(-) diff --git a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs index 1da28433edb..faf38983bb1 100644 --- a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs @@ -65,12 +65,14 @@ public void SolutionProjectIgnoresDuplicateDefaultTargets(string name) using (TestEnvironment testEnvironment = TestEnvironment.Create()) { TransientTestFolder folder = testEnvironment.CreateFolder(createFolder: true); - TransientTestFile sln = testEnvironment.CreateFile(folder, "MySln.sln", @"Microsoft Visual Studio Solution File, Format Version 16.00"); + TransientTestFile sln = testEnvironment.CreateFile(folder, "MySln.sln", "Microsoft Visual Studio Solution File, Format Version 12.00"); TransientTestFile targetsFile = testEnvironment.CreateFile(folder, name, - @" - + """ + + - "); + + """); ProjectInstance[] instances = SolutionProjectGenerator.Generate(SolutionFile.Parse(sln.Path), null, null, _buildEventContext, CreateMockLoggingService()); instances.ShouldHaveSingleItem(); instances[0].Targets["Build"].AfterTargets.ShouldBe(string.Empty); @@ -87,33 +89,35 @@ public void BuildProjectAsTarget() TransientTestFolder folder = testEnvironment.CreateFolder(createFolder: true); TransientTestFolder classLibFolder = testEnvironment.CreateFolder(Path.Combine(folder.Path, "classlib"), createFolder: true); TransientTestFile classLibrary = testEnvironment.CreateFile(classLibFolder, "classlib.csproj", - @" - - + """ + + + - "); + """); TransientTestFolder simpleProjectFolder = testEnvironment.CreateFolder(Path.Combine(folder.Path, "simpleProject"), createFolder: true); TransientTestFile simpleProject = testEnvironment.CreateFile(simpleProjectFolder, "simpleProject.csproj", - @" - - + """ + + + - "); + """); TransientTestFile solutionFile = testEnvironment.CreateFile(folder, "testFolder.sln", - @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.6.30114.105 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""simpleProject"", ""simpleProject\simpleProject.csproj"", ""{AA52A05F-A9C0-4C89-9933-BF976A304C91}"" -EndProject -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""classlib"", ""classlib\classlib.csproj"", ""{80B8E6B8-E46D-4456-91B1-848FD35C4AB9}"" -EndProject - "); + """ + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio Version 16 + VisualStudioVersion = 16.6.30114.105 + MinimumVisualStudioVersion = 10.0.40219.1 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "simpleProject", "simpleProject\simpleProject.csproj", "{AA52A05F-A9C0-4C89-9933-BF976A304C91}" + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "classlib", "classlib\classlib.csproj", "{80B8E6B8-E46D-4456-91B1-848FD35C4AB9}" + EndProject + """); RunnerUtilities.ExecMSBuild(solutionFile.Path + " /t:classlib", out bool success); success.ShouldBeTrue(); } @@ -130,56 +134,58 @@ public void BuildProjectWithMultipleTargets() TransientTestFolder folder = testEnvironment.CreateFolder(createFolder: true); TransientTestFolder classLibFolder = testEnvironment.CreateFolder(Path.Combine(folder.Path, "classlib"), createFolder: true); TransientTestFile classLibrary = testEnvironment.CreateFile(classLibFolder, "classlib.csproj", - @" - - + """ + + + - - + + - - + + - "); + """); TransientTestFolder simpleProjectFolder = testEnvironment.CreateFolder(Path.Combine(folder.Path, "simpleProject"), createFolder: true); TransientTestFile simpleProject = testEnvironment.CreateFile(simpleProjectFolder, "simpleProject.csproj", - @" - - + """ + + + - - + + - - + + - "); + """); TransientTestFile solutionFile = testEnvironment.CreateFile(folder, "testFolder.sln", - @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.6.30114.105 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""simpleProject"", ""simpleProject\simpleProject.csproj"", ""{AA52A05F-A9C0-4C89-9933-BF976A304C91}"" -EndProject -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""classlib"", ""classlib\classlib.csproj"", ""{80B8E6B8-E46D-4456-91B1-848FD35C4AB9}"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x86 = Debug|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AA52A05F-A9C0-4C89-9933-BF976A304C91}.Debug|x86.ActiveCfg = Debug|x86 - {AA52A05F-A9C0-4C89-9933-BF976A304C91}.Debug|x86.Build.0 = Debug|x86 - {80B8E6B8-E46D-4456-91B1-848FD35C4AB9}.Debug|x86.ActiveCfg = Debug|x86 - {80B8E6B8-E46D-4456-91B1-848FD35C4AB9}.Debug|x86.Build.0 = Debug|x86 - EndGlobalSection -EndGlobal - "); + """ + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio Version 16 + VisualStudioVersion = 16.6.30114.105 + MinimumVisualStudioVersion = 10.0.40219.1 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "simpleProject", "simpleProject\simpleProject.csproj", "{AA52A05F-A9C0-4C89-9933-BF976A304C91}" + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "classlib", "classlib\classlib.csproj", "{80B8E6B8-E46D-4456-91B1-848FD35C4AB9}" + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA52A05F-A9C0-4C89-9933-BF976A304C91}.Debug|x86.ActiveCfg = Debug|x86 + {AA52A05F-A9C0-4C89-9933-BF976A304C91}.Debug|x86.Build.0 = Debug|x86 + {80B8E6B8-E46D-4456-91B1-848FD35C4AB9}.Debug|x86.ActiveCfg = Debug|x86 + {80B8E6B8-E46D-4456-91B1-848FD35C4AB9}.Debug|x86.Build.0 = Debug|x86 + EndGlobalSection + EndGlobal + """); string output = RunnerUtilities.ExecMSBuild(solutionFile.Path + " /t:Clean;Build;Custom", out bool success); success.ShouldBeTrue(); @@ -204,56 +210,58 @@ public void BuildProjectWithMultipleTargetsInParallel() TransientTestFolder folder = testEnvironment.CreateFolder(createFolder: true); TransientTestFolder classLibFolder = testEnvironment.CreateFolder(Path.Combine(folder.Path, "classlib"), createFolder: true); TransientTestFile classLibrary = testEnvironment.CreateFile(classLibFolder, "classlib.csproj", - @" - - + """ + + + - - + + - - + + - "); + """); TransientTestFolder simpleProjectFolder = testEnvironment.CreateFolder(Path.Combine(folder.Path, "simpleProject"), createFolder: true); TransientTestFile simpleProject = testEnvironment.CreateFile(simpleProjectFolder, "simpleProject.csproj", - @" - - + """ + + + - - + + - - + + - "); + """); TransientTestFile solutionFile = testEnvironment.CreateFile(folder, "testFolder.sln", - @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.6.30114.105 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""simpleProject"", ""simpleProject\simpleProject.csproj"", ""{AA52A05F-A9C0-4C89-9933-BF976A304C91}"" -EndProject -Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""classlib"", ""classlib\classlib.csproj"", ""{80B8E6B8-E46D-4456-91B1-848FD35C4AB9}"" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x86 = Debug|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AA52A05F-A9C0-4C89-9933-BF976A304C91}.Debug|x86.ActiveCfg = Debug|x86 - {AA52A05F-A9C0-4C89-9933-BF976A304C91}.Debug|x86.Build.0 = Debug|x86 - {80B8E6B8-E46D-4456-91B1-848FD35C4AB9}.Debug|x86.ActiveCfg = Debug|x86 - {80B8E6B8-E46D-4456-91B1-848FD35C4AB9}.Debug|x86.Build.0 = Debug|x86 - EndGlobalSection -EndGlobal - "); + """ + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio Version 16 + VisualStudioVersion = 16.6.30114.105 + MinimumVisualStudioVersion = 10.0.40219.1 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "simpleProject", "simpleProject\simpleProject.csproj", "{AA52A05F-A9C0-4C89-9933-BF976A304C91}" + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "classlib", "classlib\classlib.csproj", "{80B8E6B8-E46D-4456-91B1-848FD35C4AB9}" + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AA52A05F-A9C0-4C89-9933-BF976A304C91}.Debug|x86.ActiveCfg = Debug|x86 + {AA52A05F-A9C0-4C89-9933-BF976A304C91}.Debug|x86.Build.0 = Debug|x86 + {80B8E6B8-E46D-4456-91B1-848FD35C4AB9}.Debug|x86.ActiveCfg = Debug|x86 + {80B8E6B8-E46D-4456-91B1-848FD35C4AB9}.Debug|x86.Build.0 = Debug|x86 + EndGlobalSection + EndGlobal + """); try { @@ -331,10 +339,12 @@ public void AddNewErrorWarningMessageElement() /// Test to make sure we properly set the ToolsVersion attribute on the in-memory project based /// on the Solution File Format Version. /// - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [Trait("Category", "netcore-osx-failing")] [Trait("Category", "netcore-linux-failing")] - public void EmitToolsVersionAttributeToInMemoryProject9() + public void EmitToolsVersionAttributeToInMemoryProject9(bool useNewParser) { if (FrameworkLocationHelper.PathToDotNetFrameworkV35 == null) { @@ -343,7 +353,7 @@ public void EmitToolsVersionAttributeToInMemoryProject9() } string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -353,9 +363,9 @@ public void EmitToolsVersionAttributeToInMemoryProject9() Other|Win32 = Other|Win32 EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, "3.5", _buildEventContext, CreateMockLoggingService()); @@ -366,10 +376,12 @@ public void EmitToolsVersionAttributeToInMemoryProject9() /// Test to make sure we properly set the ToolsVersion attribute on the in-memory project based /// on the Solution File Format Version. /// - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [Trait("Category", "netcore-osx-failing")] [Trait("Category", "netcore-linux-failing")] - public void EmitToolsVersionAttributeToInMemoryProject10() + public void EmitToolsVersionAttributeToInMemoryProject10(bool useNewParser) { if (FrameworkLocationHelper.PathToDotNetFrameworkV35 == null) { @@ -378,7 +390,7 @@ public void EmitToolsVersionAttributeToInMemoryProject10() } string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 10.00 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -388,9 +400,9 @@ public void EmitToolsVersionAttributeToInMemoryProject10() Other|Win32 = Other|Win32 EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, "3.5", _buildEventContext, CreateMockLoggingService()); @@ -407,7 +419,7 @@ public void DefaultSubToolsetIfSolutionVersionSubToolsetDoesntExist() Environment.SetEnvironmentVariable("VisualStudioVersion", null); string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 10.00 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -417,7 +429,7 @@ public void DefaultSubToolsetIfSolutionVersionSubToolsetDoesntExist() Other|Win32 = Other|Win32 EndGlobalSection EndGlobal - "; + """; SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); @@ -443,13 +455,15 @@ public void DefaultSubToolsetIfSolutionVersionSubToolsetDoesntExist() /// Test to make sure that if the solution version corresponds to an existing sub-toolset version, /// barring other factors that might override, the sub-toolset will be based on the solution version. /// - [Fact] - public void SubToolsetSetBySolutionVersion() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SubToolsetSetBySolutionVersion(bool useNewParser) { Environment.SetEnvironmentVariable("VisualStudioVersion", null); string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 12.00 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -459,9 +473,9 @@ public void SubToolsetSetBySolutionVersion() Other|Win32 = Other|Win32 EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, _buildEventContext, CreateMockLoggingService()); @@ -478,13 +492,15 @@ public void SubToolsetSetBySolutionVersion() /// /// Test to make sure that even if the solution version corresponds to an existing sub-toolset version, /// - [Fact] - public void SolutionBasedSubToolsetVersionOverriddenByEnvironment() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SolutionBasedSubToolsetVersionOverriddenByEnvironment(bool useNewParser) { Environment.SetEnvironmentVariable("VisualStudioVersion", "ABC"); string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 12.00 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -494,9 +510,9 @@ public void SolutionBasedSubToolsetVersionOverriddenByEnvironment() Other|Win32 = Other|Win32 EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, _buildEventContext, CreateMockLoggingService()); @@ -719,42 +735,43 @@ public void SolutionDoesntPassSubToolsetToChildProjects() /// Verify that we throw the appropriate error if the solution declares a dependency /// on a project that doesn't exist. /// + /// This test would only work for the old parser. In the new parser the dependency is not added if it was not in the solution file. [Fact] public void SolutionWithMissingDependencies() { Assert.Throws(() => { string solutionFileContents = - @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 11 -Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `B`, `Project2\B.csproj`, `{881C1674-4ECA-451D-85B6-D7C59B7F16FA}` - ProjectSection(ProjectDependencies) = postProject - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} = {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = preSolution - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|x64.ActiveCfg = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|x64.Build.0 = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|Any CPU.Build.0 = Release|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|x64.ActiveCfg = Release|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal -".Replace("`", "\""); + """ + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio 11 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "B", "Project2\B.csproj", "{881C1674-4ECA-451D-85B6-D7C59B7F16FA}" + ProjectSection(ProjectDependencies) = postProject + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} = {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} + EndProjectSection + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = preSolution + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|x64.ActiveCfg = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|x64.Build.0 = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|Any CPU.Build.0 = Release|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|x64.ActiveCfg = Release|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + """; SolutionFile sp = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); ProjectInstance[] instances = SolutionProjectGenerator.Generate(sp, null, null, _buildEventContext, CreateMockLoggingService()); @@ -764,62 +781,64 @@ public void SolutionWithMissingDependencies() /// Blob should contain dependency info /// Here B depends on C /// - [Fact] - public void SolutionConfigurationWithDependencies() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SolutionConfigurationWithDependencies(bool useNewParser) { string solutionFileContents = - @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 11 -Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `A`, `Project1\A.csproj`, `{786E302A-96CE-43DC-B640-D6B6CC9BF6C0}` -EndProject -Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `B`, `Project2\B.csproj`, `{881C1674-4ECA-451D-85B6-D7C59B7F16FA}` - ProjectSection(ProjectDependencies) = postProject - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} = {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} - EndProjectSection -EndProject -Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `C`, `Project3\C.csproj`, `{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}` -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = preSolution - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|x64.ActiveCfg = Debug|Any CPU - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|x64.Build.0 = Debug|Any CPU - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Release|Any CPU.Build.0 = Release|Any CPU - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Release|x64.ActiveCfg = Release|Any CPU - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Release|x64.Build.0 = Release|Any CPU - {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Debug|x64.ActiveCfg = Debug|Any CPU - {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Debug|x64.Build.0 = Debug|Any CPU - {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Release|Any CPU.Build.0 = Release|Any CPU - {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Release|x64.ActiveCfg = Release|Any CPU - {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Release|x64.Build.0 = Release|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|x64.ActiveCfg = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|x64.Build.0 = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|Any CPU.Build.0 = Release|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|x64.ActiveCfg = Release|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|x64.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal -".Replace("`", "\""); + """ + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio 11 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "A", "Project1\A.csproj", "{786E302A-96CE-43DC-B640-D6B6CC9BF6C0}" + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "B", "Project2\B.csproj", "{881C1674-4ECA-451D-85B6-D7C59B7F16FA}" + ProjectSection(ProjectDependencies) = postProject + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} = {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} + EndProjectSection + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C", "Project3\C.csproj", "{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}" + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = preSolution + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|x64.ActiveCfg = Debug|Any CPU + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|x64.Build.0 = Debug|Any CPU + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Release|Any CPU.Build.0 = Release|Any CPU + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Release|x64.ActiveCfg = Release|Any CPU + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Release|x64.Build.0 = Release|Any CPU + {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Debug|x64.ActiveCfg = Debug|Any CPU + {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Debug|x64.Build.0 = Debug|Any CPU + {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Release|Any CPU.Build.0 = Release|Any CPU + {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Release|x64.ActiveCfg = Release|Any CPU + {786E302A-96CE-43DC-B640-D6B6CC9BF6C0}.Release|x64.Build.0 = Release|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|x64.ActiveCfg = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|x64.Build.0 = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|Any CPU.Build.0 = Release|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|x64.ActiveCfg = Release|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectRootElement projectXml = ProjectRootElement.Create(); @@ -838,11 +857,14 @@ public void SolutionConfigurationWithDependencies() string solutionConfigurationContents = msbuildProject.GetPropertyValue("CurrentSolutionConfigurationContents"); // Only the specified solution configuration is represented in THE BLOB: nothing for x64 in this case - string expected = $@" - Debug|AnyCPU - Debug|AnyCPU - Debug|AnyCPU -".Replace("`", "\"").Replace("##temp##", FileUtilities.TempFileDirectory); + string expected = + $$""" + + Debug|AnyCPU + Debug|AnyCPU + Debug|AnyCPU + + """.Replace("##temp##", FileUtilities.TempFileDirectory); Helpers.VerifyAssertLineByLine(expected, solutionConfigurationContents); } @@ -860,19 +882,19 @@ public void SolutionGeneratingMetaproj() TransientTestFile proj2 = env.CreateFile("B.csproj", @""); TransientTestFile proj3 = env.CreateFile("C.csproj", @""); TransientTestFile proj = env.CreateFile("mysln.sln", - @$" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 11 -Project(`{"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"}`) = `A`, `{proj1.Path}`, `{"{786E302A-96CE-43DC-B640-D6B6CC9BF6C0}"}` -EndProject -Project(`{"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"}`) = `B`, `{proj2.Path}`, `{"{881C1674-4ECA-451D-85B6-D7C59B7F16FA}"}` - ProjectSection(ProjectDependencies) = postProject - {"{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}"} = {"{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}"} - EndProjectSection -EndProject -Project(`{"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"}`) = `C`, `{proj3.Path}`, `{"{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}"}` -EndProject -".Replace("`", "\"")); + $$""" + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio 11 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "A", "{{proj1.Path}}", "{786E302A-96CE-43DC-B640-D6B6CC9BF6C0}" + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "B", "{{proj2.Path}}", "{881C1674-4ECA-451D-85B6-D7C59B7F16FA}" + ProjectSection(ProjectDependencies) = postProject + {"{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}"} = {"{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}"} + EndProjectSection + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "C", "{{proj3.Path}}", "{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}" + EndProject + """); RunnerUtilities.ExecMSBuild("\"" + proj.Path + "\"", out bool successfulExit); successfulExit.ShouldBeTrue(); } @@ -890,37 +912,37 @@ public void SolutionConfigurationWithDependenciesRelaysItsOutputs() { #region Large strings representing solution & projects const string solutionFileContents = - @" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 11 -Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `B`, `B.csproj`, `{881C1674-4ECA-451D-85B6-D7C59B7F16FA}` - ProjectSection(ProjectDependencies) = postProject - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} = {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} - EndProjectSection -EndProject -Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `C`, `C.csproj`, `{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}` -EndProject -Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `D`, `D.csproj`, `{B6E7E06F-FC0B-48F1-911A-55E0E1566F00}` -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = preSolution - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|Any CPU.Build.0 = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6E7E06F-FC0B-48F1-911A-55E0E1566F00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6E7E06F-FC0B-48F1-911A-55E0E1566F00}.Debug|Any CPU.Build.0 = Debug|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal -"; + """ + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio 11 + Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `B`, `B.csproj`, `{881C1674-4ECA-451D-85B6-D7C59B7F16FA}` + ProjectSection(ProjectDependencies) = postProject + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} = {4A727FF8-65F2-401E-95AD-7C8BBFBE3167} + EndProjectSection + EndProject + Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `C`, `C.csproj`, `{4A727FF8-65F2-401E-95AD-7C8BBFBE3167}` + EndProject + Project(`{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}`) = `D`, `D.csproj`, `{B6E7E06F-FC0B-48F1-911A-55E0E1566F00}` + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = preSolution + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A727FF8-65F2-401E-95AD-7C8BBFBE3167}.Debug|Any CPU.Build.0 = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {881C1674-4ECA-451D-85B6-D7C59B7F16FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6E7E06F-FC0B-48F1-911A-55E0E1566F00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6E7E06F-FC0B-48F1-911A-55E0E1566F00}.Debug|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + """; const string projectBravoFileContents = - @" + """ @@ -934,9 +956,9 @@ public void SolutionConfigurationWithDependenciesRelaysItsOutputs() - "; + """; const string projectCharlieFileContents = - @" + """ @@ -944,9 +966,9 @@ public void SolutionConfigurationWithDependenciesRelaysItsOutputs() - "; + """; const string projectDeltaFileContents = - @" + """ {B6E7E06F-FC0B-48F1-911A-55E0E1566F00} @@ -957,60 +979,62 @@ public void SolutionConfigurationWithDependenciesRelaysItsOutputs() - "; - const string automaticProjectFileContents = @" - - - - - - - - - - - - - - - - - - - - @(BravoProjectOutputs) - @(CharlieProjectOutputs) - @(DeltaProjectOutputs) - - - - - - - - - - - - - - - Count())' != '3' ` Text='Overall sln outputs must include outputs of each referenced project (there should be 3).' /> - AnyHaveMetadataValue('Identity', '$(StringifiedBravoProjectOutputs)'))' != 'true'` Text='Overall sln outputs must include outputs of normal project build of project B.' /> - AnyHaveMetadataValue('Identity', '$(StringifiedCharlieProjectOutputs)'))' != 'true' ` Text='Overall sln outputs must include outputs of normal project build of project C.' /> - AnyHaveMetadataValue('Identity', '$(StringifiedDeltaProjectOutputs)'))' != 'true' ` Text='Overall sln outputs must include outputs of normal project build of project D.' /> - -"; + """; + const string automaticProjectFileContents = + """ + + + + + + + + + + + + + + + + + + + + @(BravoProjectOutputs) + @(CharlieProjectOutputs) + @(DeltaProjectOutputs) + + + + + + + + + + + + + + + Count())' != '3' ` Text='Overall sln outputs must include outputs of each referenced project (there should be 3).' /> + AnyHaveMetadataValue('Identity', '$(StringifiedBravoProjectOutputs)'))' != 'true'` Text='Overall sln outputs must include outputs of normal project build of project B.' /> + AnyHaveMetadataValue('Identity', '$(StringifiedCharlieProjectOutputs)'))' != 'true' ` Text='Overall sln outputs must include outputs of normal project build of project C.' /> + AnyHaveMetadataValue('Identity', '$(StringifiedDeltaProjectOutputs)'))' != 'true' ` Text='Overall sln outputs must include outputs of normal project build of project D.' /> + + + """; #endregion var logger = new MockLogger(output); @@ -1039,11 +1063,13 @@ public void SolutionConfigurationWithDependenciesRelaysItsOutputs() /// /// Test the SolutionProjectGenerator.AddPropertyGroupForSolutionConfiguration method /// - [Fact] - public void TestAddPropertyGroupForSolutionConfiguration() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TestAddPropertyGroupForSolutionConfiguration(bool useNewParser) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' @@ -1063,9 +1089,9 @@ public void TestAddPropertyGroupForSolutionConfiguration() {A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Debug|Mixed Platforms.Build.0 = VCConfig1|Win32 EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectRootElement projectXml = ProjectRootElement.Create(); @@ -1112,11 +1138,13 @@ public void TestAddPropertyGroupForSolutionConfiguration() /// /// Make sure that BuildProjectInSolution is set to true of the Build.0 entry is in the solution configuration. /// - [Fact] - public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionSet() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionSet(bool useNewParser) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' @@ -1131,9 +1159,9 @@ public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionSe {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Mixed Platforms.Build.0 = CSConfig1|Any CPU EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectRootElement projectXml = ProjectRootElement.Create(); @@ -1156,11 +1184,13 @@ public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionSe /// /// Make sure that BuildProjectInSolution is set to false of the Build.0 entry is in the solution configuration. /// - [Fact] - public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionNotSet() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionNotSet(bool useNewParser) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' @@ -1174,9 +1204,9 @@ public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionNo {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Mixed Platforms.ActiveCfg = CSConfig1|Any CPU EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectRootElement projectXml = ProjectRootElement.Create(); @@ -1200,10 +1230,12 @@ public void TestAddPropertyGroupForSolutionConfigurationBuildProjectInSolutionNo /// In this bug, SkipNonexistentProjects was always set to 'Build'. It should be 'Build' for metaprojects and 'True' for everything else. /// The repro below has one of each case. WebProjects can't build so they are set as SkipNonexistentProjects='Build' /// - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [Trait("Category", "netcore-osx-failing")] [Trait("Category", "netcore-linux-failing")] - public void Regress751742_SkipNonexistentProjects() + public void Regress751742_SkipNonexistentProjects(bool useNewParser) { if (FrameworkLocationHelper.PathToDotNetFrameworkV20 == null) { @@ -1212,7 +1244,7 @@ public void Regress751742_SkipNonexistentProjects() } var solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' @@ -1232,10 +1264,10 @@ public void Regress751742_SkipNonexistentProjects() {A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Debug|Mixed Platforms.Build.0 = VCConfig1|Win32 EndGlobalSection EndGlobal - "; + """; // We're not passing in a /tv:xx switch, so the solution project will have tools version 2.0 - var solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); var instance = SolutionProjectGenerator.Generate(solution, null, ObjectModelHelpers.MSBuildDefaultToolsVersion, _buildEventContext, CreateMockLoggingService())[0]; @@ -1274,11 +1306,13 @@ public void Regress751742_SkipNonexistentProjects() /// if set when building a solution, will be specified as the ToolsVersion on the MSBuild task when /// building the projects contained within the solution. /// - [Fact] - public void ToolsVersionOverrideShouldBeSpecifiedOnMSBuildTaskInvocations() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ToolsVersionOverrideShouldBeSpecifiedOnMSBuildTaskInvocations(bool useNewParser) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' @@ -1298,10 +1332,10 @@ public void ToolsVersionOverrideShouldBeSpecifiedOnMSBuildTaskInvocations() {A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Debug|Mixed Platforms.Build.0 = VCConfig1|Win32 EndGlobalSection EndGlobal - "; + """; // We're not passing in a /tv:xx switch, so the solution project will have tools version 2.0 - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, ObjectModelHelpers.MSBuildDefaultToolsVersion, _buildEventContext, CreateMockLoggingService()); @@ -1340,42 +1374,44 @@ public void ToolsVersionOverrideShouldBeSpecifiedOnMSBuildTaskInvocations() /// /// Make sure that whatever the solution ToolsVersion is, it gets mapped to all its metaprojs, too. /// - [Fact] - public void SolutionWithDependenciesHasCorrectToolsVersionInMetaprojs() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SolutionWithDependenciesHasCorrectToolsVersionInMetaprojs(bool useNewParser) { string solutionFileContents = - @" -Microsoft Visual Studio Solution File, Format Version 12.00 -Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ConsoleApplication2', 'ConsoleApplication2\ConsoleApplication2.csproj', '{5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}' - ProjectSection(ProjectDependencies) = postProject - {E0D295A1-CAFA-4E68-9929-468657DAAC6C} = {E0D295A1-CAFA-4E68-9929-468657DAAC6C} - EndProjectSection -EndProject -Project('{F184B08F-C81C-45F6-A57F-5ABD9991F28F}') = 'ConsoleApplication1', 'ConsoleApplication1\ConsoleApplication1.vbproj', '{E0D295A1-CAFA-4E68-9929-468657DAAC6C}' -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}.Release|Any CPU.Build.0 = Release|Any CPU - {E0D295A1-CAFA-4E68-9929-468657DAAC6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E0D295A1-CAFA-4E68-9929-468657DAAC6C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E0D295A1-CAFA-4E68-9929-468657DAAC6C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E0D295A1-CAFA-4E68-9929-468657DAAC6C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal - "; + """ + Microsoft Visual Studio Solution File, Format Version 12.00 + Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ConsoleApplication2', 'ConsoleApplication2\ConsoleApplication2.csproj', '{5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}' + ProjectSection(ProjectDependencies) = postProject + {E0D295A1-CAFA-4E68-9929-468657DAAC6C} = {E0D295A1-CAFA-4E68-9929-468657DAAC6C} + EndProjectSection + EndProject + Project('{F184B08F-C81C-45F6-A57F-5ABD9991F28F}') = 'ConsoleApplication1', 'ConsoleApplication1\ConsoleApplication1.vbproj', '{E0D295A1-CAFA-4E68-9929-468657DAAC6C}' + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B97A3C7-3DEE-47A4-870F-5CB6384FE6A4}.Release|Any CPU.Build.0 = Release|Any CPU + {E0D295A1-CAFA-4E68-9929-468657DAAC6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0D295A1-CAFA-4E68-9929-468657DAAC6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0D295A1-CAFA-4E68-9929-468657DAAC6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0D295A1-CAFA-4E68-9929-468657DAAC6C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + """; // We're not passing in a /tv:xx switch, so the solution project will have tools version 2.0 - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); string[] solutionToolsVersions = { "4.0", ObjectModelHelpers.MSBuildDefaultToolsVersion }; @@ -1409,11 +1445,13 @@ public void SolutionWithDependenciesHasCorrectToolsVersionInMetaprojs() /// /// Test the SolutionProjectGenerator.Generate method has its toolset redirected correctly. /// - [Fact] - public void ToolsVersionOverrideCausesToolsetRedirect() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ToolsVersionOverrideCausesToolsetRedirect(bool useNewParser) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'ClassLibrary1', 'ClassLibrary1\ClassLibrary1.csproj', '{6185CC21-BE89-448A-B3C0-D1C27112E595}' @@ -1433,8 +1471,8 @@ public void ToolsVersionOverrideCausesToolsetRedirect() {A6F99D27-47B9-4EA4-BFC9-25157CBDC281}.Debug|Mixed Platforms.Build.0 = VCConfig1|Win32 EndGlobalSection EndGlobal - "; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + """; + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); bool caughtException = false; try @@ -1454,11 +1492,13 @@ public void ToolsVersionOverrideCausesToolsetRedirect() /// /// Test the SolutionProjectGenerator.AddPropertyGroupForSolutionConfiguration method /// - [Fact] - public void TestDisambiguateProjectTargetName() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TestDisambiguateProjectTargetName(bool useNewParser) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}') = 'Build', 'Build\Build.csproj', '{21397922-C38F-4A0E-B950-77B3FBD51881}' @@ -1478,9 +1518,9 @@ public void TestDisambiguateProjectTargetName() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, BuildEventContext.Invalid, CreateMockLoggingService()); @@ -1534,11 +1574,13 @@ public void TestDisambiguateProjectTargetName() /// /// Tests the algorithm for choosing default configuration/platform values for solutions /// + /// This test would only work for the old parser. In the new parser SolutionConfigurations are not available, + /// and constructed from projects configurations. [Fact] public void TestConfigurationPlatformDefaults1() { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1550,7 +1592,7 @@ public void TestConfigurationPlatformDefaults1() Release|Win32 = Release|Win32 EndGlobalSection EndGlobal - "; + """; SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); @@ -1572,11 +1614,13 @@ public void TestConfigurationPlatformDefaults1() /// /// Tests the algorithm for choosing default configuration/platform values for solutions /// + /// This test would only work for the old parser. In the new parser SolutionConfigurations are not available, + /// and constructed from projects configurations. [Fact] public void TestConfigurationPlatformDefaults2() { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1586,7 +1630,7 @@ public void TestConfigurationPlatformDefaults2() Other|Win32 = Other|Win32 EndGlobalSection EndGlobal - "; + """; SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); @@ -1602,10 +1646,12 @@ public void TestConfigurationPlatformDefaults2() /// /// Tests the algorithm for choosing default Venus configuration values for solutions /// - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [Trait("Category", "netcore-osx-failing")] [Trait("Category", "netcore-linux-failing")] - public void TestVenusConfigurationDefaults() + public void TestVenusConfigurationDefaults(bool useNewParser) { if (FrameworkLocationHelper.PathToDotNetFrameworkV20 == null) { @@ -1615,13 +1661,13 @@ public void TestVenusConfigurationDefaults() Dictionary globalProperties = new Dictionary(); globalProperties["Configuration"] = "Debug"; - ProjectInstance msbuildProject = CreateVenusSolutionProject(globalProperties); + ProjectInstance msbuildProject = CreateVenusSolutionProject(globalProperties, useNewParser); // ASP.NET configuration should match the selected solution configuration Assert.Equal("Debug", msbuildProject.GetPropertyValue("AspNetConfiguration")); globalProperties["Configuration"] = "Release"; - msbuildProject = CreateVenusSolutionProject(globalProperties); + msbuildProject = CreateVenusSolutionProject(globalProperties, useNewParser); Assert.Equal("Release", msbuildProject.GetPropertyValue("AspNetConfiguration")); // Check that the two standard Asp.net configurations are represented on the targets @@ -1632,10 +1678,12 @@ public void TestVenusConfigurationDefaults() /// /// Tests that the correct value for TargetFrameworkVersion gets set when creating Venus solutions /// - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [Trait("Category", "netcore-osx-failing")] [Trait("Category", "netcore-linux-failing")] - public void VenusSolutionDefaultTargetFrameworkVersion() + public void VenusSolutionDefaultTargetFrameworkVersion(bool useNewParser) { if (FrameworkLocationHelper.PathToDotNetFrameworkV20 == null) { @@ -1644,7 +1692,7 @@ public void VenusSolutionDefaultTargetFrameworkVersion() } // v4.0 by default - ProjectInstance msbuildProject = CreateVenusSolutionProject(); + ProjectInstance msbuildProject = CreateVenusSolutionProject(useNewParser); Assert.Equal("v4.0", msbuildProject.GetPropertyValue("TargetFrameworkVersion")); if (FrameworkLocationHelper.PathToDotNetFrameworkV35 == null) @@ -1654,34 +1702,36 @@ public void VenusSolutionDefaultTargetFrameworkVersion() } // v3.5 if MSBuildToolsVersion is 3.5 - msbuildProject = CreateVenusSolutionProject("3.5"); + msbuildProject = CreateVenusSolutionProject("3.5", useNewParser); Assert.Equal("v3.5", msbuildProject.GetPropertyValue("TargetFrameworkVersion")); // v2.0 if MSBuildToolsVersion is 2.0 - msbuildProject = CreateVenusSolutionProject("2.0"); + msbuildProject = CreateVenusSolutionProject("2.0", useNewParser); Assert.Equal("v2.0", msbuildProject.GetPropertyValue("TargetFrameworkVersion")); // may be user defined IDictionary globalProperties = new Dictionary(); globalProperties.Add("TargetFrameworkVersion", "userdefined"); - msbuildProject = CreateVenusSolutionProject(globalProperties); + msbuildProject = CreateVenusSolutionProject(globalProperties, useNewParser); Assert.Equal("userdefined", msbuildProject.GetPropertyValue("TargetFrameworkVersion")); } /// /// Tests the algorithm for choosing target framework paths for ResolveAssemblyReferences for Venus /// - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [Trait("Category", "netcore-osx-failing")] [Trait("Category", "netcore-linux-failing")] - public void TestTargetFrameworkPaths0() + public void TestTargetFrameworkPaths0(bool useNewParser) { if (FrameworkLocationHelper.PathToDotNetFrameworkSdkV20 != null) { IDictionary globalProperties = new Dictionary(); globalProperties.Add("TargetFrameworkVersion", "v2.0"); - ProjectInstance msbuildProject = CreateVenusSolutionProject("2.0"); + ProjectInstance msbuildProject = CreateVenusSolutionProject("2.0", useNewParser); // ToolsVersion is 2.0, TargetFrameworkVersion is v2.0 --> one item pointing to v2.0 Assert.Equal("2.0", msbuildProject.ToolsVersion); @@ -1696,10 +1746,12 @@ public void TestTargetFrameworkPaths0() /// /// Tests the algorithm for choosing target framework paths for ResolveAssemblyReferences for Venus /// - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [Trait("Category", "netcore-osx-failing")] [Trait("Category", "netcore-linux-failing")] - public void TestTargetFrameworkPaths1() + public void TestTargetFrameworkPaths1(bool useNewParser) { if (FrameworkLocationHelper.PathToDotNetFrameworkV20 == null) { @@ -1707,7 +1759,7 @@ public void TestTargetFrameworkPaths1() return; } - ProjectInstance msbuildProject = CreateVenusSolutionProject(); + ProjectInstance msbuildProject = CreateVenusSolutionProject(useNewParser); // ToolsVersion is 4.0, TargetFrameworkVersion is v2.0 --> one item pointing to v2.0 msbuildProject.SetProperty("TargetFrameworkVersion", "v2.0"); @@ -1722,10 +1774,12 @@ public void TestTargetFrameworkPaths1() /// /// Tests the algorithm for choosing target framework paths for ResolveAssemblyReferences for Venus /// - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [Trait("Category", "netcore-osx-failing")] [Trait("Category", "netcore-linux-failing")] - public void TestTargetFrameworkPaths2() + public void TestTargetFrameworkPaths2(bool useNewParser) { if (FrameworkLocationHelper.PathToDotNetFrameworkV20 == null) { @@ -1733,7 +1787,7 @@ public void TestTargetFrameworkPaths2() return; } - ProjectInstance msbuildProject = CreateVenusSolutionProject(); + ProjectInstance msbuildProject = CreateVenusSolutionProject(useNewParser); // ToolsVersion is 4.0, TargetFrameworkVersion is v4.0 --> items for v2.0 and v4.0 msbuildProject.SetProperty("TargetFrameworkVersion", "v4.0"); @@ -1771,11 +1825,13 @@ public void TestTargetFrameworkPaths2() /// /// Test the PredictActiveSolutionConfigurationName method /// + /// This test would only work for the old parser. + /// In the new parser SolutionConfigurations are not available, and constructed from projects configurations. [Fact] public void TestPredictSolutionConfigurationName() { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1785,7 +1841,7 @@ public void TestPredictSolutionConfigurationName() Debug|Win32 = Debug|Win32 EndGlobalSection EndGlobal - "; + """; SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); @@ -1806,11 +1862,13 @@ public void TestPredictSolutionConfigurationName() /// /// Verifies that the SolutionProjectGenerator will correctly escape project file paths /// - [Fact] - public void SolutionGeneratorEscapingProjectFilePaths() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SolutionGeneratorEscapingProjectFilePaths(bool useNewParser) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{F184B08F-C81C-45F6-A57F-5ABD9991F28F}') = 'ConsoleApplication1', '%abtest\ConsoleApplication1.vbproj', '{AB3413A6-D689-486D-B7F0-A095371B3F13}' @@ -1830,9 +1888,9 @@ public void SolutionGeneratorEscapingProjectFilePaths() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); // Creating a ProjectRootElement shouldn't affect the ProjectCollection at all Assert.Empty(ProjectCollection.GlobalProjectCollection.LoadedProjects); @@ -1849,8 +1907,10 @@ public void SolutionGeneratorEscapingProjectFilePaths() /// /// Verifies that the SolutionProjectGenerator will emit a solution file. /// - [Fact] - public void SolutionGeneratorCanEmitSolutions() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SolutionGeneratorCanEmitSolutions(bool useNewParser) { string oldValueForMSBuildEmitSolution = Environment.GetEnvironmentVariable("MSBuildEmitSolution"); @@ -1858,7 +1918,7 @@ public void SolutionGeneratorCanEmitSolutions() ProjectCollection.GlobalProjectCollection.UnloadAllProjects(); string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{F184B08F-C81C-45F6-A57F-5ABD9991F28F}') = 'ConsoleApplication1', 'ConsoleApplication1\ConsoleApplication1.vbproj', '{AB3413A6-D689-486D-B7F0-A095371B3F13}' @@ -1878,7 +1938,7 @@ public void SolutionGeneratorCanEmitSolutions() HideSolutionNode = FALSE EndGlobalSection EndGlobal - "; + """; SolutionFile solution = null; using ProjectCollection collection = new ProjectCollection(); @@ -1887,7 +1947,7 @@ public void SolutionGeneratorCanEmitSolutions() { Environment.SetEnvironmentVariable("MSBuildEmitSolution", "1"); - solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + solution = ParseSolutionHelper(solutionFileContents, useNewParser); // Creating a ProjectRootElement shouldn't affect the ProjectCollection at all Assert.Empty(ProjectCollection.GlobalProjectCollection.LoadedProjects); @@ -1919,16 +1979,18 @@ public void SolutionGeneratorCanEmitSolutions() /// Make sure that we output a warning and don't build anything when we're given an invalid /// solution configuration and SkipInvalidConfigurations is set to true. /// - [Fact] + [Theory] + [InlineData(false)] + [InlineData(true)] [Trait("Category", "netcore-osx-failing")] [Trait("Category", "netcore-linux-failing")] - public void TestSkipInvalidConfigurationsCase() + public void TestSkipInvalidConfigurationsCase(bool useNewParser) { string tmpFileName = FileUtilities.GetTemporaryFileName(); string projectFilePath = tmpFileName + ".sln"; - string solutionContents = - @" + string solutionFileContents = + """ Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2005 Project('{E24C65DC-7377-472B-9ABA-BC803B73C61A}') = 'C:\solutions\WebSite2\', '..\..\solutions\WebSite2\', '{F90528C4-6989-4D33-AFE8-F53173597CC2}' @@ -1959,7 +2021,8 @@ public void TestSkipInvalidConfigurationsCase() {F90528C4-6989-4D33-AFE8-F53173597CC2}.Debug|Any CPU.ActiveCfg = Debug|.NET {F90528C4-6989-4D33-AFE8-F53173597CC2}.Debug|Any CPU.Build.0 = Debug|.NET EndGlobalSection - EndGlobal"; + EndGlobal + """; try { @@ -1969,7 +2032,7 @@ public void TestSkipInvalidConfigurationsCase() globalProperties["Configuration"] = "Nonexistent"; globalProperties["SkipInvalidConfigurations"] = "true"; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionContents.Replace('\'', '"')); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, globalProperties, null, BuildEventContext.Invalid, CreateMockLoggingService()); ProjectInstance msbuildProject = instances[0]; @@ -2173,50 +2236,52 @@ public void BadFrameworkMonkierExpectBuildToFail2() /// Bug indicated that when a target framework version greater than 4.0 was used then the solution project generator would crash. /// this test is to make sure the fix is not regressed. /// - [Fact] - public void TestTargetFrameworkVersionGreaterThan4() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void TestTargetFrameworkVersionGreaterThan4(bool useNewParser) { string tmpFileName = FileUtilities.GetTemporaryFileName(); string projectFilePath = tmpFileName + ".sln"; string solutionFileContents = - @" -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project('{E24C65DC-7377-472B-9ABA-BC803B73C61A}') = 'WebSite1', '..\WebSite1\', '{6B8F98F2-C976-4029-9321-5CCD73A174DA}' - ProjectSection(WebsiteProperties) = preProject - TargetFrameworkMoniker = '.NETFramework,Version=v4.34' - Debug.AspNetCompiler.VirtualPath = '/WebSite1' - Debug.AspNetCompiler.PhysicalPath = '..\WebSite1\' - Debug.AspNetCompiler.TargetPath = 'PrecompiledWeb\WebSite1\' - Debug.AspNetCompiler.Updateable = 'true' - Debug.AspNetCompiler.ForceOverwrite = 'true' - Debug.AspNetCompiler.FixedNames = 'false' - Debug.AspNetCompiler.Debug = 'True' - Release.AspNetCompiler.VirtualPath = '/WebSite1' - Release.AspNetCompiler.PhysicalPath = '..\WebSite1\' - Release.AspNetCompiler.TargetPath = 'PrecompiledWeb\WebSite1\' - Release.AspNetCompiler.Updateable = 'true' - Release.AspNetCompiler.ForceOverwrite = 'true' - Release.AspNetCompiler.FixedNames = 'false' - Release.AspNetCompiler.Debug = 'False' - VWDPort = '45602' - DefaultWebSiteLanguage = 'Visual Basic' - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6B8F98F2-C976-4029-9321-5CCD73A174DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6B8F98F2-C976-4029-9321-5CCD73A174DA}.Debug|Any CPU.Build.0 = Debug|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal - "; + """ + Microsoft Visual Studio Solution File, Format Version 11.00 + # Visual Studio 2010 + Project('{E24C65DC-7377-472B-9ABA-BC803B73C61A}') = 'WebSite1', '..\WebSite1\', '{6B8F98F2-C976-4029-9321-5CCD73A174DA}' + ProjectSection(WebsiteProperties) = preProject + TargetFrameworkMoniker = '.NETFramework,Version=v4.34' + Debug.AspNetCompiler.VirtualPath = '/WebSite1' + Debug.AspNetCompiler.PhysicalPath = '..\WebSite1\' + Debug.AspNetCompiler.TargetPath = 'PrecompiledWeb\WebSite1\' + Debug.AspNetCompiler.Updateable = 'true' + Debug.AspNetCompiler.ForceOverwrite = 'true' + Debug.AspNetCompiler.FixedNames = 'false' + Debug.AspNetCompiler.Debug = 'True' + Release.AspNetCompiler.VirtualPath = '/WebSite1' + Release.AspNetCompiler.PhysicalPath = '..\WebSite1\' + Release.AspNetCompiler.TargetPath = 'PrecompiledWeb\WebSite1\' + Release.AspNetCompiler.Updateable = 'true' + Release.AspNetCompiler.ForceOverwrite = 'true' + Release.AspNetCompiler.FixedNames = 'false' + Release.AspNetCompiler.Debug = 'False' + VWDPort = '45602' + DefaultWebSiteLanguage = 'Visual Basic' + EndProjectSection + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6B8F98F2-C976-4029-9321-5CCD73A174DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B8F98F2-C976-4029-9321-5CCD73A174DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + """; try { @@ -2226,7 +2291,9 @@ public void TestTargetFrameworkVersionGreaterThan4() globalProperties["Configuration"] = "Release"; globalProperties["SkipInvalidConfigurations"] = "true"; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents.Replace('\'', '"')); + + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); + using ProjectCollection collection = new ProjectCollection(); collection.RegisterLogger(logger); @@ -2256,14 +2323,16 @@ public void TestTargetFrameworkVersionGreaterThan4() /// /// Verifies that when target names are specified they end up in the metaproj. /// - [Fact] - public void CustomTargetNamesAreInInMetaproj() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CustomTargetNamesAreInInMetaproj(bool useNewParser) { - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper( - @" - Microsoft Visual Studio Solution File, Format Version 14.00 + string solutionFileContents = + """ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2015 - Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ClassLibrary1"", ""ClassLibrary1.csproj"", ""{6185CC21-BE89-448A-B3C0-D1C27112E595}"" + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1.csproj", "{6185CC21-BE89-448A-B3C0-D1C27112E595}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -2275,7 +2344,9 @@ public void CustomTargetNamesAreInInMetaproj() {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.ActiveCfg = CSConfig2|Any CPU EndGlobalSection EndGlobal - "); + """; + + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, BuildEventContext.Invalid, CreateMockLoggingService(), new List { "One" }); @@ -2304,14 +2375,16 @@ public void CustomTargetNamesAreInInMetaproj() /// /// Verifies that disambiguated target names are used when a project name matches a standard solution entry point. /// - [Fact] - public void DisambiguatedTargetNamesAreInInMetaproj() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void DisambiguatedTargetNamesAreInMetaproj(bool useNewParser) { - foreach(string projectName in ProjectInSolution.projectNamesToDisambiguate) + foreach (string projectName in ProjectInSolution.projectNamesToDisambiguate) { - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper( - $$""" - Microsoft Visual Studio Solution File, Format Version 14.00 + string solutionFileContents = + $$""" + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2015 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "{{projectName}}", "{{projectName}}.csproj", "{6185CC21-BE89-448A-B3C0-D1C27112E595}" EndProject @@ -2326,7 +2399,9 @@ public void DisambiguatedTargetNamesAreInInMetaproj() {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal - """); + """; + + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, null, null, BuildEventContext.Invalid, CreateMockLoggingService(), null); @@ -2349,29 +2424,33 @@ public void DisambiguatedTargetNamesAreInInMetaproj() /// Verifies that illegal user target names (the ones already used internally) don't crash the SolutionProjectGenerator /// [Theory] - [InlineData(false)] - [InlineData(true)] - public void IllegalUserTargetNamesDoNotThrow(bool forceCaseDifference) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public void IllegalUserTargetNamesDoNotThrow(bool forceCaseDifference, bool useNewParser) { - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper( - @" - Microsoft Visual Studio Solution File, Format Version 14.00 + string solutionFileContents = + """ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2015 - Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ClassLibrary1"", ""ClassLibrary1.csproj"", ""{6185CC21-BE89-448A-B3C0-D1C27112E595}"" + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1.csproj", "{6185CC21-BE89-448A-B3C0-D1C27112E595}" EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection EndGlobal - "); + """; + + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances; @@ -2426,31 +2505,34 @@ public void AfterTargetsComeFromImport() { string baseDirectory = Guid.NewGuid().ToString("N"); - string solutionFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"{Guid.NewGuid():N}.sln"), @" - Microsoft Visual Studio Solution File, Format Version 14.00 + string solutionFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"{Guid.NewGuid():N}.sln"), + """ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2015 - Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ClassLibrary1"", ""ClassLibrary1.csproj"", ""{6185CC21-BE89-448A-B3C0-D1C27112E595}"" + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1.csproj", "{6185CC21-BE89-448A-B3C0-D1C27112E595}" EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection EndGlobal - "); + """); - ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"after.{Path.GetFileName(solutionFilePath)}.targets"), @" - - + ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"after.{Path.GetFileName(solutionFilePath)}.targets"), + """ + + - "); + + """); try { @@ -2482,31 +2564,34 @@ public void BeforeTargetsFromImportCanHookDynamicTarget() { string baseDirectory = Guid.NewGuid().ToString("N"); - string solutionFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"{Guid.NewGuid():N}.sln"), @" - Microsoft Visual Studio Solution File, Format Version 14.00 + string solutionFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"{Guid.NewGuid():N}.sln"), + """ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2015 - Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ClassLibrary1"", ""ClassLibrary1.csproj"", ""{6185CC21-BE89-448A-B3C0-D1C27112E595}"" + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1.csproj", "{6185CC21-BE89-448A-B3C0-D1C27112E595}" EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection EndGlobal - "); + """); - ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"after.{Path.GetFileName(solutionFilePath)}.targets"), @" - - - + ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"after.{Path.GetFileName(solutionFilePath)}.targets"), + """ + + + - "); + + """); try { @@ -2557,48 +2642,55 @@ public void DirectorySolutionPropsTest(string projectName, bool enable) string baseDirectory = Guid.NewGuid().ToString("N"); - string solutionFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"{Guid.NewGuid():N}.sln"), @" - Microsoft Visual Studio Solution File, Format Version 14.00 + string solutionFilePath = ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, $"{Guid.NewGuid():N}.sln"), + """ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2015 - Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""ClassLibrary1"", ""ClassLibrary1.csproj"", ""{6185CC21-BE89-448A-B3C0-D1C27112E595}"" + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1.csproj", "{6185CC21-BE89-448A-B3C0-D1C27112E595}" EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6185CC21-BE89-448A-B3C0-D1C27112E595}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection EndGlobal - "); + """); - string projectPath = ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, projectName), $@" - + string projectPath = ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, projectName), + $$""" + - {expectedPropertyValue} + {{expectedPropertyValue}} - "); + + """); if (projectPath.StartsWith("Custom", StringComparison.OrdinalIgnoreCase)) { // If a custom file name was given, create a Directory.Solution.props and Directory.Build.targets to ensure that they aren't imported - ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, "Directory.Solution.props"), $@" - - - This file should not be imported - - "); - - ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, "Directory.Solution.targets"), $@" - - - This file should not be imported - - "); + ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, "Directory.Solution.props"), + """ + + + This file should not be imported + + + """); + + ObjectModelHelpers.CreateFileInTempProjectDirectory(Path.Combine(baseDirectory, "Directory.Solution.targets"), + """ + + + This file should not be imported + + + """); } try @@ -2640,20 +2732,23 @@ public void DirectorySolutionPropsTest(string projectName, bool enable) /// Regression test for https://github.com/dotnet/msbuild/issues/6236 /// [Theory] - [InlineData("http://localhost:8080")] - [InlineData("a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-")] - public void AbsolutePathWorksForUnsupportedPaths(string relativePath) + [InlineData("http://localhost:8080", false)] + [InlineData("http://localhost:8080", true)] + [InlineData("a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-", false)] + [InlineData("a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-", true)] + public void AbsolutePathWorksForUnsupportedPaths(string relativePath, bool useNewParser) { string solutionFileContents = - $@" -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31025.194 -MinimumVisualStudioVersion = 10.0.40219.1 -Project(""{{E24C65DC-7377-472B-9ABA-BC803B73C61A}}"") = ""WebSite1"", ""{relativePath}"", ""{{{{96E0707C-2E9C-4704-946F-FA583147737F}}}}"" -EndProject"; + $$""" + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio Version 16 + VisualStudioVersion = 16.0.31025.194 + MinimumVisualStudioVersion = 10.0.40219.1 + Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "WebSite1", "{{relativePath}}", "{96E0707C-2E9C-4704-946F-FA583147737F}" + EndProject + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInSolution projectInSolution = solution.ProjectsInOrder.ShouldHaveSingleItem(); @@ -2665,25 +2760,25 @@ public void AbsolutePathWorksForUnsupportedPaths(string relativePath) /// /// Create a Project derived from a Venus solution /// - private ProjectInstance CreateVenusSolutionProject() + private ProjectInstance CreateVenusSolutionProject(bool useNewParser) { - return CreateVenusSolutionProject(null, null); + return CreateVenusSolutionProject(null, null, useNewParser); } /// /// Create a Project derived from a Venus solution /// - private ProjectInstance CreateVenusSolutionProject(IDictionary globalProperties) + private ProjectInstance CreateVenusSolutionProject(IDictionary globalProperties, bool useNewParser) { - return CreateVenusSolutionProject(globalProperties, null); + return CreateVenusSolutionProject(globalProperties, null, useNewParser); } /// /// Create a Project derived from a Venus solution /// - private ProjectInstance CreateVenusSolutionProject(string toolsVersion) + private ProjectInstance CreateVenusSolutionProject(string toolsVersion, bool useNewParser) { - return CreateVenusSolutionProject(null, toolsVersion); + return CreateVenusSolutionProject(null, toolsVersion, useNewParser); } /// @@ -2692,10 +2787,10 @@ private ProjectInstance CreateVenusSolutionProject(string toolsVersion) /// /// The dictionary of global properties. May be null. /// The ToolsVersion override value. May be null. - private ProjectInstance CreateVenusSolutionProject(IDictionary globalProperties, string toolsVersion) + private ProjectInstance CreateVenusSolutionProject(IDictionary globalProperties, string toolsVersion, bool useNewParser) { string solutionFileContents = - @" + """ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project('{E24C65DC-7377-472B-9ABA-BC803B73C61A}') = 'C:\solutions\WebSite2\', '..\..\solutions\WebSite2\', '{F90528C4-6989-4D33-AFE8-F53173597CC2}' @@ -2727,9 +2822,9 @@ private ProjectInstance CreateVenusSolutionProject(IDictionary g {F90528C4-6989-4D33-AFE8-F53173597CC2}.Debug|Any CPU.Build.0 = Debug|.NET EndGlobalSection EndGlobal - "; + """; - SolutionFile solution = SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + SolutionFile solution = ParseSolutionHelper(solutionFileContents, useNewParser); ProjectInstance[] instances = SolutionProjectGenerator.Generate(solution, globalProperties, toolsVersion, BuildEventContext.Invalid, CreateMockLoggingService()); @@ -2776,6 +2871,12 @@ private void AssertProjectItemNameCount(ProjectInstance msbuildProject, string i Assert.Equal(count, itemGroup.Count()); } + private SolutionFile ParseSolutionHelper(string solutionFileContents, bool useNewParser) + { + return useNewParser ? SolutionFile_NewParser_Tests.ParseSolutionHelper(solutionFileContents) : + SolutionFile_OldParser_Tests.ParseSolutionHelper(solutionFileContents); + } + #endregion // Helper Functions } } From bdf1737b8ae0346bffd66aa6fb91db5ccf67c764 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 14 Oct 2024 18:27:44 +0200 Subject: [PATCH 18/46] add change wave 17.13 --- src/Framework/ChangeWaves.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Framework/ChangeWaves.cs b/src/Framework/ChangeWaves.cs index 1d682c4fc75..1b8994f2d47 100644 --- a/src/Framework/ChangeWaves.cs +++ b/src/Framework/ChangeWaves.cs @@ -27,7 +27,8 @@ internal static class ChangeWaves { internal static readonly Version Wave17_10 = new Version(17, 10); internal static readonly Version Wave17_12 = new Version(17, 12); - internal static readonly Version[] AllWaves = { Wave17_10, Wave17_12 }; + internal static readonly Version Wave17_13 = new Version(17, 13); + internal static readonly Version[] AllWaves = { Wave17_10, Wave17_12, Wave17_13 }; /// /// Special value indicating that all features behind all Change Waves should be enabled. From f527f7ad16ee55d566bf16cf514a17b981ef7357 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 14 Oct 2024 18:28:40 +0200 Subject: [PATCH 19/46] use new parser if 17.13 chqange wave enabled --- src/Build/Construction/Solution/SolutionFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index dd9497fe1be..b25d79c8363 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -193,7 +193,7 @@ internal int VisualStudioVersion internal bool UseNewParser => ShouldUseNewParser(_solutionFile); - internal static bool ShouldUseNewParser(string solutionFile) => FileUtilities.IsSolutionXFilename(solutionFile); + internal static bool ShouldUseNewParser(string solutionFile) => FileUtilities.IsSolutionXFilename(solutionFile) || ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_13); /// /// All projects in this solution, in the order they appeared in the solution file From f448ffca219fba23d4e0b7701b7ff92048010f2b Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 14 Oct 2024 18:37:23 +0200 Subject: [PATCH 20/46] fix test --- src/Build.UnitTests/Construction/SolutionFilter_Tests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs index e173c47c640..1b988d17b0c 100644 --- a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs @@ -71,14 +71,14 @@ public void SolutionFilterFiltersProjects(bool graphBuild) "); // Slashes here (and in the .slnf) are hardcoded as backslashes intentionally to support the common case. TransientTestFile solutionFile = testEnvironment.CreateFile(simpleProjectFolder, "SimpleProject.sln", - @" + """ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29326.124 MinimumVisualStudioVersion = 10.0.40219.1 - Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""SimpleProject"", ""SimpleProject\SimpleProject.csproj"", ""{79B5EBA6-5D27-4976-BC31-14422245A59A}"" + Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleProject", "SimpleProject\SimpleProject.csproj", "{79B5EBA6-5D27-4976-BC31-14422245A59A}" EndProject - Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""ClassLibrary"", ""..\ClassLibrary\ClassLibrary\ClassLibrary.csproj"", ""{8EFCCA22-9D51-4268-90F7-A595E11FCB2D}"" + Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClassLibrary", "..\ClassLibrary\ClassLibrary\ClassLibrary.csproj", "{8EFCCA22-9D51-4268-90F7-A595E11FCB2D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -106,7 +106,7 @@ public void SolutionFilterFiltersProjects(bool graphBuild) SolutionGuid = {DE7234EC-0C4D-4070-B66A-DCF1B4F0CFEF} EndGlobalSection EndGlobal - "); + """); TransientTestFile filterFile = testEnvironment.CreateFile(folder, "solutionFilter.slnf", @" { From 02021e3c402486e3be16079986afb03b25afd337 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 16 Oct 2024 11:53:06 +0200 Subject: [PATCH 21/46] throw ArgumentException if solutionFile is null or empty --- src/Build/Construction/Solution/SolutionFile.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index b25d79c8363..b1c7a7d0d57 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -221,6 +221,10 @@ internal string FullPath set { + if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_13) && string.IsNullOrEmpty(value)) + { + throw new ArgumentException(nameof(FullPath)); + } // Should already be canonicalized to a full path ErrorUtilities.VerifyThrowInternalRooted(value); // To reduce code duplication, this should be From 3e54b2a8421f57fbe938b1c69a4e720468400456 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 16 Oct 2024 15:41:54 +0200 Subject: [PATCH 22/46] fix unix tests --- .../Construction/SolutionFile_Tests.cs | 71 ++++++++++--------- .../SolutionFile_NewParser_Tests.cs | 8 ++- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index d133e45c3e4..a86fb4a2a42 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -97,7 +97,7 @@ public void ParseSolution_VC2(bool convertToSlnx) string expectedProjectName = convertToSlnx ? "Project name" : "Project name.myvctype"; Assert.Equal(expectedProjectName, solution.ProjectsInOrder[0].ProjectName); - Assert.Equal("Relative path\\to\\Project name.myvctype", solution.ProjectsInOrder[0].RelativePath); + Assert.Equal(ConvertToUnixPathIfNeeded("Relative path\\to\\Project name.myvctype"), solution.ProjectsInOrder[0].RelativePath); if (!convertToSlnx) { // When converting to SLNX, the project GUID is not preserved. @@ -190,17 +190,17 @@ public void BasicSolution(bool convertToSlnx) // When converting to slnx, the order of the projects is not preserved. ProjectInSolution consoleApplication1 = solution.ProjectsInOrder.First(p => p.ProjectName == "ConsoleApplication1"); - Assert.Equal(@"ConsoleApplication1\ConsoleApplication1.vbproj", consoleApplication1.RelativePath); + Assert.Equal(ConvertToUnixPathIfNeeded("ConsoleApplication1\\ConsoleApplication1.vbproj"), consoleApplication1.RelativePath); Assert.Empty(consoleApplication1.Dependencies); Assert.Null(consoleApplication1.ParentProjectGuid); ProjectInSolution vbClassLibrary = solution.ProjectsInOrder.First(p => p.ProjectName == "vbClassLibrary"); - Assert.Equal(@"vbClassLibrary\vbClassLibrary.vbproj", vbClassLibrary.RelativePath); + Assert.Equal(ConvertToUnixPathIfNeeded("vbClassLibrary\\vbClassLibrary.vbproj"), vbClassLibrary.RelativePath); Assert.Empty(vbClassLibrary.Dependencies); Assert.Null(vbClassLibrary.ParentProjectGuid); ProjectInSolution classLibrary1 = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary1"); - Assert.Equal(@"ClassLibrary1\ClassLibrary1.csproj", classLibrary1.RelativePath); + Assert.Equal(ConvertToUnixPathIfNeeded("ClassLibrary1\\ClassLibrary1.csproj"), classLibrary1.RelativePath); Assert.Empty(classLibrary1.Dependencies); Assert.Null(classLibrary1.ParentProjectGuid); @@ -270,14 +270,20 @@ public void SolutionFolders(bool convertToSlnx) Assert.Equal(3, solution.ProjectsInOrder.Count); - var classLibrary1 = solution.ProjectsInOrder.First(p => p.RelativePath == @"ClassLibrary1\ClassLibrary1.csproj"); + var classLibrary1 = solution.ProjectsInOrder + .FirstOrDefault(p => p.RelativePath == ConvertToUnixPathIfNeeded("ClassLibrary1\\ClassLibrary1.csproj")); + Assert.NotNull(classLibrary1); Assert.Empty(classLibrary1.Dependencies); Assert.Null(classLibrary1.ParentProjectGuid); - var myPhysicalFolderClassLibrary1 = solution.ProjectsInOrder.First(p => p.RelativePath == @"MyPhysicalFolder\ClassLibrary1\ClassLibrary1.csproj"); + var myPhysicalFolderClassLibrary1 = solution.ProjectsInOrder + .FirstOrDefault(p => p.RelativePath == ConvertToUnixPathIfNeeded("MyPhysicalFolder\\ClassLibrary1\\ClassLibrary1.csproj")); + Assert.NotNull(myPhysicalFolderClassLibrary1); Assert.Empty(myPhysicalFolderClassLibrary1.Dependencies); - var classLibrary2 = solution.ProjectsInOrder.First(p => p.RelativePath == @"ClassLibrary2\ClassLibrary2.csproj"); + var classLibrary2 = solution.ProjectsInOrder + .FirstOrDefault(p => p.RelativePath == ConvertToUnixPathIfNeeded("ClassLibrary2\\ClassLibrary2.csproj")); + Assert.NotNull(classLibrary2); Assert.Empty(classLibrary2.Dependencies); // When converting to slnx, the guids are not preserved. @@ -353,19 +359,19 @@ public void SolutionDependencies(bool convertToSlnx) var classLibrary2 = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary2"); var classLibrary3 = solution.ProjectsInOrder.First(p => p.ProjectName == "ClassLibrary3"); - Assert.Equal(@"ClassLibrary1\ClassLibrary1.csproj", classLibrary1.RelativePath); + Assert.Equal(ConvertToUnixPathIfNeeded("ClassLibrary1\\ClassLibrary1.csproj"), classLibrary1.RelativePath); Assert.Single(classLibrary1.Dependencies); Assert.Equal(classLibrary3.ProjectGuid, classLibrary1.Dependencies[0]); Assert.Null(solution.ProjectsInOrder[0].ParentProjectGuid); - Assert.Equal(@"ClassLibrary2\ClassLibrary2.csproj", classLibrary2.RelativePath); + Assert.Equal(ConvertToUnixPathIfNeeded("ClassLibrary2\\ClassLibrary2.csproj"), classLibrary2.RelativePath); Assert.Equal(2, classLibrary2.Dependencies.Count); // When converting to SLNX, the projects dependencies order is not preserved. Assert.Contains(classLibrary3.ProjectGuid, classLibrary2.Dependencies); Assert.Contains(classLibrary1.ProjectGuid, classLibrary2.Dependencies); Assert.Null(solution.ProjectsInOrder[1].ParentProjectGuid); - Assert.Equal(@"ClassLibrary3\ClassLibrary3.csproj", solution.ProjectsInOrder[2].RelativePath); + Assert.Equal(ConvertToUnixPathIfNeeded("ClassLibrary3\\ClassLibrary3.csproj"), solution.ProjectsInOrder[2].RelativePath); Assert.Empty(solution.ProjectsInOrder[2].Dependencies); Assert.Null(solution.ProjectsInOrder[2].ParentProjectGuid); } @@ -677,33 +683,30 @@ public void ParseProjectConfigurationsInSolutionConfigurations2(bool convertToSl private static SolutionFile ParseSolutionHelper(string solutionFileContents, bool convertToSlnx = false) { solutionFileContents = solutionFileContents.Replace('\'', '"'); - string solutionPath = FileUtilities.GetTemporaryFileName(".sln"); - string slnxPath = solutionPath + "x"; - try - { - File.WriteAllText(solutionPath, solutionFileContents); - if (convertToSlnx) - { - ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(solutionPath); - SolutionModel solutionModel = serializer.OpenAsync(solutionPath, CancellationToken.None).Result; - SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); - - SolutionFile slnx = SolutionFile.Parse(slnxPath); - return slnx; - } - - SolutionFile sln = SolutionFile.Parse(solutionPath); - return sln; - } - finally + + using (TestEnvironment testEnvironment = TestEnvironment.Create()) { - File.Delete(solutionPath); + TransientTestFile sln = testEnvironment.CreateFile(FileUtilities.GetTemporaryFileName(".sln"), solutionFileContents); - if (convertToSlnx) - { - File.Delete(slnxPath); - } + string solutionPath = convertToSlnx ? ConvertToSlnx(sln.Path) : sln.Path; + + return SolutionFile.Parse(solutionPath); } } + + private static string ConvertToSlnx(string slnPath) + { + string slnxPath = slnPath + "x"; + ISolutionSerializer serializer = SolutionSerializers.GetSerializerByMoniker(slnPath).ShouldNotBeNull(); + SolutionModel solutionModel = serializer.OpenAsync(slnPath, CancellationToken.None).Result; + SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); + return slnxPath; + } + + private static string ConvertToUnixPathIfNeeded(string path) + { + // In the new parser, ProjectModel.FilePath is converted to Unix-style. + return !NativeMethodsShared.IsWindows ? path.Replace('\\', '/') : path; + } } } diff --git a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs index 6db6e939e56..7f56b600dca 100644 --- a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs @@ -86,7 +86,7 @@ public void ProjectWithWebsiteProperties(bool convertToSlnx) solution.ProjectsInOrder[0].ProjectType.ShouldBe(SolutionProjectType.WebProject); solution.ProjectsInOrder[0].ProjectName.ShouldBe(@"C:\WebSites\WebApplication3\"); - solution.ProjectsInOrder[0].RelativePath.ShouldBe(@"C:\WebSites\WebApplication3\"); + solution.ProjectsInOrder[0].RelativePath.ShouldBe(ConvertToUnixPathIfNeeded(@"C:\WebSites\WebApplication3\")); solution.ProjectsInOrder[0].Dependencies.Count.ShouldBe(2); solution.ProjectsInOrder[0].ParentProjectGuid.ShouldBeNull(); solution.ProjectsInOrder[0].GetUniqueProjectName().ShouldBe(@"C:\WebSites\WebApplication3\"); @@ -153,5 +153,11 @@ private static string ConvertToSlnx(string slnPath) SolutionSerializers.SlnXml.SaveAsync(slnxPath, solutionModel, CancellationToken.None).Wait(); return slnxPath; } + + private static string ConvertToUnixPathIfNeeded(string path) + { + // In the new parser, ProjectModel.FilePath is converted to Unix-style. + return !NativeMethodsShared.IsWindows ? path.Replace('\\', '/') : path; + } } } From dab0dd8455d494f07ff3000c72b4fad31e013170 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 16 Oct 2024 15:44:59 +0200 Subject: [PATCH 23/46] reorder usings --- src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index a86fb4a2a42..01acb79505f 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -5,17 +5,17 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; +using System.Threading; using Microsoft.Build.Construction; using Microsoft.Build.Exceptions; using Microsoft.Build.Shared; -using Microsoft.VisualStudio.SolutionPersistence.Serializer; using Microsoft.VisualStudio.SolutionPersistence; +using Microsoft.VisualStudio.SolutionPersistence.Model; +using Microsoft.VisualStudio.SolutionPersistence.Serializer; using Shouldly; using Xunit; -using Microsoft.VisualStudio.SolutionPersistence.Model; -using System.Threading; -using System.Linq; #nullable disable From 2743bbb65fa8024f0274da712bcde33380a2a60d Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Wed, 16 Oct 2024 15:55:31 +0200 Subject: [PATCH 24/46] add 17.13 change wave to docs --- documentation/wiki/ChangeWaves.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/wiki/ChangeWaves.md b/documentation/wiki/ChangeWaves.md index 09e7ca1394c..79371ec92a0 100644 --- a/documentation/wiki/ChangeWaves.md +++ b/documentation/wiki/ChangeWaves.md @@ -23,6 +23,9 @@ A wave of features is set to "rotate out" (i.e. become standard functionality) t ## Current Rotation of Change Waves +### 17.13 +- [.SLNX support - use the new parser for .sln and .slnx]() + ### 17.12 - [Log TaskParameterEvent for scalar parameters](https://github.com/dotnet/msbuild/pull/9908) - [Convert.ToString during a property evaluation uses the InvariantCulture for all types](https://github.com/dotnet/msbuild/pull/9874) From f8bb751f835bc23eb604e2f275291db5bfda1ff4 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 17 Oct 2024 08:37:36 +0200 Subject: [PATCH 25/46] add this pr to change waves docs --- documentation/wiki/ChangeWaves.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/wiki/ChangeWaves.md b/documentation/wiki/ChangeWaves.md index 79371ec92a0..2efbeab95b3 100644 --- a/documentation/wiki/ChangeWaves.md +++ b/documentation/wiki/ChangeWaves.md @@ -24,7 +24,7 @@ A wave of features is set to "rotate out" (i.e. become standard functionality) t ## Current Rotation of Change Waves ### 17.13 -- [.SLNX support - use the new parser for .sln and .slnx]() +- [.SLNX support - use the new parser for .sln and .slnx](https://github.com/dotnet/msbuild/pull/10836) ### 17.12 - [Log TaskParameterEvent for scalar parameters](https://github.com/dotnet/msbuild/pull/9908) From dc79f39e425cd6ded98a316b8266bb57ea43b82e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 17 Oct 2024 08:39:24 +0200 Subject: [PATCH 26/46] small fix --- src/Build/Construction/Solution/SolutionFile.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 232ead07a13..96aecafa403 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -328,6 +328,7 @@ internal void ParseUsingNewParser() /// /// Maps to . + /// /// is a result of parsing solution using the new parser. /// /// private void ReadSolutionModel(SolutionModel solutionModel) From 6affa445f662d9e219ed27205c3b3c9338cb1473 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 17 Oct 2024 08:39:48 +0200 Subject: [PATCH 27/46] small fix --- src/Build/Construction/Solution/SolutionFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 96aecafa403..6246286f666 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -328,7 +328,7 @@ internal void ParseUsingNewParser() /// /// Maps to . - /// /// is a result of parsing solution using the new parser. + /// is a result of parsing solution using the new parser. /// /// private void ReadSolutionModel(SolutionModel solutionModel) From 741c7c845cb0a639784241959aaafd417b7b9169 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 17 Oct 2024 09:47:24 +0200 Subject: [PATCH 28/46] fix typo --- src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index 01acb79505f..39e31faa5e9 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -294,7 +294,7 @@ public void SolutionFolders(bool convertToSlnx) } else { - // try at list assert not null + // try at least assert not null Assert.NotNull(myPhysicalFolderClassLibrary1.ParentProjectGuid); Assert.NotNull(classLibrary2.ParentProjectGuid); } From 531d8cd7ba1647c3d1abca18c218d416bc3108f4 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 17 Oct 2024 10:22:54 +0200 Subject: [PATCH 29/46] fix tests --- .../Graph/GetCompatiblePlatformGraph_Tests.cs | 8 +++--- .../Graph/GraphLoadedFromSolution_tests.cs | 25 ++----------------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs b/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs index b941649ad74..d6344240f1a 100644 --- a/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs +++ b/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs @@ -402,14 +402,14 @@ public void SolutionWithoutAllConfigurations() // Slashes here (and in the .slnf) are hardcoded as backslashes intentionally to support the common case. TransientTestFile solutionFile = testEnvironment.CreateFile(folder, "SimpleProject.sln", - @" + """ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29326.124 MinimumVisualStudioVersion = 10.0.40219.1 - Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""Project1"", ""1\1\1.csproj"", ""{79B5EBA6-5D27-4976-BC31-14422245A59A}"" + Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project1", "1\1\1.csproj", "{79B5EBA6-5D27-4976-BC31-14422245A59A}" EndProject - Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""2"", ""2\2\2.proj"", ""{8EFCCA22-9D51-4268-90F7-A595E11FCB2D}"" + Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "2", "2\2\2.proj", "{8EFCCA22-9D51-4268-90F7-A595E11FCB2D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -434,7 +434,7 @@ public void SolutionWithoutAllConfigurations() SolutionGuid = {DE7234EC-0C4D-4070-B66A-DCF1B4F0CFEF} EndGlobalSection EndGlobal - "); + """); ProjectCollection projectCollection = testEnvironment.CreateProjectCollection().Collection; MockLogger logger = new(); diff --git a/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs b/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs index 431ea412875..6d535479b1e 100644 --- a/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs +++ b/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs @@ -59,7 +59,8 @@ public void GraphConstructionFailsOnNonExistentSolution() new ProjectGraph("nonExistent.sln"); }); - exception.Message.ShouldContain("The project file could not be loaded. Could not find file"); + exception.Message.ShouldContain("The project file could not be loaded."); + exception.Message.ShouldContain("Could not find file"); } [Fact] @@ -646,28 +647,6 @@ IEnumerable GetIncomingEdgeItemsToNode(ProjectGraphNode nod } } - [Fact] - public void GraphConstructionShouldThrowOnMissingSolutionDependencies() - { - var solutionContents = SolutionFileBuilder.FromGraphEdges( - _env, - new Dictionary { { 1, null }, { 2, null } }, - new[] { ("1", new[] { Guid.NewGuid().ToString("B") }) }).BuildSolution(); - - var solutionFile = _env.CreateFile( - "solution.sln", - solutionContents) - .Path; - - var exception = Should.Throw( - () => - { - new ProjectGraph(solutionFile); - }); - - exception.Message.ShouldContain("but a project with this GUID was not found in the .SLN file"); - } - private static bool IsSolutionItemReference(ProjectItemInstance edgeItem) { return edgeItem.ItemType == GraphBuilder.SolutionItemReference; From 092ba6540a0042644d4a53e4cb08ae2dad278e0e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 17 Oct 2024 12:13:52 +0200 Subject: [PATCH 30/46] reference package Microsoft.VisualStudio.SolutionPersistence in Microsoft..Build.Engine.UnitTests.csproj --- src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj index a5f9ba12a47..925a3c6b6e0 100644 --- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj +++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj @@ -22,6 +22,7 @@ + all From cae485d2aaab6ee6002f6592ccd52809802fd99c Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 17 Oct 2024 16:34:23 +0200 Subject: [PATCH 31/46] refererence Microsoft.VisualStudio.SolutionPersistence in MSBuild.csproj and in Microsoft.Build.Utilities.csproj --- src/MSBuild/MSBuild.csproj | 1 + src/Utilities/Microsoft.Build.Utilities.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index 2ce4c96e4bf..ad7d445466e 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -176,6 +176,7 @@ + diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index 6b8153dbc9c..b7235909df5 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -25,6 +25,7 @@ + From 4c35c2578892c175de1693225a8dbbb916e6e913 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 17 Oct 2024 16:40:54 +0200 Subject: [PATCH 32/46] Revert "refererence Microsoft.VisualStudio.SolutionPersistence in MSBuild.csproj and in Microsoft.Build.Utilities.csproj" This reverts commit cae485d2aaab6ee6002f6592ccd52809802fd99c. --- src/MSBuild/MSBuild.csproj | 1 - src/Utilities/Microsoft.Build.Utilities.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index ad7d445466e..2ce4c96e4bf 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -176,7 +176,6 @@ - diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index b7235909df5..6b8153dbc9c 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -25,7 +25,6 @@ - From d22aa3161cf8f48693c7c9040b2b97fe10f8e59a Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Thu, 17 Oct 2024 16:41:08 +0200 Subject: [PATCH 33/46] Revert "reference package Microsoft.VisualStudio.SolutionPersistence in Microsoft..Build.Engine.UnitTests.csproj" This reverts commit 092ba6540a0042644d4a53e4cb08ae2dad278e0e. --- src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj index 925a3c6b6e0..a5f9ba12a47 100644 --- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj +++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj @@ -22,7 +22,6 @@ - all From 18280dd1f86e66570ac8fd1388c169e38bf1bdf6 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova Date: Fri, 18 Oct 2024 14:54:44 +0200 Subject: [PATCH 34/46] fix bootstrap --- eng/BootStrapMsBuild.targets | 1 + eng/dependabot/Packages.props | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/BootStrapMsBuild.targets b/eng/BootStrapMsBuild.targets index dcff8617638..35b08dd7926 100644 --- a/eng/BootStrapMsBuild.targets +++ b/eng/BootStrapMsBuild.targets @@ -35,6 +35,7 @@ <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('Newtonsoft.Json'))' == 'true'" /> <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('NuGetSdkResolver'))' == 'true'" /> <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('Microsoft.Extensions.'))' == 'true'" /> + <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('Microsoft.VisualStudio.'))' == 'true'" /> <_NuGetRuntimeDependencies Include="%(RuntimeTargetsCopyLocalItems.Identity)" Condition="'@(RuntimeTargetsCopyLocalItems->Contains('NuGet.'))' == 'true'" /> diff --git a/eng/dependabot/Packages.props b/eng/dependabot/Packages.props index 4aab28833bb..f37eea5e042 100644 --- a/eng/dependabot/Packages.props +++ b/eng/dependabot/Packages.props @@ -61,7 +61,7 @@ - + From 5d85ba82023404e1a052993bc9731fb32c172851 Mon Sep 17 00:00:00 2001 From: YuliiaKovalova <95473390+YuliiaKovalova@users.noreply.github.com> Date: Fri, 25 Oct 2024 09:58:01 +0200 Subject: [PATCH 35/46] remove extra code Co-authored-by: kasperk81 <83082615+kasperk81@users.noreply.github.com> --- eng/BootStrapMsBuild.targets | 1 - 1 file changed, 1 deletion(-) diff --git a/eng/BootStrapMsBuild.targets b/eng/BootStrapMsBuild.targets index 35b08dd7926..dcff8617638 100644 --- a/eng/BootStrapMsBuild.targets +++ b/eng/BootStrapMsBuild.targets @@ -35,7 +35,6 @@ <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('Newtonsoft.Json'))' == 'true'" /> <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('NuGetSdkResolver'))' == 'true'" /> <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('Microsoft.Extensions.'))' == 'true'" /> - <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('Microsoft.VisualStudio.'))' == 'true'" /> <_NuGetRuntimeDependencies Include="%(RuntimeTargetsCopyLocalItems.Identity)" Condition="'@(RuntimeTargetsCopyLocalItems->Contains('NuGet.'))' == 'true'" /> From cbccc42e6be7d199b682d0e9134e26b8be785176 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 28 Oct 2024 18:51:29 +0100 Subject: [PATCH 36/46] add Microsoft.VisualStudio.SolutionPersistence to BootStrapMsBuild.targets --- eng/BootStrapMsBuild.targets | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/BootStrapMsBuild.targets b/eng/BootStrapMsBuild.targets index dcff8617638..ab7d16d0963 100644 --- a/eng/BootStrapMsBuild.targets +++ b/eng/BootStrapMsBuild.targets @@ -35,7 +35,8 @@ <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('Newtonsoft.Json'))' == 'true'" /> <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('NuGetSdkResolver'))' == 'true'" /> <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('Microsoft.Extensions.'))' == 'true'" /> - + <_NuGetRuntimeDependencies Include="%(RuntimeCopyLocalItems.Identity)" Condition="'@(RuntimeCopyLocalItems->Contains('Microsoft.VisualStudio.SolutionPersistence'))' == 'true'" /> + <_NuGetRuntimeDependencies Include="%(RuntimeTargetsCopyLocalItems.Identity)" Condition="'@(RuntimeTargetsCopyLocalItems->Contains('NuGet.'))' == 'true'" /> From ddaefd01323aa2b9b7612f55ddf17bdc2d5b55b7 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada <114938397+surayya-MS@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:53:50 +0100 Subject: [PATCH 37/46] Update src/Build/Construction/Solution/SolutionFile.cs Co-authored-by: Rainer Sigwald --- src/Build/Construction/Solution/SolutionFile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 6246286f666..837c11af143 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -223,7 +223,7 @@ internal string FullPath { if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_13) && string.IsNullOrEmpty(value)) { - throw new ArgumentException(nameof(FullPath)); + throw new ArgumentNullException(nameof(FullPath)); } // Should already be canonicalized to a full path ErrorUtilities.VerifyThrowInternalRooted(value); From 536200247d37ceed04b079ff43c198df1788229e Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada <114938397+surayya-MS@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:57:15 +0100 Subject: [PATCH 38/46] Update documentation/wiki/ChangeWaves.md Co-authored-by: Rainer Sigwald --- documentation/wiki/ChangeWaves.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/wiki/ChangeWaves.md b/documentation/wiki/ChangeWaves.md index 2efbeab95b3..5df8c06508f 100644 --- a/documentation/wiki/ChangeWaves.md +++ b/documentation/wiki/ChangeWaves.md @@ -23,7 +23,7 @@ A wave of features is set to "rotate out" (i.e. become standard functionality) t ## Current Rotation of Change Waves -### 17.13 +### 17.14 - [.SLNX support - use the new parser for .sln and .slnx](https://github.com/dotnet/msbuild/pull/10836) ### 17.12 From fb7655a60adb8095d4bf470f604db0e71303661c Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 28 Oct 2024 18:59:51 +0100 Subject: [PATCH 39/46] use 17.14 instead of 17.13 --- eng/Versions.props | 2 +- src/Build/Construction/Solution/SolutionFile.cs | 4 ++-- src/Framework/ChangeWaves.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 2109d36bf19..0b3badabcfb 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -2,7 +2,7 @@ - 17.13.0 + 17.14.0 17.12.0 15.1.0.0 preview diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 6246286f666..c2e3e503596 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -193,7 +193,7 @@ internal int VisualStudioVersion internal bool UseNewParser => ShouldUseNewParser(_solutionFile); - internal static bool ShouldUseNewParser(string solutionFile) => FileUtilities.IsSolutionXFilename(solutionFile) || ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_13); + internal static bool ShouldUseNewParser(string solutionFile) => FileUtilities.IsSolutionXFilename(solutionFile) || ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_14); /// /// All projects in this solution, in the order they appeared in the solution file @@ -221,7 +221,7 @@ internal string FullPath set { - if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_13) && string.IsNullOrEmpty(value)) + if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_14) && string.IsNullOrEmpty(value)) { throw new ArgumentException(nameof(FullPath)); } diff --git a/src/Framework/ChangeWaves.cs b/src/Framework/ChangeWaves.cs index 1b8994f2d47..8e58f93835c 100644 --- a/src/Framework/ChangeWaves.cs +++ b/src/Framework/ChangeWaves.cs @@ -27,8 +27,8 @@ internal static class ChangeWaves { internal static readonly Version Wave17_10 = new Version(17, 10); internal static readonly Version Wave17_12 = new Version(17, 12); - internal static readonly Version Wave17_13 = new Version(17, 13); - internal static readonly Version[] AllWaves = { Wave17_10, Wave17_12, Wave17_13 }; + internal static readonly Version Wave17_14 = new Version(17, 14); + internal static readonly Version[] AllWaves = { Wave17_10, Wave17_12, Wave17_14 }; /// /// Special value indicating that all features behind all Change Waves should be enabled. From 47448008e898749634de27cb2a2a2b604a050163 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 28 Oct 2024 19:02:56 +0100 Subject: [PATCH 40/46] fix space --- eng/dependabot/Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/dependabot/Packages.props b/eng/dependabot/Packages.props index f37eea5e042..4aab28833bb 100644 --- a/eng/dependabot/Packages.props +++ b/eng/dependabot/Packages.props @@ -61,7 +61,7 @@ - + From 23e5b72eb3e162a9ec6443766b45eaa805381f5a Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 28 Oct 2024 19:08:25 +0100 Subject: [PATCH 41/46] use Assert.Throws around the line that throws exception --- .../Construction/SolutionFile_Tests.cs | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index 39e31faa5e9..fcf15461cc3 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -32,31 +32,31 @@ public class SolutionFile_Tests [Fact] public void ParseSolution_VC() { + string solutionFileContents = + """ + Microsoft Visual Studio Solution File, Format Version 9.00 + # Visual Studio 2005 + Project('{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}') = 'Project name.vcproj', 'Relative path\to\Project name.vcproj', '{0ABED153-9451-483C-8140-9E8D7306B216}' + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|AnyCPU = Debug|AnyCPU + Release|AnyCPU = Release|AnyCPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.ActiveCfg = Debug|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.Build.0 = Debug|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.ActiveCfg = Release|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.Build.0 = Release|AnyCPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobalf + """; + Assert.Throws(() => { - string solutionFileContents = - """ - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}') = 'Project name.vcproj', 'Relative path\to\Project name.vcproj', '{0ABED153-9451-483C-8140-9E8D7306B216}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|AnyCPU = Debug|AnyCPU - Release|AnyCPU = Release|AnyCPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.ActiveCfg = Debug|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.Build.0 = Debug|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.ActiveCfg = Release|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.Build.0 = Release|AnyCPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - EndGlobal - """; - ParseSolutionHelper(solutionFileContents); Assert.Fail("Should not get here"); }); @@ -112,31 +112,31 @@ public void ParseSolution_VC2(bool convertToSlnx) [Fact] public void ParseSolution_EmptyProjectName() { + string solutionFileContents = + """ + Microsoft Visual Studio Solution File, Format Version 9.00 + # Visual Studio 2005 + Project('{Project GUID}') = '', 'src\.proj', '{0ABED153-9451-483C-8140-9E8D7306B216}' + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|AnyCPU = Debug|AnyCPU + Release|AnyCPU = Release|AnyCPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.ActiveCfg = Debug|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.Build.0 = Debug|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.ActiveCfg = Release|AnyCPU + {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.Build.0 = Release|AnyCPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + """; + Assert.Throws(() => { - string solutionFileContents = - """ - Microsoft Visual Studio Solution File, Format Version 9.00 - # Visual Studio 2005 - Project('{Project GUID}') = '', 'src\.proj', '{0ABED153-9451-483C-8140-9E8D7306B216}' - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|AnyCPU = Debug|AnyCPU - Release|AnyCPU = Release|AnyCPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.ActiveCfg = Debug|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Debug|AnyCPU.Build.0 = Debug|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.ActiveCfg = Release|AnyCPU - {0ABED153-9451-483C-8140-9E8D7306B216}.Release|AnyCPU.Build.0 = Release|AnyCPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - EndGlobal - """; - SolutionFile solution = ParseSolutionHelper(solutionFileContents); }); } From 7b01fc0aa7aaa012203b17486e717db2fa2fec05 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 28 Oct 2024 19:13:39 +0100 Subject: [PATCH 42/46] save long line for test --- .../Construction/SolutionProjectGenerator_Tests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs index faf38983bb1..cd359a54c8b 100644 --- a/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionProjectGenerator_Tests.cs @@ -38,6 +38,8 @@ public class SolutionProjectGenerator_Tests : IDisposable private static readonly BuildEventContext _buildEventContext = new BuildEventContext(0, 0, BuildEventContext.InvalidProjectContextId, 0); + private const string _longLineString = "a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-"; + public SolutionProjectGenerator_Tests(ITestOutputHelper output) { this.output = output; @@ -2734,8 +2736,8 @@ public void DirectorySolutionPropsTest(string projectName, bool enable) [Theory] [InlineData("http://localhost:8080", false)] [InlineData("http://localhost:8080", true)] - [InlineData("a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-", false)] - [InlineData("a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-a-really-long-string-", true)] + [InlineData(_longLineString, false)] + [InlineData(_longLineString, true)] public void AbsolutePathWorksForUnsupportedPaths(string relativePath, bool useNewParser) { string solutionFileContents = From 8b2308e6be88b7868529ff9891c9331367e34289 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 28 Oct 2024 19:26:43 +0100 Subject: [PATCH 43/46] set VersionPrefix to 17.13 --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index 0b3badabcfb..2109d36bf19 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -2,7 +2,7 @@ - 17.14.0 + 17.13.0 17.12.0 15.1.0.0 preview From 1066ee0b0e66f50994e0b28ad6294e0775b9c8a0 Mon Sep 17 00:00:00 2001 From: Surayya Huseyn Zada Date: Mon, 28 Oct 2024 19:35:41 +0100 Subject: [PATCH 44/46] fix bootstrap targets --- eng/BootStrapMsBuild.targets | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/BootStrapMsBuild.targets b/eng/BootStrapMsBuild.targets index ab7d16d0963..d6ea835a03a 100644 --- a/eng/BootStrapMsBuild.targets +++ b/eng/BootStrapMsBuild.targets @@ -49,7 +49,7 @@