diff --git a/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/App.sln b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/App.sln new file mode 100644 index 000000000..acf657a65 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/App.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26006.2 +MinimumVisualStudioVersion = 10.0.40219.1 +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 + Foo Bar|Any CPU = Foo Bar|Any CPU + Foo Bar|x64 = Foo Bar|x64 + Foo Bar|x86 = Foo Bar|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithAdditionalConfigs/Library.cs b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithAdditionalConfigs/Library.cs new file mode 100644 index 000000000..786c0221c --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithAdditionalConfigs/Library.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 ProjectWithAdditionalConfigs +{ + public static class Library + { + public static string GetMessage() + { + return "Hello World!"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithAdditionalConfigs/ProjectWithAdditionalConfigs.csproj b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithAdditionalConfigs/ProjectWithAdditionalConfigs.csproj new file mode 100644 index 000000000..9b87758e9 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithAdditionalConfigs/ProjectWithAdditionalConfigs.csproj @@ -0,0 +1,10 @@ + + + + netstandard2.0 + {A302325B-D680-4C0E-8680-7AE283981624} + AnyCPU;x64;x86;AdditionalPlatform + Debug;Release;FooBar;AdditionalConfiguration + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithMatchingConfigs/Library.cs b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithMatchingConfigs/Library.cs new file mode 100644 index 000000000..c0d7436b3 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithMatchingConfigs/Library.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 ProjectWithMatchingConfigs +{ + public static class Library + { + public static string GetMessage() + { + return "Hello World!"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithMatchingConfigs/ProjectWithMatchingConfigs.csproj b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithMatchingConfigs/ProjectWithMatchingConfigs.csproj new file mode 100644 index 000000000..7c789108f --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithMatchingConfigs/ProjectWithMatchingConfigs.csproj @@ -0,0 +1,10 @@ + + + + netstandard2.0 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D} + AnyCPU;x64;x86 + Debug;Release;FooBar + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithoutMatchingConfigs/Library.cs b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithoutMatchingConfigs/Library.cs new file mode 100644 index 000000000..fb9a17c27 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithoutMatchingConfigs/Library.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 ProjectWithoutMatchingConfigs +{ + public static class Library + { + public static string GetMessage() + { + return "Hello World!"; + } + } +} diff --git a/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithoutMatchingConfigs/ProjectWithoutMatchingConfigs.csproj b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithoutMatchingConfigs/ProjectWithoutMatchingConfigs.csproj new file mode 100644 index 000000000..4bc811fa1 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndProjectConfigs/ProjectWithoutMatchingConfigs/ProjectWithoutMatchingConfigs.csproj @@ -0,0 +1,8 @@ + + + + netstandard2.0 + {C49B64DE-4401-4825-8A88-10DCB5950E57} + + + diff --git a/src/dotnet/ProjectInstanceExtensions.cs b/src/dotnet/ProjectInstanceExtensions.cs index f437572c5..fdfd41b38 100644 --- a/src/dotnet/ProjectInstanceExtensions.cs +++ b/src/dotnet/ProjectInstanceExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.Build.Execution; using Microsoft.DotNet.Cli.Sln.Internal; using System; +using System.Collections.Generic; using System.Linq; namespace Microsoft.DotNet.Tools.Common @@ -45,5 +46,25 @@ namespace Microsoft.DotNet.Tools.Common return projectTypeGuid; } + + public static IEnumerable GetPlatforms(this ProjectInstance projectInstance) + { + return (projectInstance.GetPropertyValue("Platforms") ?? "") + .Split( + new char[] { ';' }, + StringSplitOptions.RemoveEmptyEntries) + .Where(p => !string.IsNullOrWhiteSpace(p)) + .DefaultIfEmpty("AnyCPU"); + } + + public static IEnumerable GetConfigurations(this ProjectInstance projectInstance) + { + return (projectInstance.GetPropertyValue("Configurations") ?? "Debug;Release") + .Split( + new char[] { ';' }, + StringSplitOptions.RemoveEmptyEntries) + .Where(c => !string.IsNullOrWhiteSpace(c)) + .DefaultIfEmpty("Debug"); + } } } diff --git a/src/dotnet/SlnFileExtensions.cs b/src/dotnet/SlnFileExtensions.cs index 84ce82b0d..e1d91c224 100644 --- a/src/dotnet/SlnFileExtensions.cs +++ b/src/dotnet/SlnFileExtensions.cs @@ -59,7 +59,16 @@ namespace Microsoft.DotNet.Tools.Common FilePath = relativeProjectPath }; - slnFile.AddDefaultBuildConfigurations(slnProject); + // 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. + slnFile.AddDefaultBuildConfigurations(); + + slnFile.MapSolutionConfigurationsToProject( + projectInstance, + slnFile.ProjectConfigurationsSection.GetOrCreatePropertySet(slnProject.Id)); slnFile.AddSolutionFolders(slnProject); @@ -70,11 +79,13 @@ namespace Microsoft.DotNet.Tools.Common } } - public static void AddDefaultBuildConfigurations(this SlnFile slnFile, SlnProject slnProject) + private static void AddDefaultBuildConfigurations(this SlnFile slnFile) { - if (slnProject == null) + var configurationsSection = slnFile.SolutionConfigurationsSection; + + if (!configurationsSection.IsEmpty) { - throw new ArgumentException(); + return; } var defaultConfigurations = new List() @@ -87,57 +98,108 @@ namespace Microsoft.DotNet.Tools.Common "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)) + configurationsSection[config] = config; + } + } + + private static void MapSolutionConfigurationsToProject( + this SlnFile slnFile, + ProjectInstance projectInstance, + SlnPropertySet solutionProjectConfigs) + { + var (projectConfigurations, defaultProjectConfiguration) = GetKeysDictionary(projectInstance.GetConfigurations()); + var (projectPlatforms, defaultProjectPlatform) = GetKeysDictionary(projectInstance.GetPlatforms()); + + foreach (var solutionConfigKey in slnFile.SolutionConfigurationsSection.Keys) + { + var projectConfigKey = MapSolutionConfigKeyToProjectConfigKey( + solutionConfigKey, + projectConfigurations, + defaultProjectConfiguration, + projectPlatforms, + defaultProjectPlatform); + if (projectConfigKey == null) { - solutionConfigs[config] = config; + continue; + } + + var activeConfigKey = $"{solutionConfigKey}.ActiveCfg"; + if (!solutionProjectConfigs.ContainsKey(activeConfigKey)) + { + solutionProjectConfigs[activeConfigKey] = projectConfigKey; + } + + var buildKey = $"{solutionConfigKey}.Build.0"; + if (!solutionProjectConfigs.ContainsKey(buildKey)) + { + solutionProjectConfigs[buildKey] = projectConfigKey; } } } - private static void AddDefaultProjectConfigurations( - List defaultConfigurations, - SlnPropertySet projectConfigs) + private static (Dictionary Keys, string DefaultKey) GetKeysDictionary(IEnumerable keys) { - foreach (var config in defaultConfigurations) - { - var activeCfgKey = $"{config}.ActiveCfg"; - if (!projectConfigs.ContainsKey(activeCfgKey)) - { - projectConfigs[activeCfgKey] = config; - } + // A dictionary mapping key -> key is used instead of a HashSet so the original case of the key can be retrieved from the set + var dictionary = new Dictionary(StringComparer.CurrentCultureIgnoreCase); - var build0Key = $"{config}.Build.0"; - if (!projectConfigs.ContainsKey(build0Key)) - { - projectConfigs[build0Key] = config; - } + foreach (var key in keys) + { + dictionary[key] = key; } + + return (dictionary, keys.FirstOrDefault()); } - public static void AddSolutionFolders(this SlnFile slnFile, SlnProject slnProject) + private static string GetMatchingProjectKey(IDictionary projectKeys, string solutionKey) { - if (slnProject == null) + string projectKey; + if (projectKeys.TryGetValue(solutionKey, out projectKey)) { - throw new ArgumentException(); + return projectKey; } + var keyWithoutWhitespace = String.Concat(solutionKey.Where(c => !Char.IsWhiteSpace(c))); + if (projectKeys.TryGetValue(keyWithoutWhitespace, out projectKey)) + { + return projectKey; + } + + return null; + } + + private static string MapSolutionConfigKeyToProjectConfigKey( + string solutionConfigKey, + Dictionary projectConfigurations, + string defaultProjectConfiguration, + Dictionary projectPlatforms, + string defaultProjectPlatform) + { + var pair = solutionConfigKey.Split(new char[] {'|'}, 2); + if (pair.Length != 2) + { + return null; + } + + var projectConfiguration = GetMatchingProjectKey(projectConfigurations, pair[0]) ?? defaultProjectConfiguration; + if (projectConfiguration == null) + { + return null; + } + + var projectPlatform = GetMatchingProjectKey(projectPlatforms, pair[1]) ?? defaultProjectPlatform; + if (projectPlatform == null) + { + return null; + } + + // VS stores "Any CPU" platform in the solution regardless of how it is named at the project level + return $"{projectConfiguration}|{(projectPlatform == "AnyCPU" ? "Any CPU" : projectPlatform)}"; + } + + private static void AddSolutionFolders(this SlnFile slnFile, SlnProject slnProject) + { var solutionFolders = slnProject.GetSolutionFoldersFromProject(); if (solutionFolders.Any()) diff --git a/test/dotnet-sln-add.Tests/GivenDotnetSlnAdd.cs b/test/dotnet-sln-add.Tests/GivenDotnetSlnAdd.cs index 346fd6638..bb50d5f5d 100644 --- a/test/dotnet-sln-add.Tests/GivenDotnetSlnAdd.cs +++ b/test/dotnet-sln-add.Tests/GivenDotnetSlnAdd.cs @@ -81,16 +81,16 @@ Global {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|x64 - __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|x64 - __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|x86 - __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|x86 + __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|x64 - __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|x64 - __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|x86 - __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|x86 + __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -117,16 +117,16 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|x64 - __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|x64 - __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|x86 - __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|x86 + __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|x64 - __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|x64 - __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|x86 - __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|x86 + __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal "; @@ -166,16 +166,16 @@ Global {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 __LIB_PROJECT_GUID__.Debug|Any CPU.ActiveCfg = Debug|Any CPU __LIB_PROJECT_GUID__.Debug|Any CPU.Build.0 = Debug|Any CPU - __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|x64 - __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|x64 - __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|x86 - __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|x86 + __LIB_PROJECT_GUID__.Debug|x64.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x64.Build.0 = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.ActiveCfg = Debug|Any CPU + __LIB_PROJECT_GUID__.Debug|x86.Build.0 = Debug|Any CPU __LIB_PROJECT_GUID__.Release|Any CPU.ActiveCfg = Release|Any CPU __LIB_PROJECT_GUID__.Release|Any CPU.Build.0 = Release|Any CPU - __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|x64 - __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|x64 - __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|x86 - __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|x86 + __LIB_PROJECT_GUID__.Release|x64.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x64.Build.0 = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.ActiveCfg = Release|Any CPU + __LIB_PROJECT_GUID__.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -184,6 +184,141 @@ Global __LIB_PROJECT_GUID__ = __SRC_FOLDER_GUID__ EndGlobalSection EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingProjectWithoutMatchingConfigs = @" +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}"") = ""ProjectWithoutMatchingConfigs"", ""ProjectWithoutMatchingConfigs\ProjectWithoutMatchingConfigs.csproj"", ""{C49B64DE-4401-4825-8A88-10DCB5950E57}"" +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 + Foo Bar|Any CPU = Foo Bar|Any CPU + Foo Bar|x64 = Foo Bar|x64 + Foo Bar|x86 = Foo Bar|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x64.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x64.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x86.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Debug|x86.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|Any CPU.Build.0 = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x64.ActiveCfg = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x64.Build.0 = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x86.ActiveCfg = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Release|x86.Build.0 = Release|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|Any CPU.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|Any CPU.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x64.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x64.Build.0 = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x86.ActiveCfg = Debug|Any CPU + {C49B64DE-4401-4825-8A88-10DCB5950E57}.Foo Bar|x86.Build.0 = Debug|Any CPU + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingProjectWithMatchingConfigs = @" +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}"") = ""ProjectWithMatchingConfigs"", ""ProjectWithMatchingConfigs\ProjectWithMatchingConfigs.csproj"", ""{C9601CA2-DB64-4FB6-B463-368C7764BF0D}"" +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 + Foo Bar|Any CPU = Foo Bar|Any CPU + Foo Bar|x64 = Foo Bar|x64 + Foo Bar|x86 = Foo Bar|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x64.ActiveCfg = Debug|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x64.Build.0 = Debug|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x86.ActiveCfg = Debug|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Debug|x86.Build.0 = Debug|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|Any CPU.Build.0 = Release|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x64.ActiveCfg = Release|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x64.Build.0 = Release|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x86.ActiveCfg = Release|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Release|x86.Build.0 = Release|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|Any CPU.ActiveCfg = FooBar|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|Any CPU.Build.0 = FooBar|Any CPU + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x64.ActiveCfg = FooBar|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x64.Build.0 = FooBar|x64 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x86.ActiveCfg = FooBar|x86 + {C9601CA2-DB64-4FB6-B463-368C7764BF0D}.Foo Bar|x86.Build.0 = FooBar|x86 + EndGlobalSection +EndGlobal +"; + + private const string ExpectedSlnFileAfterAddingProjectWithAdditionalConfigs = @" +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}"") = ""ProjectWithAdditionalConfigs"", ""ProjectWithAdditionalConfigs\ProjectWithAdditionalConfigs.csproj"", ""{A302325B-D680-4C0E-8680-7AE283981624}"" +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 + Foo Bar|Any CPU = Foo Bar|Any CPU + Foo Bar|x64 = Foo Bar|x64 + Foo Bar|x86 = Foo Bar|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x64.ActiveCfg = Debug|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x64.Build.0 = Debug|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x86.ActiveCfg = Debug|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Debug|x86.Build.0 = Debug|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Release|Any CPU.Build.0 = Release|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Release|x64.ActiveCfg = Release|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Release|x64.Build.0 = Release|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Release|x86.ActiveCfg = Release|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Release|x86.Build.0 = Release|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|Any CPU.ActiveCfg = FooBar|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|Any CPU.Build.0 = FooBar|Any CPU + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x64.ActiveCfg = FooBar|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x64.Build.0 = FooBar|x64 + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x86.ActiveCfg = FooBar|x86 + {A302325B-D680-4C0E-8680-7AE283981624}.Foo Bar|x86.Build.0 = FooBar|x86 + EndGlobalSection +EndGlobal "; [Theory] @@ -788,6 +923,69 @@ EndGlobal solutionFolderProjects.Count().Should().Be(1); } + [Fact] + public void WhenProjectWithoutMatchingConfigurationsIsAddedSolutionMapsToFirstAvailable() + { + var slnDirectory = TestAssets + .Get("TestAppWithSlnAndProjectConfigs") + .CreateInstance() + .WithSourceFiles() + .Root + .FullName; + + var slnFullPath = Path.Combine(slnDirectory, "App.sln"); + + var result = new DotnetCommand() + .WithWorkingDirectory(slnDirectory) + .ExecuteWithCapturedOutput($"sln add ProjectWithoutMatchingConfigs"); + result.Should().Pass(); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(ExpectedSlnFileAfterAddingProjectWithoutMatchingConfigs); + } + + [Fact] + public void WhenProjectWithMatchingConfigurationsIsAddedSolutionMapsAll() + { + var slnDirectory = TestAssets + .Get("TestAppWithSlnAndProjectConfigs") + .CreateInstance() + .WithSourceFiles() + .Root + .FullName; + + var slnFullPath = Path.Combine(slnDirectory, "App.sln"); + + var result = new DotnetCommand() + .WithWorkingDirectory(slnDirectory) + .ExecuteWithCapturedOutput($"sln add ProjectWithMatchingConfigs"); + result.Should().Pass(); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(ExpectedSlnFileAfterAddingProjectWithMatchingConfigs); + } + + [Fact] + public void WhenProjectWithAdditionalConfigurationsIsAddedSolutionDoesNotMapThem() + { + var slnDirectory = TestAssets + .Get("TestAppWithSlnAndProjectConfigs") + .CreateInstance() + .WithSourceFiles() + .Root + .FullName; + + var slnFullPath = Path.Combine(slnDirectory, "App.sln"); + + var result = new DotnetCommand() + .WithWorkingDirectory(slnDirectory) + .ExecuteWithCapturedOutput($"sln add ProjectWithAdditionalConfigs"); + result.Should().Pass(); + + File.ReadAllText(slnFullPath) + .Should().BeVisuallyEquivalentTo(ExpectedSlnFileAfterAddingProjectWithAdditionalConfigs); + } + private string GetExpectedSlnContents( string slnPath, string slnTemplate,