diff --git a/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/NoSolutionItemsAfterMigration.sln b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/NoSolutionItemsAfterMigration.sln
new file mode 100644
index 000000000..da0ae5935
--- /dev/null
+++ b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/NoSolutionItemsAfterMigration.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}") = "TestApp", "TestApp\TestApp.xproj", "{D65E5A1F-719F-4F95-8835-88BDD67AD457}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FAACC4BE-31AE-4EB7-A4C8-5BB4617EB4AF}"
+ ProjectSection(SolutionItems) = preProject
+ global.json = global.json
+ EndProjectSection
+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
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|x64.ActiveCfg = Debug|x64
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|x64.Build.0 = Debug|x64
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|x86.ActiveCfg = Debug|x86
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|x86.Build.0 = Debug|x86
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|x64.ActiveCfg = Release|x64
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|x64.Build.0 = Release|x64
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|x86.ActiveCfg = Release|x86
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/ReadmeSolutionItemAfterMigration.sln b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/ReadmeSolutionItemAfterMigration.sln
new file mode 100644
index 000000000..05aecb75d
--- /dev/null
+++ b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/ReadmeSolutionItemAfterMigration.sln
@@ -0,0 +1,40 @@
+
+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}") = "TestApp", "TestApp\TestApp.xproj", "{D65E5A1F-719F-4F95-8835-88BDD67AD457}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FAACC4BE-31AE-4EB7-A4C8-5BB4617EB4AF}"
+ ProjectSection(SolutionItems) = preProject
+ global.json = global.json
+ readme.txt = readme.txt
+ EndProjectSection
+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
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|x64.ActiveCfg = Debug|x64
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|x64.Build.0 = Debug|x64
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|x86.ActiveCfg = Debug|x86
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Debug|x86.Build.0 = Debug|x86
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|x64.ActiveCfg = Release|x64
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|x64.Build.0 = Release|x64
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|x86.ActiveCfg = Release|x86
+ {D65E5A1F-719F-4F95-8835-88BDD67AD457}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/TestApp/Program.cs b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/TestApp/Program.cs
new file mode 100644
index 000000000..2289ac741
--- /dev/null
+++ b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/TestApp/Program.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+namespace TestApp
+{
+ public class Program
+ {
+ public static int Main(string[] args)
+ {
+ Console.WriteLine("Hello World!");
+ return 0;
+ }
+ }
+}
diff --git a/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/TestApp/TestApp.xproj b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/TestApp/TestApp.xproj
new file mode 100644
index 000000000..d18702195
--- /dev/null
+++ b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/TestApp/TestApp.xproj
@@ -0,0 +1,18 @@
+
+
+
+ 14.0.23107
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 0138cb8f-4aa9-4029-a21e-c07c30f425ba
+ TestAppWithContents
+ ..\..\..\artifacts\obj\$(MSBuildProjectName)
+ ..\..\..\artifacts\
+
+
+ 2.0
+
+
+
diff --git a/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/TestApp/project.json b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/TestApp/project.json
new file mode 100644
index 000000000..166d41c2b
--- /dev/null
+++ b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/TestApp/project.json
@@ -0,0 +1,26 @@
+{
+ "version": "1.0.0-*",
+ "buildOptions": {
+ "emitEntryPoint": true,
+ "preserveCompilationContext": true
+ },
+ "dependencies": {
+ "Microsoft.NETCore.App": "1.0.1"
+ },
+ "frameworks": {
+ "netcoreapp1.0": {}
+ },
+ "runtimes": {
+ "win7-x64": {},
+ "win7-x86": {},
+ "osx.10.10-x64": {},
+ "osx.10.11-x64": {},
+ "ubuntu.14.04-x64": {},
+ "ubuntu.16.04-x64": {},
+ "centos.7-x64": {},
+ "rhel.7.2-x64": {},
+ "debian.8-x64": {},
+ "fedora.23-x64": {},
+ "opensuse.13.2-x64": {}
+ }
+}
diff --git a/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/global.json b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/global.json
new file mode 100644
index 000000000..22936715c
--- /dev/null
+++ b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/global.json
@@ -0,0 +1,3 @@
+{
+ "projects": [ "." ]
+}
diff --git a/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/readme.txt b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/readme.txt
new file mode 100644
index 000000000..6e3eb33f4
--- /dev/null
+++ b/TestAssets/NonRestoredTestProjects/PJAppWithSlnAndSolutionItemsToMoveToBackup/readme.txt
@@ -0,0 +1 @@
+This is just for our test to verify that we do not remove the readme.txt link from the solution.
diff --git a/src/dotnet/SlnFileExtensions.cs b/src/dotnet/SlnFileExtensions.cs
new file mode 100644
index 000000000..7fbf0fb70
--- /dev/null
+++ b/src/dotnet/SlnFileExtensions.cs
@@ -0,0 +1,330 @@
+// Copyright (c) .NET Foundation and contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Build.Construction;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Execution;
+using Microsoft.DotNet.Cli.Sln.Internal;
+using Microsoft.DotNet.Cli.Utils;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Microsoft.DotNet.Tools.Common
+{
+ internal static class SlnFileExtensions
+ {
+ public static void AddProject(this SlnFile slnFile, string fullProjectPath)
+ {
+ if (string.IsNullOrEmpty(fullProjectPath))
+ {
+ throw new ArgumentException();
+ }
+
+ var relativeProjectPath = PathUtility.GetRelativePath(
+ PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory),
+ fullProjectPath);
+
+ if (slnFile.Projects.Any((p) =>
+ string.Equals(p.FilePath, relativeProjectPath, StringComparison.OrdinalIgnoreCase)))
+ {
+ Reporter.Output.WriteLine(string.Format(
+ CommonLocalizableStrings.SolutionAlreadyContainsProject,
+ slnFile.FullPath,
+ relativeProjectPath));
+ }
+ else
+ {
+ var projectInstance = new ProjectInstance(fullProjectPath);
+
+ var slnProject = new SlnProject
+ {
+ Id = projectInstance.GetProjectId(),
+ TypeGuid = projectInstance.GetProjectTypeGuid(),
+ Name = Path.GetFileNameWithoutExtension(relativeProjectPath),
+ FilePath = relativeProjectPath
+ };
+
+ slnFile.AddDefaultBuildConfigurations(slnProject);
+
+ slnFile.AddSolutionFolders(slnProject);
+
+ slnFile.Projects.Add(slnProject);
+
+ Reporter.Output.WriteLine(
+ string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, relativeProjectPath));
+ }
+ }
+
+ public static void AddDefaultBuildConfigurations(this SlnFile slnFile, SlnProject slnProject)
+ {
+ if (slnProject == null)
+ {
+ throw new ArgumentException();
+ }
+
+ var defaultConfigurations = new List()
+ {
+ "Debug|Any CPU",
+ "Debug|x64",
+ "Debug|x86",
+ "Release|Any CPU",
+ "Release|x64",
+ "Release|x86",
+ };
+
+ // NOTE: The order you create the sections determines the order they are written to the sln
+ // file. In the case of an empty sln file, in order to make sure the solution configurations
+ // section comes first we need to add it first. This doesn't affect correctness but does
+ // stop VS from re-ordering things later on. Since we are keeping the SlnFile class low-level
+ // it shouldn't care about the VS implementation details. That's why we handle this here.
+ AddDefaultSolutionConfigurations(defaultConfigurations, slnFile.SolutionConfigurationsSection);
+ AddDefaultProjectConfigurations(
+ defaultConfigurations,
+ slnFile.ProjectConfigurationsSection.GetOrCreatePropertySet(slnProject.Id));
+ }
+
+ private static void AddDefaultSolutionConfigurations(
+ List defaultConfigurations,
+ SlnPropertySet solutionConfigs)
+ {
+ foreach (var config in defaultConfigurations)
+ {
+ if (!solutionConfigs.ContainsKey(config))
+ {
+ solutionConfigs[config] = config;
+ }
+ }
+ }
+
+ private static void AddDefaultProjectConfigurations(
+ List defaultConfigurations,
+ SlnPropertySet projectConfigs)
+ {
+ foreach (var config in defaultConfigurations)
+ {
+ var activeCfgKey = $"{config}.ActiveCfg";
+ if (!projectConfigs.ContainsKey(activeCfgKey))
+ {
+ projectConfigs[activeCfgKey] = config;
+ }
+
+ var build0Key = $"{config}.Build.0";
+ if (!projectConfigs.ContainsKey(build0Key))
+ {
+ projectConfigs[build0Key] = config;
+ }
+ }
+ }
+
+ public static void AddSolutionFolders(this SlnFile slnFile, SlnProject slnProject)
+ {
+ if (slnProject == null)
+ {
+ throw new ArgumentException();
+ }
+
+ var solutionFolders = slnProject.GetSolutionFoldersFromProject();
+
+ if (solutionFolders.Any())
+ {
+ var nestedProjectsSection = slnFile.Sections.GetOrCreateSection(
+ "NestedProjects",
+ SlnSectionType.PreProcess);
+
+ var pathToGuidMap = slnFile.GetSolutionFolderPaths(nestedProjectsSection.Properties);
+
+ string parentDirGuid = null;
+ var solutionFolderHierarchy = string.Empty;
+ foreach (var dir in solutionFolders)
+ {
+ solutionFolderHierarchy = Path.Combine(solutionFolderHierarchy, dir);
+ if (pathToGuidMap.ContainsKey(solutionFolderHierarchy))
+ {
+ 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;
+ }
+ }
+
+ nestedProjectsSection.Properties[slnProject.Id] = parentDirGuid;
+ }
+ }
+
+ private static IDictionary GetSolutionFolderPaths(
+ this 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;
+ }
+
+ public static bool RemoveProject(this SlnFile slnFile, string projectPath)
+ {
+ if (string.IsNullOrEmpty(projectPath))
+ {
+ throw new ArgumentException();
+ }
+
+ var projectPathNormalized = PathUtility.GetPathWithDirectorySeparator(projectPath);
+
+ var projectsToRemove = slnFile.Projects.Where((p) =>
+ string.Equals(p.FilePath, projectPathNormalized, StringComparison.OrdinalIgnoreCase)).ToList();
+
+ bool projectRemoved = false;
+ if (projectsToRemove.Count == 0)
+ {
+ Reporter.Output.WriteLine(string.Format(
+ CommonLocalizableStrings.ProjectReferenceCouldNotBeFound,
+ projectPath));
+ }
+ else
+ {
+ foreach (var slnProject in projectsToRemove)
+ {
+ var buildConfigsToRemove = slnFile.ProjectConfigurationsSection.GetPropertySet(slnProject.Id);
+ if (buildConfigsToRemove != null)
+ {
+ slnFile.ProjectConfigurationsSection.Remove(buildConfigsToRemove);
+ }
+
+ var nestedProjectsSection = slnFile.Sections.GetSection(
+ "NestedProjects",
+ SlnSectionType.PreProcess);
+ if (nestedProjectsSection != null && nestedProjectsSection.Properties.ContainsKey(slnProject.Id))
+ {
+ nestedProjectsSection.Properties.Remove(slnProject.Id);
+ }
+
+ slnFile.Projects.Remove(slnProject);
+ Reporter.Output.WriteLine(
+ string.Format(CommonLocalizableStrings.ProjectReferenceRemoved, slnProject.FilePath));
+ }
+
+ projectRemoved = true;
+ }
+
+ return projectRemoved;
+ }
+
+ public static void RemoveEmptyConfigurationSections(this SlnFile slnFile)
+ {
+ if (slnFile.Projects.Count == 0)
+ {
+ var solutionConfigs = slnFile.Sections.GetSection("SolutionConfigurationPlatforms");
+ if (solutionConfigs != null)
+ {
+ slnFile.Sections.Remove(solutionConfigs);
+ }
+
+ var projectConfigs = slnFile.Sections.GetSection("ProjectConfigurationPlatforms");
+ if (projectConfigs != null)
+ {
+ slnFile.Sections.Remove(projectConfigs);
+ }
+ }
+ }
+
+ public static void RemoveEmptySolutionFolders(this SlnFile slnFile)
+ {
+ var solutionFolderProjects = slnFile.Projects
+ .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid)
+ .ToList();
+
+ if (solutionFolderProjects.Any())
+ {
+ var nestedProjectsSection = slnFile.Sections.GetSection(
+ "NestedProjects",
+ SlnSectionType.PreProcess);
+
+ if (nestedProjectsSection == null)
+ {
+ foreach (var solutionFolderProject in solutionFolderProjects)
+ {
+ if (solutionFolderProject.Sections.Count() == 0)
+ {
+ slnFile.Projects.Remove(solutionFolderProject);
+ }
+ }
+ }
+ else
+ {
+ var solutionFoldersInUse = slnFile.GetSolutionFoldersThatContainProjectsInItsHierarchy(
+ nestedProjectsSection.Properties);
+
+ foreach (var solutionFolderProject in solutionFolderProjects)
+ {
+ if (!solutionFoldersInUse.Contains(solutionFolderProject.Id))
+ {
+ nestedProjectsSection.Properties.Remove(solutionFolderProject.Id);
+ if (solutionFolderProject.Sections.Count() == 0)
+ {
+ slnFile.Projects.Remove(solutionFolderProject);
+ }
+ }
+ }
+
+ if (nestedProjectsSection.IsEmpty)
+ {
+ slnFile.Sections.Remove(nestedProjectsSection);
+ }
+ }
+ }
+ }
+
+ private static HashSet GetSolutionFoldersThatContainProjectsInItsHierarchy(
+ this 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/src/dotnet/SlnProjectCollectionExtensions.cs b/src/dotnet/SlnProjectCollectionExtensions.cs
index d99b34eee..09499e5a2 100644
--- a/src/dotnet/SlnProjectCollectionExtensions.cs
+++ b/src/dotnet/SlnProjectCollectionExtensions.cs
@@ -8,7 +8,7 @@ using System.Linq;
namespace Microsoft.DotNet.Tools.Common
{
- public static class SlnProjectCollectionExtensions
+ internal static class SlnProjectCollectionExtensions
{
public static IEnumerable GetProjectsByType(
this SlnProjectCollection projects,
diff --git a/src/dotnet/SlnProjectExtensions.cs b/src/dotnet/SlnProjectExtensions.cs
index 925089beb..80ebf5d3e 100644
--- a/src/dotnet/SlnProjectExtensions.cs
+++ b/src/dotnet/SlnProjectExtensions.cs
@@ -8,7 +8,7 @@ using System.Linq;
namespace Microsoft.DotNet.Tools.Common
{
- public static class SlnProjectExtensions
+ internal static class SlnProjectExtensions
{
public static IList GetSolutionFoldersFromProject(this SlnProject project)
{
diff --git a/src/dotnet/commands/dotnet-add/dotnet-add-reference/xlf/LocalizableStrings.de.xlf b/src/dotnet/commands/dotnet-add/dotnet-add-reference/xlf/LocalizableStrings.de.xlf
index becde7aba..ab216036c 100644
--- a/src/dotnet/commands/dotnet-add/dotnet-add-reference/xlf/LocalizableStrings.de.xlf
+++ b/src/dotnet/commands/dotnet-add/dotnet-add-reference/xlf/LocalizableStrings.de.xlf
@@ -10,7 +10,7 @@
Command to add project to project reference
- Befehl zum Hinzufügen des Projekts zum Projektverweis
+ Befehl zum Hinzufügen eines Projekt-zu-Projekt-Verweises
diff --git a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs
index 34c8bbc53..b50aca8d1 100644
--- a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs
+++ b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs
@@ -162,6 +162,8 @@ namespace Microsoft.DotNet.Tools.Migrate
_slnFile.MinimumVisualStudioVersion = MinimumVisualStudioVersion;
}
+ RemoveReferencesToMigratedFiles(_slnFile);
+
_slnFile.Write();
foreach (var csprojFile in csprojFilesToAdd)
@@ -170,6 +172,26 @@ namespace Microsoft.DotNet.Tools.Migrate
}
}
+ private void RemoveReferencesToMigratedFiles(SlnFile slnFile)
+ {
+ var solutionFolders = slnFile.Projects.GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid);
+
+ foreach (var solutionFolder in solutionFolders)
+ {
+ var solutionItems = solutionFolder.Sections.GetSection("SolutionItems");
+ if (solutionItems != null && solutionItems.Properties.ContainsKey("global.json"))
+ {
+ solutionItems.Properties.Remove("global.json");
+ if (solutionItems.IsEmpty)
+ {
+ solutionFolder.Sections.Remove(solutionItems);
+ }
+ }
+ }
+
+ slnFile.RemoveEmptySolutionFolders();
+ }
+
private void AddProject(string slnPath, string csprojPath)
{
List args = new List()
diff --git a/src/dotnet/commands/dotnet-sln/add/Program.cs b/src/dotnet/commands/dotnet-sln/add/Program.cs
index 889506662..6b6124a29 100644
--- a/src/dotnet/commands/dotnet-sln/add/Program.cs
+++ b/src/dotnet/commands/dotnet-sln/add/Program.cs
@@ -1,9 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-using Microsoft.Build.Construction;
-using Microsoft.Build.Evaluation;
-using Microsoft.Build.Execution;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Sln.Internal;
using Microsoft.DotNet.Cli.Utils;
@@ -49,7 +46,7 @@ namespace Microsoft.DotNet.Tools.Sln.Add
int preAddProjectCount = slnFile.Projects.Count;
foreach (var fullProjectPath in fullProjectPaths)
{
- AddProject(slnFile, fullProjectPath);
+ slnFile.AddProject(fullProjectPath);
}
if (slnFile.Projects.Count > preAddProjectCount)
@@ -59,165 +56,5 @@ namespace Microsoft.DotNet.Tools.Sln.Add
return 0;
}
-
- private void AddProject(SlnFile slnFile, string fullProjectPath)
- {
- var relativeProjectPath = PathUtility.GetRelativePath(
- PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory),
- fullProjectPath);
-
- if (slnFile.Projects.Any((p) =>
- string.Equals(p.FilePath, relativeProjectPath, StringComparison.OrdinalIgnoreCase)))
- {
- Reporter.Output.WriteLine(string.Format(
- CommonLocalizableStrings.SolutionAlreadyContainsProject,
- slnFile.FullPath,
- relativeProjectPath));
- }
- else
- {
- var projectInstance = new ProjectInstance(fullProjectPath);
-
- var slnProject = new SlnProject
- {
- Id = projectInstance.GetProjectId(),
- TypeGuid = projectInstance.GetProjectTypeGuid(),
- Name = Path.GetFileNameWithoutExtension(relativeProjectPath),
- FilePath = relativeProjectPath
- };
-
- AddDefaultBuildConfigurations(slnFile, slnProject);
-
- AddSolutionFolders(slnFile, slnProject);
-
- slnFile.Projects.Add(slnProject);
-
- Reporter.Output.WriteLine(
- string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, relativeProjectPath));
- }
- }
-
- private void AddDefaultBuildConfigurations(SlnFile slnFile, SlnProject slnProject)
- {
- var defaultConfigurations = new List()
- {
- "Debug|Any CPU",
- "Debug|x64",
- "Debug|x86",
- "Release|Any CPU",
- "Release|x64",
- "Release|x86",
- };
-
- // NOTE: The order you create the sections determines the order they are written to the sln
- // file. In the case of an empty sln file, in order to make sure the solution configurations
- // section comes first we need to add it first. This doesn't affect correctness but does
- // stop VS from re-ordering things later on. Since we are keeping the SlnFile class low-level
- // it shouldn't care about the VS implementation details. That's why we handle this here.
- AddDefaultSolutionConfigurations(defaultConfigurations, slnFile.SolutionConfigurationsSection);
- AddDefaultProjectConfigurations(
- defaultConfigurations,
- slnFile.ProjectConfigurationsSection.GetOrCreatePropertySet(slnProject.Id));
- }
-
- private void AddDefaultSolutionConfigurations(
- List defaultConfigurations,
- SlnPropertySet solutionConfigs)
- {
- foreach (var config in defaultConfigurations)
- {
- if (!solutionConfigs.ContainsKey(config))
- {
- solutionConfigs[config] = config;
- }
- }
- }
-
- private void AddDefaultProjectConfigurations(
- List defaultConfigurations,
- SlnPropertySet projectConfigs)
- {
- foreach (var config in defaultConfigurations)
- {
- var activeCfgKey = $"{config}.ActiveCfg";
- if (!projectConfigs.ContainsKey(activeCfgKey))
- {
- projectConfigs[activeCfgKey] = config;
- }
-
- var build0Key = $"{config}.Build.0";
- if (!projectConfigs.ContainsKey(build0Key))
- {
- projectConfigs[build0Key] = config;
- }
- }
- }
-
- private void AddSolutionFolders(SlnFile slnFile, SlnProject slnProject)
- {
- var solutionFolders = slnProject.GetSolutionFoldersFromProject();
-
- if (solutionFolders.Any())
- {
- var nestedProjectsSection = slnFile.Sections.GetOrCreateSection(
- "NestedProjects",
- SlnSectionType.PreProcess);
-
- var pathToGuidMap = GetSolutionFolderPaths(slnFile, nestedProjectsSection.Properties);
-
- string parentDirGuid = null;
- var solutionFolderHierarchy = string.Empty;
- foreach (var dir in solutionFolders)
- {
- solutionFolderHierarchy = Path.Combine(solutionFolderHierarchy, dir);
- if (pathToGuidMap.ContainsKey(solutionFolderHierarchy))
- {
- 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;
- }
- }
-
- 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 18f3866e5..c147052cd 100644
--- a/src/dotnet/commands/dotnet-sln/remove/Program.cs
+++ b/src/dotnet/commands/dotnet-sln/remove/Program.cs
@@ -48,12 +48,12 @@ namespace Microsoft.DotNet.Tools.Sln.Remove
bool slnChanged = false;
foreach (var path in relativeProjectPaths)
{
- slnChanged |= RemoveProject(slnFile, path);
+ slnChanged |= slnFile.RemoveProject(path);
}
- RemoveEmptyConfigurationSections(slnFile);
+ slnFile.RemoveEmptyConfigurationSections();
- RemoveEmptySolutionFolders(slnFile);
+ slnFile.RemoveEmptySolutionFolders();
if (slnChanged)
{
@@ -62,120 +62,5 @@ namespace Microsoft.DotNet.Tools.Sln.Remove
return 0;
}
-
- private bool RemoveProject(SlnFile slnFile, string projectPath)
- {
- var projectPathNormalized = PathUtility.GetPathWithDirectorySeparator(projectPath);
-
- var projectsToRemove = slnFile.Projects.Where((p) =>
- string.Equals(p.FilePath, projectPathNormalized, StringComparison.OrdinalIgnoreCase)).ToList();
-
- bool projectRemoved = false;
- if (projectsToRemove.Count == 0)
- {
- Reporter.Output.WriteLine(string.Format(
- CommonLocalizableStrings.ProjectReferenceCouldNotBeFound,
- projectPath));
- }
- else
- {
- foreach (var slnProject in projectsToRemove)
- {
- var buildConfigsToRemove = slnFile.ProjectConfigurationsSection.GetPropertySet(slnProject.Id);
- if (buildConfigsToRemove != null)
- {
- slnFile.ProjectConfigurationsSection.Remove(buildConfigsToRemove);
- }
-
- var nestedProjectsSection = slnFile.Sections.GetSection(
- "NestedProjects",
- SlnSectionType.PreProcess);
- if (nestedProjectsSection != null && nestedProjectsSection.Properties.ContainsKey(slnProject.Id))
- {
- nestedProjectsSection.Properties.Remove(slnProject.Id);
- }
-
- slnFile.Projects.Remove(slnProject);
- Reporter.Output.WriteLine(
- string.Format(CommonLocalizableStrings.ProjectReferenceRemoved, slnProject.FilePath));
- }
-
- projectRemoved = true;
- }
-
- return projectRemoved;
- }
-
- private void RemoveEmptyConfigurationSections(SlnFile slnFile)
- {
- if (slnFile.Projects.Count == 0)
- {
- var solutionConfigs = slnFile.Sections.GetSection("SolutionConfigurationPlatforms");
- if (solutionConfigs != null)
- {
- slnFile.Sections.Remove(solutionConfigs);
- }
-
- var projectConfigs = slnFile.Sections.GetSection("ProjectConfigurationPlatforms");
- if (projectConfigs != null)
- {
- slnFile.Sections.Remove(projectConfigs);
- }
- }
- }
-
- private void RemoveEmptySolutionFolders(SlnFile slnFile)
- {
- var solutionFolderProjects = slnFile.Projects
- .GetProjectsByType(ProjectTypeGuids.SolutionFolderGuid)
- .ToList();
-
- if (solutionFolderProjects.Any())
- {
- var nestedProjectsSection = slnFile.Sections.GetSection(
- "NestedProjects",
- SlnSectionType.PreProcess);
-
- var solutionFoldersInUse = GetSolutionFoldersThatContainProjectsInItsHierarchy(
- slnFile,
- nestedProjectsSection.Properties);
-
- foreach (var solutionFolderProject in solutionFolderProjects)
- {
- if (!solutionFoldersInUse.Contains(solutionFolderProject.Id))
- {
- slnFile.Projects.Remove(solutionFolderProject);
- nestedProjectsSection.Properties.Remove(solutionFolderProject.Id);
- }
- }
-
- if (nestedProjectsSection.IsEmpty)
- {
- slnFile.Sections.Remove(nestedProjectsSection);
- }
- }
- }
-
- 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-migrate.Tests/GivenThatIWantToMigrateSolutions.cs b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateSolutions.cs
index 095518dae..cbfa8ba50 100644
--- a/test/dotnet-migrate.Tests/GivenThatIWantToMigrateSolutions.cs
+++ b/test/dotnet-migrate.Tests/GivenThatIWantToMigrateSolutions.cs
@@ -149,6 +149,41 @@ namespace Microsoft.DotNet.Migration.Tests
cmd.StdErr.Should().BeEmpty();
}
+ [Theory]
+ [InlineData("NoSolutionItemsAfterMigration.sln", false)]
+ [InlineData("ReadmeSolutionItemAfterMigration.sln", true)]
+ public void WhenMigratingAnSlnLinksReferencingItemsMovedToBackupAreRemoved(
+ string slnFileName,
+ bool solutionItemsContainsReadme)
+ {
+ var projectDirectory = TestAssets
+ .GetProjectJson(TestAssetKinds.NonRestoredTestProjects, "PJAppWithSlnAndSolutionItemsToMoveToBackup")
+ .CreateInstance(Path.GetFileNameWithoutExtension(slnFileName))
+ .WithSourceFiles()
+ .Root
+ .FullName;
+
+ new DotnetCommand()
+ .WithWorkingDirectory(projectDirectory)
+ .Execute($"migrate \"{slnFileName}\"")
+ .Should().Pass();
+
+ var slnFile = SlnFile.Read(Path.Combine(projectDirectory, slnFileName));
+ var solutionFolders = slnFile.Projects.Where(p => p.TypeGuid == ProjectTypeGuids.SolutionFolderGuid);
+ if (solutionItemsContainsReadme)
+ {
+ solutionFolders.Count().Should().Be(1);
+ var solutionItems = solutionFolders.Single().Sections.GetSection("SolutionItems");
+ solutionItems.Should().NotBeNull();
+ solutionItems.Properties.Count().Should().Be(1);
+ solutionItems.Properties["readme.txt"].Should().Be("readme.txt");
+ }
+ else
+ {
+ solutionFolders.Count().Should().Be(0);
+ }
+ }
+
private void MigrateAndBuild(string groupName, string projectName, [CallerMemberName] string callingMethod = "", string identifier = "")
{
var projectDirectory = TestAssets