diff --git a/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/App.sln b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/App.sln
new file mode 100644
index 000000000..6b63b4437
--- /dev/null
+++ b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/App.sln
@@ -0,0 +1,39 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26006.2
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{DDF3765C-59FB-4AA6-BE83-779ED13AA64A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{72BFCA87-B033-4721-8712-4D12166B4A39}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Debug|x64.ActiveCfg = Debug|x64
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Debug|x64.Build.0 = Debug|x64
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Debug|x86.ActiveCfg = Debug|x86
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Debug|x86.Build.0 = Debug|x86
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Release|x64.ActiveCfg = Release|x64
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Release|x64.Build.0 = Release|x64
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Release|x86.ActiveCfg = Release|x86
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {DDF3765C-59FB-4AA6-BE83-779ED13AA64A} = {72BFCA87-B033-4721-8712-4D12166B4A39}
+ EndGlobalSection
+EndGlobal
diff --git a/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/App/App.csproj b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/App/App.csproj
new file mode 100644
index 000000000..b38669c62
--- /dev/null
+++ b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/App/App.csproj
@@ -0,0 +1,15 @@
+
+
+ Exe
+ netcoreapp1.0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/App/Program.cs b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/App/Program.cs
new file mode 100644
index 000000000..abb853a4a
--- /dev/null
+++ b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/App/Program.cs
@@ -0,0 +1,10 @@
+using System;
+
+class Program
+{
+ static void Main(string[] args)
+ {
+ Console.WriteLine("Hello from the main app");
+ Console.WriteLine(Lib.Library.GetMessage());
+ }
+}
diff --git a/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/src/Lib/Lib.csproj b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/src/Lib/Lib.csproj
new file mode 100644
index 000000000..9f8bcbb51
--- /dev/null
+++ b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/src/Lib/Lib.csproj
@@ -0,0 +1,11 @@
+
+
+
+ netstandard1.4
+
+
+
+
+
+
+
diff --git a/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/src/Lib/Library.cs b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/src/Lib/Library.cs
new file mode 100644
index 000000000..205c42a01
--- /dev/null
+++ b/TestAssets/TestProjects/TestAppWithSlnAndSolutionFolders/src/src/Lib/Library.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Lib
+{
+ public class Library
+ {
+ public static string GetMessage()
+ {
+ return "Message from Lib";
+ }
+ }
+}
diff --git a/src/dotnet/SlnProjectCollectionExtensions.cs b/src/dotnet/SlnProjectCollectionExtensions.cs
index 1e964f775..d99b34eee 100644
--- a/src/dotnet/SlnProjectCollectionExtensions.cs
+++ b/src/dotnet/SlnProjectCollectionExtensions.cs
@@ -10,34 +10,18 @@ namespace Microsoft.DotNet.Tools.Common
{
public static class SlnProjectCollectionExtensions
{
- public static HashSet GetReferencedSolutionFolders(this SlnProjectCollection projects)
+ public static IEnumerable GetProjectsByType(
+ this SlnProjectCollection projects,
+ string typeGuid)
{
- var referencedSolutionFolders = new HashSet();
+ return projects.Where(p => p.TypeGuid == typeGuid);
+ }
- var solutionFolderProjects = projects
- .Where(p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid)
- .ToList();
-
- if (solutionFolderProjects.Any())
- {
- var nonSolutionFolderProjects = projects
- .Where(p => p.TypeGuid != ProjectTypeGuids.SolutionFolderGuid)
- .ToList();
-
- foreach (var project in nonSolutionFolderProjects)
- {
- var solutionFolders = project.GetSolutionFoldersFromProject();
- foreach (var solutionFolder in solutionFolders)
- {
- if (!referencedSolutionFolders.Contains(solutionFolder))
- {
- referencedSolutionFolders.Add(solutionFolder);
- }
- }
- }
- }
-
- return referencedSolutionFolders;
+ public static IEnumerable GetProjectsNotOfType(
+ this SlnProjectCollection projects,
+ string typeGuid)
+ {
+ return projects.Where(p => p.TypeGuid != typeGuid);
}
}
}
diff --git a/src/dotnet/SlnProjectExtensions.cs b/src/dotnet/SlnProjectExtensions.cs
index 14f730329..925089beb 100644
--- a/src/dotnet/SlnProjectExtensions.cs
+++ b/src/dotnet/SlnProjectExtensions.cs
@@ -12,17 +12,44 @@ namespace Microsoft.DotNet.Tools.Common
{
public static IList GetSolutionFoldersFromProject(this SlnProject project)
{
- var currentDirString = $".{Path.DirectorySeparatorChar}";
+ var solutionFolders = new List();
- var directoryPath = Path.GetDirectoryName(project.FilePath);
- if (directoryPath.StartsWith(currentDirString))
+ var projectFilePath = project.FilePath;
+ if (IsPathInTreeRootedAtSolutionDirectory(projectFilePath))
{
- directoryPath = directoryPath.Substring(currentDirString.Length);
+ var currentDirString = $".{Path.DirectorySeparatorChar}";
+ if (projectFilePath.StartsWith(currentDirString))
+ {
+ projectFilePath = projectFilePath.Substring(currentDirString.Length);
+ }
+
+ var projectDirectoryPath = TrimProject(projectFilePath);
+ if (!string.IsNullOrEmpty(projectDirectoryPath))
+ {
+ var solutionFoldersPath = TrimProjectDirectory(projectDirectoryPath);
+ if (!string.IsNullOrEmpty(solutionFoldersPath))
+ {
+ solutionFolders.AddRange(solutionFoldersPath.Split(Path.DirectorySeparatorChar));
+ }
+ }
}
- return directoryPath.StartsWith("..")
- ? new List()
- : new List(directoryPath.Split(Path.DirectorySeparatorChar));
+ return solutionFolders;
+ }
+
+ private static bool IsPathInTreeRootedAtSolutionDirectory(string path)
+ {
+ return !path.StartsWith("..");
+ }
+
+ private static string TrimProject(string path)
+ {
+ return Path.GetDirectoryName(path);
+ }
+
+ private static string TrimProjectDirectory(string path)
+ {
+ return Path.GetDirectoryName(path);
}
}
}
diff --git a/src/dotnet/commands/dotnet-sln/add/Program.cs b/src/dotnet/commands/dotnet-sln/add/Program.cs
index 5cb8869ce..889506662 100644
--- a/src/dotnet/commands/dotnet-sln/add/Program.cs
+++ b/src/dotnet/commands/dotnet-sln/add/Program.cs
@@ -163,28 +163,61 @@ namespace Microsoft.DotNet.Tools.Sln.Add
"NestedProjects",
SlnSectionType.PreProcess);
+ var pathToGuidMap = GetSolutionFolderPaths(slnFile, nestedProjectsSection.Properties);
+
string parentDirGuid = null;
+ var solutionFolderHierarchy = string.Empty;
foreach (var dir in solutionFolders)
{
- var solutionFolder = new SlnProject
+ solutionFolderHierarchy = Path.Combine(solutionFolderHierarchy, dir);
+ if (pathToGuidMap.ContainsKey(solutionFolderHierarchy))
{
- Id = Guid.NewGuid().ToString("B").ToUpper(),
- TypeGuid = ProjectTypeGuids.SolutionFolderGuid,
- Name = dir,
- FilePath = dir
- };
-
- slnFile.Projects.Add(solutionFolder);
-
- if (parentDirGuid != null)
- {
- nestedProjectsSection.Properties[solutionFolder.Id] = parentDirGuid;
+ parentDirGuid = pathToGuidMap[solutionFolderHierarchy];
+ }
+ else
+ {
+ var solutionFolder = new SlnProject
+ {
+ Id = Guid.NewGuid().ToString("B").ToUpper(),
+ TypeGuid = ProjectTypeGuids.SolutionFolderGuid,
+ Name = dir,
+ FilePath = dir
+ };
+
+ slnFile.Projects.Add(solutionFolder);
+
+ if (parentDirGuid != null)
+ {
+ nestedProjectsSection.Properties[solutionFolder.Id] = parentDirGuid;
+ }
+ parentDirGuid = solutionFolder.Id;
}
- parentDirGuid = solutionFolder.Id;
}
nestedProjectsSection.Properties[slnProject.Id] = parentDirGuid;
}
}
+
+ private IDictionary GetSolutionFolderPaths(SlnFile slnFile, SlnPropertySet nestedProjects)
+ {
+ var solutionFolderPaths = new Dictionary();
+
+ var solutionFolderProjects = slnFile.Projects.GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid);
+ foreach (var slnProject in solutionFolderProjects)
+ {
+ var path = slnProject.FilePath;
+ var id = slnProject.Id;
+ while (nestedProjects.ContainsKey(id))
+ {
+ id = nestedProjects[id];
+ var parentSlnProject = solutionFolderProjects.Where(p => p.Id == id).Single();
+ path = Path.Combine(parentSlnProject.FilePath, path);
+ }
+
+ solutionFolderPaths[path] = slnProject.Id;
+ }
+
+ return solutionFolderPaths;
+ }
}
}
diff --git a/src/dotnet/commands/dotnet-sln/remove/Program.cs b/src/dotnet/commands/dotnet-sln/remove/Program.cs
index deb9ea72a..18f3866e5 100644
--- a/src/dotnet/commands/dotnet-sln/remove/Program.cs
+++ b/src/dotnet/commands/dotnet-sln/remove/Program.cs
@@ -126,10 +126,8 @@ namespace Microsoft.DotNet.Tools.Sln.Remove
private void RemoveEmptySolutionFolders(SlnFile slnFile)
{
- var referencedSolutionFolders = slnFile.Projects.GetReferencedSolutionFolders();
-
var solutionFolderProjects = slnFile.Projects
- .Where(p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid)
+ .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid)
.ToList();
if (solutionFolderProjects.Any())
@@ -138,9 +136,13 @@ namespace Microsoft.DotNet.Tools.Sln.Remove
"NestedProjects",
SlnSectionType.PreProcess);
+ var solutionFoldersInUse = GetSolutionFoldersThatContainProjectsInItsHierarchy(
+ slnFile,
+ nestedProjectsSection.Properties);
+
foreach (var solutionFolderProject in solutionFolderProjects)
{
- if (!referencedSolutionFolders.Contains(solutionFolderProject.Name))
+ if (!solutionFoldersInUse.Contains(solutionFolderProject.Id))
{
slnFile.Projects.Remove(solutionFolderProject);
nestedProjectsSection.Properties.Remove(solutionFolderProject.Id);
@@ -153,5 +155,27 @@ namespace Microsoft.DotNet.Tools.Sln.Remove
}
}
}
+
+ private HashSet GetSolutionFoldersThatContainProjectsInItsHierarchy(
+ SlnFile slnFile,
+ SlnPropertySet nestedProjects)
+ {
+ var solutionFoldersInUse = new HashSet();
+
+ var nonSolutionFolderProjects = slnFile.Projects.GetProjectsNotOfType(
+ ProjectTypeGuids.SolutionFolderGuid);
+
+ foreach (var nonSolutionFolderProject in nonSolutionFolderProjects)
+ {
+ var id = nonSolutionFolderProject.Id;
+ while (nestedProjects.ContainsKey(id))
+ {
+ id = nestedProjects[id];
+ solutionFoldersInUse.Add(id);
+ }
+ }
+
+ return solutionFoldersInUse;
+ }
}
}
diff --git a/test/dotnet-sln-add.Tests/GivenDotnetSlnAdd.cs b/test/dotnet-sln-add.Tests/GivenDotnetSlnAdd.cs
index 4a0d87381..c1ca138d5 100644
--- a/test/dotnet-sln-add.Tests/GivenDotnetSlnAdd.cs
+++ b/test/dotnet-sln-add.Tests/GivenDotnetSlnAdd.cs
@@ -35,8 +35,6 @@ VisualStudioVersion = 15.0.26006.2
MinimumVisualStudioVersion = 10.0.40219.1
Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App\App.csproj"", ""{7072A694-548F-4CAE-A58F-12D257D5F486}""
EndProject
-Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""Lib"", ""Lib"", ""__LIB_FOLDER_GUID__""
-EndProject
Project(""{13B669BE-BB05-4DDF-9536-439F39A36129}"") = ""Lib"", ""Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__""
EndProject
Global
@@ -77,9 +75,6 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- __LIB_PROJECT_GUID__ = __LIB_FOLDER_GUID__
- EndGlobalSection
EndGlobal
";
@@ -88,8 +83,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26006.2
MinimumVisualStudioVersion = 10.0.40219.1
-Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""Lib"", ""Lib"", ""__LIB_FOLDER_GUID__""
-EndProject
Project(""{13B669BE-BB05-4DDF-9536-439F39A36129}"") = ""Lib"", ""Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__""
EndProject
Global
@@ -115,9 +108,6 @@ Global
__LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|x86
__LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|x86
EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- __LIB_PROJECT_GUID__ = __LIB_FOLDER_GUID__
- EndGlobalSection
EndGlobal
";
@@ -130,8 +120,6 @@ Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""App"", ""App.csproj"", "
EndProject
Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""src"", ""src"", ""__SRC_FOLDER_GUID__""
EndProject
-Project(""{2150E333-8FDC-42A3-9474-1A3956D46DE8}"") = ""Lib"", ""Lib"", ""__LIB_FOLDER_GUID__""
-EndProject
Project(""{13B669BE-BB05-4DDF-9536-439F39A36129}"") = ""Lib"", ""src\Lib\Lib.csproj"", ""__LIB_PROJECT_GUID__""
EndProject
Global
@@ -173,8 +161,7 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- __LIB_FOLDER_GUID__ = __SRC_FOLDER_GUID__
- __LIB_PROJECT_GUID__ = __LIB_FOLDER_GUID__
+ __LIB_PROJECT_GUID__ = __SRC_FOLDER_GUID__
EndGlobalSection
EndGlobal
";
@@ -345,6 +332,70 @@ EndGlobal
.Should().BeVisuallyEquivalentTo(expectedSlnContents);
}
+ [Fact]
+ public void WhenProjectDirectoryIsAddedSolutionFoldersAreNotCreated()
+ {
+ var projectDirectory = TestAssets
+ .Get("TestAppWithSlnAndCsprojFiles")
+ .CreateInstance()
+ .WithSourceFiles()
+ .Root
+ .FullName;
+
+ var projectToAdd = Path.Combine("Lib", "Lib.csproj");
+ var cmd = new DotnetCommand()
+ .WithWorkingDirectory(projectDirectory)
+ .ExecuteWithCapturedOutput($"sln App.sln add {projectToAdd}");
+ cmd.Should().Pass();
+
+ var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln"));
+ var solutionFolderProjects = slnFile.Projects.Where(
+ p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid);
+ solutionFolderProjects.Count().Should().Be(0);
+ slnFile.Sections.GetSection("NestedProjects").Should().BeNull();
+ }
+
+ [Theory]
+ [InlineData(".")]
+ [InlineData("")]
+ public void WhenSolutionFolderExistsItDoesNotGetAdded(string firstComponent)
+ {
+ var projectDirectory = TestAssets
+ .Get("TestAppWithSlnAndSolutionFolders")
+ .CreateInstance()
+ .WithSourceFiles()
+ .Root
+ .FullName;
+
+ var projectToAdd = Path.Combine($"{firstComponent}", "src", "src", "Lib", "Lib.csproj");
+ var cmd = new DotnetCommand()
+ .WithWorkingDirectory(projectDirectory)
+ .ExecuteWithCapturedOutput($"sln App.sln add {projectToAdd}");
+ cmd.Should().Pass();
+
+ var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln"));
+ slnFile.Projects.Count().Should().Be(4);
+
+ var solutionFolderProjects = slnFile.Projects.Where(
+ p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid);
+ solutionFolderProjects.Count().Should().Be(2);
+
+ var solutionFolders = slnFile.Sections.GetSection("NestedProjects").Properties;
+ solutionFolders.Count.Should().Be(3);
+
+ solutionFolders["{DDF3765C-59FB-4AA6-BE83-779ED13AA64A}"]
+ .Should().Be("{72BFCA87-B033-4721-8712-4D12166B4A39}");
+
+ var newlyAddedSrcFolder = solutionFolderProjects.Where(
+ p => p.Id != "{72BFCA87-B033-4721-8712-4D12166B4A39}").Single();
+ solutionFolders[newlyAddedSrcFolder.Id]
+ .Should().Be("{72BFCA87-B033-4721-8712-4D12166B4A39}");
+
+ var libProject = slnFile.Projects.Where(p => p.Name == "Lib").Single();
+ solutionFolders[libProject.Id]
+ .Should().Be(newlyAddedSrcFolder.Id);
+ }
+
[Theory]
[InlineData("TestAppWithSlnAndCsprojFiles", ExpectedSlnFileAfterAddingLibProj, "")]
[InlineData("TestAppWithSlnAndCsprojProjectGuidFiles", ExpectedSlnFileAfterAddingLibProj, "{84A45D44-B677-492D-A6DA-B3A71135AB8E}")]
@@ -574,12 +625,6 @@ EndGlobal
}
var slnContents = slnTemplate.Replace("__LIB_PROJECT_GUID__", expectedLibProjectGuid);
- var matchingLibFolder = slnFile.Projects
- .Where((p) => p.FilePath == "Lib")
- .ToList();
- matchingLibFolder.Count.Should().Be(1);
- slnContents = slnContents.Replace("__LIB_FOLDER_GUID__", matchingLibFolder[0].Id);
-
var matchingSrcFolder = slnFile.Projects
.Where((p) => p.FilePath == "src")
.ToList();