From e73d3f3f78f73a31656f79d463d51483ce3624ff Mon Sep 17 00:00:00 2001 From: David Federman Date: Wed, 6 Dec 2023 08:10:27 -0800 Subject: [PATCH 1/3] Fix sln-based graph builds with non-built projects with solution dependencies --- .../Graph/ProjectGraph_Tests.cs | 28 +++++-- src/Build/Graph/GraphBuilder.cs | 74 +++++++++---------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs index 825bbea6598..c0a16d5a18e 100644 --- a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs +++ b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs @@ -688,6 +688,7 @@ public void ConstructGraphWithSolution() * Project types other than "well-known" MSBuild project types: * Buildable project (wapproj) * Solution folder + * Project not included in build (also has solution dependencies as a regression test) * */ using (var env = TestEnvironment.Create()) @@ -708,7 +709,12 @@ public void ConstructGraphWithSolution() EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Project6", "Project6.wapproj", "{CA5CAD1A-224A-4171-B13A-F16E576FDD12}" EndProject - Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0392E290-973E-4086-A58E-F927AAA65B9A}" + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Project7", "Project7.csproj", "{28C7025E-2AB6-4962-A001-1E5B2271837C}" + ProjectSection(ProjectDependencies) = postProject + {52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98} = {52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98} + EndProjectSection + EndProject + Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0392E290-973E-4086-A58E-F927AAA65B9A}" ProjectSection(SolutionItems) = preProject SomeSolutionItemsFile = SomeSolutionItemsFile EndProjectSection @@ -777,15 +783,15 @@ public void ConstructGraphWithSolution() {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x86.Build.0 = Debug|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Debug|x86.Deploy.0 = Debug|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|Win32.ActiveCfg = Release|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|Win32.Build.0 = Release|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|Win32.Deploy.0 = Release|x86 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x64.ActiveCfg = Release|x64 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x64.Build.0 = Release|x64 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x64.Deploy.0 = Release|x64 {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x86.ActiveCfg = Release|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x86.Build.0 = Release|x86 - {CA5CAD1A-224A-4171-B13A-F16E576FDD12}.Release|x86.Deploy.0 = Release|x86 - EndGlobalSection + {28C7025E-2AB6-4962-A001-1E5B2271837C}.Debug|Win32.ActiveCfg = Debug|x86 + {28C7025E-2AB6-4962-A001-1E5B2271837C}.Debug|x64.ActiveCfg = Debug|x64 + {28C7025E-2AB6-4962-A001-1E5B2271837C}.Debug|x86.ActiveCfg = Debug|x86 + {28C7025E-2AB6-4962-A001-1E5B2271837C}.Release|Win32.ActiveCfg = Release|x86 + {28C7025E-2AB6-4962-A001-1E5B2271837C}.Release|x64.ActiveCfg = Release|x64 + {28C7025E-2AB6-4962-A001-1E5B2271837C}.Release|x86.ActiveCfg = Release|x86 + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection @@ -814,6 +820,7 @@ public void ConstructGraphWithSolution() ProjectRootElement project4Xml = ProjectRootElement.Create(); ProjectRootElement project5Xml = ProjectRootElement.Create(); ProjectRootElement project6Xml = ProjectRootElement.Create(); + ProjectRootElement project7Xml = ProjectRootElement.Create(); string project1Path = Path.Combine(env.DefaultTestDirectory.Path, "Project1.csproj"); string project2Path = Path.Combine(env.DefaultTestDirectory.Path, "Project2.vcxproj"); @@ -821,6 +828,7 @@ public void ConstructGraphWithSolution() string project4Path = Path.Combine(env.DefaultTestDirectory.Path, "Project4.vcxproj"); string project5Path = Path.Combine(env.DefaultTestDirectory.Path, "Project5.vcxproj"); string project6Path = Path.Combine(env.DefaultTestDirectory.Path, "Project6.wapproj"); + string project7Path = Path.Combine(env.DefaultTestDirectory.Path, "Project7.csproj"); project1Xml.Save(project1Path); project2Xml.Save(project2Path); @@ -828,6 +836,7 @@ public void ConstructGraphWithSolution() project4Xml.Save(project4Path); project5Xml.Save(project5Path); project6Xml.Save(project6Path); + project6Xml.Save(project7Path); var projectGraph = new ProjectGraph(slnFile.Path); projectGraph.EntryPointNodes.Count.ShouldBe(4); @@ -867,6 +876,9 @@ public void ConstructGraphWithSolution() project6Node.ProjectInstance.GlobalProperties["Configuration"].ShouldBe("Debug"); project6Node.ProjectInstance.GlobalProperties["Platform"].ShouldBe("x86"); project6Node.ProjectReferences.Count.ShouldBe(0); + + // Project not included in the build + Assert.DoesNotContain(projectGraph.ProjectNodes, node => node.ProjectInstance.FullPath == project7Path); } } diff --git a/src/Build/Graph/GraphBuilder.cs b/src/Build/Graph/GraphBuilder.cs index b4450000e4b..e343a98c4cc 100644 --- a/src/Build/Graph/GraphBuilder.cs +++ b/src/Build/Graph/GraphBuilder.cs @@ -306,7 +306,8 @@ private static void AddEdgesFromSolution(IReadOnlyDictionary> globalPropertiesForProjectConfiguration = new(StringComparer.OrdinalIgnoreCase); IReadOnlyList projectsInSolution = solution.ProjectsInOrder; - var newEntryPoints = new List(projectsInSolution.Count); + List newEntryPoints = new(projectsInSolution.Count); + Dictionary> solutionDependencies = new(); foreach (ProjectInSolution project in projectsInSolution) { @@ -332,11 +333,43 @@ private static void AddEdgesFromSolution(IReadOnlyDictionary 0) + { + // code snippet cloned from SolutionProjectGenerator.GetSolutionConfiguration + + List solutionDependenciesForProject = new(project.Dependencies.Count); + foreach (string dependencyProjectGuid in project.Dependencies) + { + if (!solution.ProjectsByGuid.TryGetValue(dependencyProjectGuid, out ProjectInSolution dependencyProject)) + { + ProjectFileErrorUtilities.ThrowInvalidProjectFile( + "SubCategoryForSolutionParsingErrors", + new BuildEventFileInfo(solution.FullPath), + "SolutionParseProjectDepNotFoundError", + project.ProjectGuid, + dependencyProjectGuid); + } + + // Add it to the list of dependencies, but only if it should build in this solution configuration + // (If a project is not selected for build in the solution configuration, it won't build even if it's depended on by something that IS selected for build) + // .. and only if it's known to be MSBuild format, as projects can't use the information otherwise + if (dependencyProject.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat) + { + solutionDependenciesForProject.Add(dependencyProject.AbsolutePath); + } + } + + if (solutionDependenciesForProject.Count > 0) + { + solutionDependencies.Add(project.AbsolutePath, solutionDependenciesForProject); + } + } } newEntryPoints.TrimExcess(); - return (newEntryPoints, GetSolutionDependencies(solution)); + return (newEntryPoints, solutionDependencies); SolutionConfigurationInSolution SelectSolutionConfiguration(SolutionFile solutionFile, IDictionary globalProperties) { @@ -367,43 +400,6 @@ ProjectConfigurationInSolution SelectProjectConfiguration( var partiallyMarchedConfig = projectConfigs.FirstOrDefault(pc => pc.Value.ConfigurationName.Equals(solutionConfig.ConfigurationName, StringComparison.OrdinalIgnoreCase)).Value; return partiallyMarchedConfig ?? projectConfigs.First().Value; } - - IReadOnlyDictionary> GetSolutionDependencies(SolutionFile solutionFile) - { - var solutionDependencies = new Dictionary>(); - - foreach (var projectWithDependencies in solutionFile.ProjectsInOrder.Where(p => p.Dependencies.Count != 0)) - { - solutionDependencies[projectWithDependencies.AbsolutePath] = projectWithDependencies.Dependencies.Select( - dependencyGuid => - { - // code snippet cloned from SolutionProjectGenerator.AddPropertyGroupForSolutionConfiguration - - if (!solutionFile.ProjectsByGuid.TryGetValue(dependencyGuid, out var dependencyProject)) - { - // If it's not itself part of the solution, that's an invalid solution - ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile( - dependencyProject != null, - "SubCategoryForSolutionParsingErrors", - new BuildEventFileInfo(solutionFile.FullPath), - "SolutionParseProjectDepNotFoundError", - projectWithDependencies.ProjectGuid, - dependencyGuid); - } - - // Add it to the list of dependencies, but only if it should build in this solution configuration - // (If a project is not selected for build in the solution configuration, it won't build even if it's depended on by something that IS selected for build) - // .. and only if it's known to be MSBuild format, as projects can't use the information otherwise - return dependencyProject?.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat - ? dependencyProject.AbsolutePath - : null; - }) - .Where(p => p != null) - .ToArray(); - } - - return solutionDependencies; - } } private static List AddGraphBuildPropertyToEntryPoints(IEnumerable entryPoints) From 7241306fb67af9555e24a871f5a706601ebf7458 Mon Sep 17 00:00:00 2001 From: David Federman Date: Mon, 11 Dec 2023 06:09:44 -0800 Subject: [PATCH 2/3] Update src/Build.UnitTests/Graph/ProjectGraph_Tests.cs Co-authored-by: Roman Konecny --- src/Build.UnitTests/Graph/ProjectGraph_Tests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs index c0a16d5a18e..ad47c1eb2e5 100644 --- a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs +++ b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs @@ -836,7 +836,7 @@ public void ConstructGraphWithSolution() project4Xml.Save(project4Path); project5Xml.Save(project5Path); project6Xml.Save(project6Path); - project6Xml.Save(project7Path); + project7Xml.Save(project7Path); var projectGraph = new ProjectGraph(slnFile.Path); projectGraph.EntryPointNodes.Count.ShouldBe(4); From 57c7bae8fd736d0788937be7780685f2caf3db57 Mon Sep 17 00:00:00 2001 From: David Federman Date: Mon, 11 Dec 2023 07:58:35 -0800 Subject: [PATCH 3/3] Add more to UT --- .../Graph/ProjectGraph_Tests.cs | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs index ad47c1eb2e5..f2bc8fd22a8 100644 --- a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs +++ b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs @@ -681,7 +681,7 @@ public void ConstructGraphWithSolution() /* * This test exercises various key features of solution-based builds: * From AssignProjectConfiguration: - * Adding synthetic project references + * Adding synthetic project references (defined both before and after the depending project) * Resolving project configuration based on the sln * Handling unresolved project references with ShouldUnsetParentConfigurationAndPlatform=true * Handling unresolved project references with ShouldUnsetParentConfigurationAndPlatform=false @@ -698,9 +698,12 @@ public void ConstructGraphWithSolution() # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 17.0.31903.59 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Project8", "Project8.csproj", "{2022C11A-1405-4983-BEC2-3A8B0233108F}" + EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Project1", "Project1.csproj", "{8761499A-7280-43C4-A32F-7F41C47CA6DF}" ProjectSection(ProjectDependencies) = postProject {52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98} = {52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98} + {2022C11A-1405-4983-BEC2-3A8B0233108F} = {2022C11A-1405-4983-BEC2-3A8B0233108F} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Project2", "Project2.vcxproj", "{D638A8EF-3A48-45F2-913C-88B29FED03CB}" @@ -714,7 +717,7 @@ public void ConstructGraphWithSolution() {52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98} = {52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98} EndProjectSection EndProject - Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0392E290-973E-4086-A58E-F927AAA65B9A}" + Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0392E290-973E-4086-A58E-F927AAA65B9A}" ProjectSection(SolutionItems) = preProject SomeSolutionItemsFile = SomeSolutionItemsFile EndProjectSection @@ -729,6 +732,18 @@ public void ConstructGraphWithSolution() Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Debug|Win32.ActiveCfg = Debug|x86 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Debug|Win32.Build.0 = Debug|x86 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Debug|x64.ActiveCfg = Debug|x64 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Debug|x64.Build.0 = Debug|x64 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Debug|x86.ActiveCfg = Debug|x86 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Debug|x86.Build.0 = Debug|x86 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Release|Win32.ActiveCfg = Release|x86 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Release|Win32.Build.0 = Release|x86 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Release|x64.ActiveCfg = Release|x64 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Release|x64.Build.0 = Release|x64 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Release|x86.ActiveCfg = Release|x86 + {2022C11A-1405-4983-BEC2-3A8B0233108F}.Release|x86.Build.0 = Release|x86 {8761499A-7280-43C4-A32F-7F41C47CA6DF}.Debug|Win32.ActiveCfg = Debug|x86 {8761499A-7280-43C4-A32F-7F41C47CA6DF}.Debug|Win32.Build.0 = Debug|x86 {8761499A-7280-43C4-A32F-7F41C47CA6DF}.Debug|x64.ActiveCfg = Debug|x64 @@ -791,7 +806,7 @@ public void ConstructGraphWithSolution() {28C7025E-2AB6-4962-A001-1E5B2271837C}.Release|Win32.ActiveCfg = Release|x86 {28C7025E-2AB6-4962-A001-1E5B2271837C}.Release|x64.ActiveCfg = Release|x64 {28C7025E-2AB6-4962-A001-1E5B2271837C}.Release|x86.ActiveCfg = Release|x86 - EndGlobalSection + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection @@ -802,7 +817,7 @@ public void ConstructGraphWithSolution() ProjectRootElement project1Xml = ProjectRootElement.Create(); - // Project 1 depends on Project 2 using ProjectReference but there is a sln-based dependency defined on Project 3 as well. + // Project 1 depends on Project 2 using ProjectReference but there is a sln-based dependency defined on Project 3 and 8 as well. project1Xml.AddItem("ProjectReference", "Project2.vcxproj"); ProjectRootElement project2Xml = ProjectRootElement.Create(); @@ -821,6 +836,7 @@ public void ConstructGraphWithSolution() ProjectRootElement project5Xml = ProjectRootElement.Create(); ProjectRootElement project6Xml = ProjectRootElement.Create(); ProjectRootElement project7Xml = ProjectRootElement.Create(); + ProjectRootElement project8Xml = ProjectRootElement.Create(); string project1Path = Path.Combine(env.DefaultTestDirectory.Path, "Project1.csproj"); string project2Path = Path.Combine(env.DefaultTestDirectory.Path, "Project2.vcxproj"); @@ -829,6 +845,7 @@ public void ConstructGraphWithSolution() string project5Path = Path.Combine(env.DefaultTestDirectory.Path, "Project5.vcxproj"); string project6Path = Path.Combine(env.DefaultTestDirectory.Path, "Project6.wapproj"); string project7Path = Path.Combine(env.DefaultTestDirectory.Path, "Project7.csproj"); + string project8Path = Path.Combine(env.DefaultTestDirectory.Path, "Project8.csproj"); project1Xml.Save(project1Path); project2Xml.Save(project2Path); @@ -837,27 +854,32 @@ public void ConstructGraphWithSolution() project5Xml.Save(project5Path); project6Xml.Save(project6Path); project7Xml.Save(project7Path); + project8Xml.Save(project8Path); var projectGraph = new ProjectGraph(slnFile.Path); - projectGraph.EntryPointNodes.Count.ShouldBe(4); + projectGraph.EntryPointNodes.Count.ShouldBe(5); + projectGraph.EntryPointNodes.Select(node => node.ProjectInstance.FullPath).ShouldBe(new[] { project1Path, project2Path, project3Path, project6Path, project8Path }, ignoreOrder: true); projectGraph.GraphRoots.Count.ShouldBe(2); projectGraph.GraphRoots.Select(node => node.ProjectInstance.FullPath).ShouldBe(new[] { project1Path, project6Path }, ignoreOrder: true); - projectGraph.ProjectNodes.Count.ShouldBe(6); + projectGraph.ProjectNodes.Count.ShouldBe(7); ProjectGraphNode project1Node = projectGraph.ProjectNodes.Single(node => node.ProjectInstance.FullPath == project1Path); project1Node.ProjectInstance.GlobalProperties["Configuration"].ShouldBe("Debug"); project1Node.ProjectInstance.GlobalProperties["Platform"].ShouldBe("x86"); - project1Node.ProjectReferences.Count.ShouldBe(2); + project1Node.ProjectReferences.Count.ShouldBe(3); + project1Node.ProjectReferences.Select(node => node.ProjectInstance.FullPath).ShouldBe(new[] { project2Path, project3Path, project8Path }, ignoreOrder: true); ProjectGraphNode project2Node = projectGraph.ProjectNodes.Single(node => node.ProjectInstance.FullPath == project2Path); project2Node.ProjectInstance.GlobalProperties["Configuration"].ShouldBe("Debug"); project2Node.ProjectInstance.GlobalProperties["Platform"].ShouldBe("Win32"); project2Node.ProjectReferences.Count.ShouldBe(1); + project2Node.ProjectReferences.Select(node => node.ProjectInstance.FullPath).ShouldBe(new[] { project4Path }, ignoreOrder: true); ProjectGraphNode project3Node = projectGraph.ProjectNodes.Single(node => node.ProjectInstance.FullPath == project3Path); project3Node.ProjectInstance.GlobalProperties["Configuration"].ShouldBe("Debug"); project3Node.ProjectInstance.GlobalProperties["Platform"].ShouldBe("Win32"); project3Node.ProjectReferences.Count.ShouldBe(1); + project3Node.ProjectReferences.Select(node => node.ProjectInstance.FullPath).ShouldBe(new[] { project5Path }, ignoreOrder: true); // Configuration and Platform get unset ProjectGraphNode project4Node = projectGraph.ProjectNodes.Single(node => node.ProjectInstance.FullPath == project4Path); @@ -879,6 +901,11 @@ public void ConstructGraphWithSolution() // Project not included in the build Assert.DoesNotContain(projectGraph.ProjectNodes, node => node.ProjectInstance.FullPath == project7Path); + + ProjectGraphNode project8Node = projectGraph.ProjectNodes.Single(node => node.ProjectInstance.FullPath == project8Path); + project8Node.ProjectInstance.GlobalProperties["Configuration"].ShouldBe("Debug"); + project8Node.ProjectInstance.GlobalProperties["Platform"].ShouldBe("x86"); + project8Node.ProjectReferences.Count.ShouldBe(0); } }