From 6210dab09cc3eb7214fcae2b9cf0b3d159106f95 Mon Sep 17 00:00:00 2001 From: Peter Huene Date: Tue, 21 Nov 2017 15:46:21 -0800 Subject: [PATCH] Remove project dependencies when removing a project from a solution. This commit fixes #6198. When a project is removed from a solution using the `sln remove` command, any projects in the solution with a project dependency (note: this is different from a project reference) on the project should have the project removed as a dependency. The fix is to scan the projects in the solution and remove any dependencies on the projects being removed. If the dependencies section is empty after the remove, we skip serialization of the section like Visual Studio does. --- .../App.sln | 44 ++++++++++++++ .../App/App.csproj | 8 +++ .../App/Program.cs | 12 ++++ .../First/Class1.cs | 8 +++ .../First/First.csproj | 7 +++ .../Second/Class1.cs | 8 +++ .../Second/Second.csproj | 7 +++ .../SlnFile.cs | 8 +++ src/dotnet/SlnFileExtensions.cs | 16 +++++ .../GivenDotnetSlnRemove.cs | 58 +++++++++++++++++++ 10 files changed, 176 insertions(+) create mode 100644 TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App.sln create mode 100644 TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App/App.csproj create mode 100644 TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App/Program.cs create mode 100644 TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/First/Class1.cs create mode 100644 TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/First/First.csproj create mode 100644 TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/Second/Class1.cs create mode 100644 TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/Second/Second.csproj diff --git a/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App.sln b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App.sln new file mode 100644 index 000000000..97c495544 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App.sln @@ -0,0 +1,44 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27110.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "App\App.csproj", "{BB02B949-F6BD-4872-95CB-96A05B1FE026}" + ProjectSection(ProjectDependencies) = postProject + {4E952B56-841D-461D-89A9-7977DDCC0625} = {4E952B56-841D-461D-89A9-7977DDCC0625} + {D53E177A-8ECF-43D5-A01E-98B884D53CA6} = {D53E177A-8ECF-43D5-A01E-98B884D53CA6} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "First", "First\First.csproj", "{D53E177A-8ECF-43D5-A01E-98B884D53CA6}" + ProjectSection(ProjectDependencies) = postProject + {4E952B56-841D-461D-89A9-7977DDCC0625} = {4E952B56-841D-461D-89A9-7977DDCC0625} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Second", "Second\Second.csproj", "{4E952B56-841D-461D-89A9-7977DDCC0625}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BB02B949-F6BD-4872-95CB-96A05B1FE026}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB02B949-F6BD-4872-95CB-96A05B1FE026}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB02B949-F6BD-4872-95CB-96A05B1FE026}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB02B949-F6BD-4872-95CB-96A05B1FE026}.Release|Any CPU.Build.0 = Release|Any CPU + {D53E177A-8ECF-43D5-A01E-98B884D53CA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D53E177A-8ECF-43D5-A01E-98B884D53CA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D53E177A-8ECF-43D5-A01E-98B884D53CA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D53E177A-8ECF-43D5-A01E-98B884D53CA6}.Release|Any CPU.Build.0 = Release|Any CPU + {4E952B56-841D-461D-89A9-7977DDCC0625}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E952B56-841D-461D-89A9-7977DDCC0625}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E952B56-841D-461D-89A9-7977DDCC0625}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E952B56-841D-461D-89A9-7977DDCC0625}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F6D9A973-1CFD-41C9-84F2-1471C0FE67DF} + EndGlobalSection +EndGlobal diff --git a/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App/App.csproj b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App/App.csproj new file mode 100644 index 000000000..ce1697ae8 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App/App.csproj @@ -0,0 +1,8 @@ + + + + Exe + netcoreapp2.0 + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App/Program.cs b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App/Program.cs new file mode 100644 index 000000000..be4278310 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/App/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace App +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/First/Class1.cs b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/First/Class1.cs new file mode 100644 index 000000000..3a358f6f2 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/First/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace First +{ + public class Class1 + { + } +} diff --git a/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/First/First.csproj b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/First/First.csproj new file mode 100644 index 000000000..9f5c4f4ab --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/First/First.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/Second/Class1.cs b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/Second/Class1.cs new file mode 100644 index 000000000..a39ee63c8 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/Second/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace Second +{ + public class Class1 + { + } +} diff --git a/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/Second/Second.csproj b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/Second/Second.csproj new file mode 100644 index 000000000..9f5c4f4ab --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnProjectDependencyToRemove/Second/Second.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/src/Microsoft.DotNet.Cli.Sln.Internal/SlnFile.cs b/src/Microsoft.DotNet.Cli.Sln.Internal/SlnFile.cs index 87fad297e..d81d579d2 100644 --- a/src/Microsoft.DotNet.Cli.Sln.Internal/SlnFile.cs +++ b/src/Microsoft.DotNet.Cli.Sln.Internal/SlnFile.cs @@ -285,6 +285,14 @@ namespace Microsoft.DotNet.Cli.Sln.Internal get { return _sections; } } + public SlnSection Dependencies + { + get + { + return _sections.GetSection("ProjectDependencies", SlnSectionType.PostProcess); + } + } + internal void Read(TextReader reader, string line, ref int curLineNum) { Line = curLineNum; diff --git a/src/dotnet/SlnFileExtensions.cs b/src/dotnet/SlnFileExtensions.cs index 16dce1b31..789318476 100644 --- a/src/dotnet/SlnFileExtensions.cs +++ b/src/dotnet/SlnFileExtensions.cs @@ -247,6 +247,22 @@ namespace Microsoft.DotNet.Tools.Common string.Format(CommonLocalizableStrings.ProjectReferenceRemoved, slnProject.FilePath)); } + foreach (var project in slnFile.Projects) + { + var dependencies = project.Dependencies; + if (dependencies == null) + { + continue; + } + + dependencies.SkipIfEmpty = true; + + foreach (var removed in projectsToRemove) + { + dependencies.Properties.Remove(removed.Id); + } + } + projectRemoved = true; } diff --git a/test/dotnet-sln-remove.Tests/GivenDotnetSlnRemove.cs b/test/dotnet-sln-remove.Tests/GivenDotnetSlnRemove.cs index 78f64b25f..0b0b6a7e2 100644 --- a/test/dotnet-sln-remove.Tests/GivenDotnetSlnRemove.cs +++ b/test/dotnet-sln-remove.Tests/GivenDotnetSlnRemove.cs @@ -169,6 +169,42 @@ Global {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 EndGlobalSection EndGlobal +"; + + private const string ExpectedSlnContentsAfterRemoveProjectWithDependencies = @" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27110.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""App"", ""App\App.csproj"", ""{BB02B949-F6BD-4872-95CB-96A05B1FE026}"" + ProjectSection(ProjectDependencies) = postProject + {D53E177A-8ECF-43D5-A01E-98B884D53CA6} = {D53E177A-8ECF-43D5-A01E-98B884D53CA6} + EndProjectSection +EndProject +Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""First"", ""First\First.csproj"", ""{D53E177A-8ECF-43D5-A01E-98B884D53CA6}"" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BB02B949-F6BD-4872-95CB-96A05B1FE026}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB02B949-F6BD-4872-95CB-96A05B1FE026}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB02B949-F6BD-4872-95CB-96A05B1FE026}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB02B949-F6BD-4872-95CB-96A05B1FE026}.Release|Any CPU.Build.0 = Release|Any CPU + {D53E177A-8ECF-43D5-A01E-98B884D53CA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D53E177A-8ECF-43D5-A01E-98B884D53CA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D53E177A-8ECF-43D5-A01E-98B884D53CA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D53E177A-8ECF-43D5-A01E-98B884D53CA6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F6D9A973-1CFD-41C9-84F2-1471C0FE67DF} + EndGlobalSection +EndGlobal "; [Theory] @@ -556,5 +592,27 @@ EndGlobal File.ReadAllText(solutionPath) .Should().BeVisuallyEquivalentTo(ExpectedSlnContentsAfterRemoveLastNestedProj); } + + [Fact] + public void WhenProjectIsRemovedThenDependenciesOnProjectAreAlsoRemoved() + { + var projectDirectory = TestAssets + .Get("TestAppWithSlnProjectDependencyToRemove") + .CreateInstance() + .WithSourceFiles() + .Root + .FullName; + + var solutionPath = Path.Combine(projectDirectory, "App.sln"); + + var projectToRemove = Path.Combine("Second", "Second.csproj"); + var cmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput($"sln remove {projectToRemove}"); + cmd.Should().Pass(); + + File.ReadAllText(solutionPath) + .Should().BeVisuallyEquivalentTo(ExpectedSlnContentsAfterRemoveProjectWithDependencies); + } } }