Merge branch 'rel/1.0.0' into patch-1

This commit is contained in:
Piotr Puszkiewicz 2017-01-24 16:49:25 -08:00 committed by GitHub
commit 38fa2548f6
14 changed files with 535 additions and 284 deletions

View file

@ -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

View file

@ -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

View file

@ -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;
}
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.23107" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.23107</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>0138cb8f-4aa9-4029-a21e-c07c30f425ba</ProjectGuid>
<RootNamespace>TestAppWithContents</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -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": {}
}
}

View file

@ -0,0 +1,3 @@
{
"projects": [ "." ]
}

View file

@ -0,0 +1 @@
This is just for our test to verify that we do not remove the readme.txt link from the solution.

View file

@ -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<string>()
{
"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<string> defaultConfigurations,
SlnPropertySet solutionConfigs)
{
foreach (var config in defaultConfigurations)
{
if (!solutionConfigs.ContainsKey(config))
{
solutionConfigs[config] = config;
}
}
}
private static void AddDefaultProjectConfigurations(
List<string> 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<string, string> GetSolutionFolderPaths(
this SlnFile slnFile,
SlnPropertySet nestedProjects)
{
var solutionFolderPaths = new Dictionary<string, string>();
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<string> GetSolutionFoldersThatContainProjectsInItsHierarchy(
this SlnFile slnFile,
SlnPropertySet nestedProjects)
{
var solutionFoldersInUse = new HashSet<string>();
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;
}
}
}

View file

@ -8,7 +8,7 @@ using System.Linq;
namespace Microsoft.DotNet.Tools.Common namespace Microsoft.DotNet.Tools.Common
{ {
public static class SlnProjectCollectionExtensions internal static class SlnProjectCollectionExtensions
{ {
public static IEnumerable<SlnProject> GetProjectsByType( public static IEnumerable<SlnProject> GetProjectsByType(
this SlnProjectCollection projects, this SlnProjectCollection projects,

View file

@ -8,7 +8,7 @@ using System.Linq;
namespace Microsoft.DotNet.Tools.Common namespace Microsoft.DotNet.Tools.Common
{ {
public static class SlnProjectExtensions internal static class SlnProjectExtensions
{ {
public static IList<string> GetSolutionFoldersFromProject(this SlnProject project) public static IList<string> GetSolutionFoldersFromProject(this SlnProject project)
{ {

View file

@ -162,6 +162,8 @@ namespace Microsoft.DotNet.Tools.Migrate
_slnFile.MinimumVisualStudioVersion = MinimumVisualStudioVersion; _slnFile.MinimumVisualStudioVersion = MinimumVisualStudioVersion;
} }
RemoveReferencesToMigratedFiles(_slnFile);
_slnFile.Write(); _slnFile.Write();
foreach (var csprojFile in csprojFilesToAdd) 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) private void AddProject(string slnPath, string csprojPath)
{ {
List<string> args = new List<string>() List<string> args = new List<string>()

View file

@ -1,9 +1,6 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. // 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. // 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;
using Microsoft.DotNet.Cli.Sln.Internal; using Microsoft.DotNet.Cli.Sln.Internal;
using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Cli.Utils;
@ -49,7 +46,7 @@ namespace Microsoft.DotNet.Tools.Sln.Add
int preAddProjectCount = slnFile.Projects.Count; int preAddProjectCount = slnFile.Projects.Count;
foreach (var fullProjectPath in fullProjectPaths) foreach (var fullProjectPath in fullProjectPaths)
{ {
AddProject(slnFile, fullProjectPath); slnFile.AddProject(fullProjectPath);
} }
if (slnFile.Projects.Count > preAddProjectCount) if (slnFile.Projects.Count > preAddProjectCount)
@ -59,165 +56,5 @@ namespace Microsoft.DotNet.Tools.Sln.Add
return 0; 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<string>()
{
"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<string> defaultConfigurations,
SlnPropertySet solutionConfigs)
{
foreach (var config in defaultConfigurations)
{
if (!solutionConfigs.ContainsKey(config))
{
solutionConfigs[config] = config;
}
}
}
private void AddDefaultProjectConfigurations(
List<string> 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<string, string> GetSolutionFolderPaths(SlnFile slnFile, SlnPropertySet nestedProjects)
{
var solutionFolderPaths = new Dictionary<string, string>();
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;
}
} }
} }

View file

@ -48,12 +48,12 @@ namespace Microsoft.DotNet.Tools.Sln.Remove
bool slnChanged = false; bool slnChanged = false;
foreach (var path in relativeProjectPaths) foreach (var path in relativeProjectPaths)
{ {
slnChanged |= RemoveProject(slnFile, path); slnChanged |= slnFile.RemoveProject(path);
} }
RemoveEmptyConfigurationSections(slnFile); slnFile.RemoveEmptyConfigurationSections();
RemoveEmptySolutionFolders(slnFile); slnFile.RemoveEmptySolutionFolders();
if (slnChanged) if (slnChanged)
{ {
@ -62,120 +62,5 @@ namespace Microsoft.DotNet.Tools.Sln.Remove
return 0; 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<string> GetSolutionFoldersThatContainProjectsInItsHierarchy(
SlnFile slnFile,
SlnPropertySet nestedProjects)
{
var solutionFoldersInUse = new HashSet<string>();
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;
}
} }
} }

View file

@ -149,6 +149,41 @@ namespace Microsoft.DotNet.Migration.Tests
cmd.StdErr.Should().BeEmpty(); 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 = "") private void MigrateAndBuild(string groupName, string projectName, [CallerMemberName] string callingMethod = "", string identifier = "")
{ {
var projectDirectory = TestAssets var projectDirectory = TestAssets