From 441277ccfa5f894f0b49b13ff6bf9d888d441fc2 Mon Sep 17 00:00:00 2001 From: Justin Goshi Date: Wed, 14 Dec 2016 13:53:11 -1000 Subject: [PATCH] Implement dotnet add project (#5022) * Implement dotnet add project * Addressed PR comments --- .../InvalidSolution/InvalidSolution.sln | 1 + .../InvalidSolution/Lib/Lib.csproj | 15 ++ .../InvalidSolution/Lib/Library.cs | 12 ++ .../TestAppWithMultipleSlnFiles/App.sln | 34 +++ .../App/App.csproj | 15 ++ .../App/Program.cs | 9 + .../TestAppWithMultipleSlnFiles/App2.sln | 34 +++ .../TestAppWithSlnAndCsprojFiles/App.sln | 34 +++ .../App/App.csproj | 19 ++ .../App/Program.cs | 10 + .../Lib/Lib.csproj | 15 ++ .../Lib/Library.cs | 12 ++ .../App.sln | 34 +++ .../App/App.csproj | 19 ++ .../App/Program.cs | 10 + .../Lib/Lib.csproj | 16 ++ .../Lib/Library.cs | 12 ++ .../App.sln | 36 ++++ .../App/App.csproj | 19 ++ .../App/Program.cs | 10 + .../Lib/Lib.csproj | 15 ++ .../Lib/Library.cs | 12 ++ .../ProjectTypeGuids.cs | 11 + src/Microsoft.DotNet.Cli.Utils/PathUtility.cs | 23 ++ src/dotnet/CommonLocalizableStrings.cs | 17 +- src/dotnet/MsbuildProject.cs | 36 +--- src/dotnet/MsbuildProjectExtensions.cs | 3 +- src/dotnet/SlnFileFactory.cs | 86 ++++++++ src/dotnet/commands/dotnet-add/Program.cs | 2 + .../dotnet-add/dotnet-add-p2p/Program.cs | 7 +- .../dotnet-add-proj/LocalizableStrings.cs | 14 ++ .../dotnet-add/dotnet-add-proj/Program.cs | 131 +++++++++++ .../commands/dotnet-migrate/MigrateCommand.cs | 4 +- ...Microsoft.DotNet.Cli.Sln.Internal.Tests.cs | 4 +- .../GivenDotnetAddProj.cs | 203 ++++++++++++++++++ test/dotnet-add-proj.Tests/MSBuild.exe | 1 + test/dotnet-add-proj.Tests/MSBuild.exe.config | 1 + .../dotnet-add-proj.Tests.csproj | 51 +++++ 38 files changed, 932 insertions(+), 55 deletions(-) create mode 100644 TestAssets/TestProjects/InvalidSolution/InvalidSolution.sln create mode 100644 TestAssets/TestProjects/InvalidSolution/Lib/Lib.csproj create mode 100644 TestAssets/TestProjects/InvalidSolution/Lib/Library.cs create mode 100644 TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App.sln create mode 100644 TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App/App.csproj create mode 100644 TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App/Program.cs create mode 100644 TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App2.sln create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App.sln create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App/App.csproj create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App/Program.cs create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/Lib/Lib.csproj create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/Lib/Library.cs create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App.sln create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App/App.csproj create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App/Program.cs create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/Lib/Lib.csproj create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/Lib/Library.cs create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App.sln create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App/App.csproj create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App/Program.cs create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/Lib/Lib.csproj create mode 100644 TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/Lib/Library.cs create mode 100644 src/Microsoft.DotNet.Cli.Sln.Internal/ProjectTypeGuids.cs create mode 100644 src/dotnet/SlnFileFactory.cs create mode 100644 src/dotnet/commands/dotnet-add/dotnet-add-proj/LocalizableStrings.cs create mode 100644 src/dotnet/commands/dotnet-add/dotnet-add-proj/Program.cs create mode 100644 test/dotnet-add-proj.Tests/GivenDotnetAddProj.cs create mode 100644 test/dotnet-add-proj.Tests/MSBuild.exe create mode 100644 test/dotnet-add-proj.Tests/MSBuild.exe.config create mode 100644 test/dotnet-add-proj.Tests/dotnet-add-proj.Tests.csproj diff --git a/TestAssets/TestProjects/InvalidSolution/InvalidSolution.sln b/TestAssets/TestProjects/InvalidSolution/InvalidSolution.sln new file mode 100644 index 000000000..6527f5d32 --- /dev/null +++ b/TestAssets/TestProjects/InvalidSolution/InvalidSolution.sln @@ -0,0 +1 @@ +This is a test of an invalid solution. diff --git a/TestAssets/TestProjects/InvalidSolution/Lib/Lib.csproj b/TestAssets/TestProjects/InvalidSolution/Lib/Lib.csproj new file mode 100644 index 000000000..f47b49f65 --- /dev/null +++ b/TestAssets/TestProjects/InvalidSolution/Lib/Lib.csproj @@ -0,0 +1,15 @@ + + + + netstandard1.4 + + + + + + + + + + + diff --git a/TestAssets/TestProjects/InvalidSolution/Lib/Library.cs b/TestAssets/TestProjects/InvalidSolution/Lib/Library.cs new file mode 100644 index 000000000..205c42a01 --- /dev/null +++ b/TestAssets/TestProjects/InvalidSolution/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/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App.sln b/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App.sln new file mode 100644 index 000000000..1a6639b73 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +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 +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 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App/App.csproj b/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App/App.csproj new file mode 100644 index 000000000..c1313e358 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App/App.csproj @@ -0,0 +1,15 @@ + + + Exe + netcoreapp1.0 + + + + + + + + + + + diff --git a/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App/Program.cs b/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App/Program.cs new file mode 100644 index 000000000..f565ae24f --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App/Program.cs @@ -0,0 +1,9 @@ +using System; + +class Program +{ + static void Main(string[] args) + { + Console.WriteLine("Hello"); + } +} diff --git a/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App2.sln b/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App2.sln new file mode 100644 index 000000000..1a6639b73 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithMultipleSlnFiles/App2.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +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 +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 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App.sln b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App.sln new file mode 100644 index 000000000..1a6639b73 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +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 +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 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App/App.csproj b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App/App.csproj new file mode 100644 index 000000000..423ceb827 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App/App.csproj @@ -0,0 +1,19 @@ + + + Exe + netcoreapp1.0 + + + + + + + + + + + + + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App/Program.cs b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/App/Program.cs new file mode 100644 index 000000000..abb853a4a --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/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/TestAppWithSlnAndCsprojFiles/Lib/Lib.csproj b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/Lib/Lib.csproj new file mode 100644 index 000000000..f47b49f65 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/Lib/Lib.csproj @@ -0,0 +1,15 @@ + + + + netstandard1.4 + + + + + + + + + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/Lib/Library.cs b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/Lib/Library.cs new file mode 100644 index 000000000..205c42a01 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojFiles/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/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App.sln b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App.sln new file mode 100644 index 000000000..1a6639b73 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +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 +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 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App/App.csproj b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App/App.csproj new file mode 100644 index 000000000..423ceb827 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App/App.csproj @@ -0,0 +1,19 @@ + + + Exe + netcoreapp1.0 + + + + + + + + + + + + + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App/Program.cs b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/App/Program.cs new file mode 100644 index 000000000..abb853a4a --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/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/TestAppWithSlnAndCsprojProjectGuidFiles/Lib/Lib.csproj b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/Lib/Lib.csproj new file mode 100644 index 000000000..aacaac752 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/Lib/Lib.csproj @@ -0,0 +1,16 @@ + + + + netstandard1.4 + {84A45D44-B677-492D-A6DA-B3A71135AB8E} + + + + + + + + + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/Lib/Library.cs b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/Lib/Library.cs new file mode 100644 index 000000000..205c42a01 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndCsprojProjectGuidFiles/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/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App.sln b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App.sln new file mode 100644 index 000000000..dab4a7da7 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App.sln @@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib", "Lib\Lib.csproj", "{B38B1FA5-B4C9-456A-8B71-8FCD62ACF400}" +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 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.ActiveCfg = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x64.Build.0 = Debug|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.ActiveCfg = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Debug|x86.Build.0 = Debug|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|Any CPU.Build.0 = Release|Any CPU + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.ActiveCfg = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x64.Build.0 = Release|x64 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.ActiveCfg = Release|x86 + {7072A694-548F-4CAE-A58F-12D257D5F486}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App/App.csproj b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App/App.csproj new file mode 100644 index 000000000..423ceb827 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App/App.csproj @@ -0,0 +1,19 @@ + + + Exe + netcoreapp1.0 + + + + + + + + + + + + + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App/Program.cs b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/App/Program.cs new file mode 100644 index 000000000..abb853a4a --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/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/TestAppWithSlnAndExistingCsprojReferences/Lib/Lib.csproj b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/Lib/Lib.csproj new file mode 100644 index 000000000..f47b49f65 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/Lib/Lib.csproj @@ -0,0 +1,15 @@ + + + + netstandard1.4 + + + + + + + + + + + diff --git a/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/Lib/Library.cs b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/Lib/Library.cs new file mode 100644 index 000000000..205c42a01 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithSlnAndExistingCsprojReferences/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/Microsoft.DotNet.Cli.Sln.Internal/ProjectTypeGuids.cs b/src/Microsoft.DotNet.Cli.Sln.Internal/ProjectTypeGuids.cs new file mode 100644 index 000000000..71d7798df --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Sln.Internal/ProjectTypeGuids.cs @@ -0,0 +1,11 @@ +// 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. + +namespace Microsoft.DotNet.Cli.Sln.Internal +{ + public static class ProjectTypeGuids + { + public const string CPSProjectTypeGuid = "{13B669BE-BB05-4DDF-9536-439F39A36129}"; + public const string CSharpProjectTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"; + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs b/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs index c84f6b6f0..a74a9f98a 100644 --- a/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs +++ b/src/Microsoft.DotNet.Cli.Utils/PathUtility.cs @@ -2,7 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.PlatformAbstractions; namespace Microsoft.DotNet.Tools.Common @@ -257,5 +260,25 @@ namespace Microsoft.DotNet.Tools.Common return Path.GetFullPath(path); } + + public static void EnsureAllPathsExist(List paths, string pathDoesNotExistLocalizedFormatString) + { + var notExisting = new List(); + foreach (var p in paths) + { + if (!File.Exists(p)) + { + notExisting.Add(p); + } + } + + if (notExisting.Count > 0) + { + throw new GracefulException( + string.Join( + Environment.NewLine, + notExisting.Select((p) => string.Format(pathDoesNotExistLocalizedFormatString, p)))); + } + } } } \ No newline at end of file diff --git a/src/dotnet/CommonLocalizableStrings.cs b/src/dotnet/CommonLocalizableStrings.cs index 3aff0bd1e..b979ad7a5 100644 --- a/src/dotnet/CommonLocalizableStrings.cs +++ b/src/dotnet/CommonLocalizableStrings.cs @@ -100,12 +100,10 @@ /// Solution public const string CouldNotFindSolutionIn = "Specified solution file {0} does not exist, or there is no solution file in the directory."; public const string CouldNotFindSolutionOrDirectory = "Could not find solution or directory `{0}`."; - public const string FoundMoreThanOneSolutionIn = "Found more than one solution file in {0}. Please specify which one to use."; - public const string FoundInvalidSolution = "The solution file {0} seems to be invalid. Please check if it is a valid solution file."; + public const string MoreThanOneSolutionInDirectory = "Found more than one solution file in {0}. Please specify which one to use."; public const string InvalidSolution = "Invalid solution `{0}`."; public const string SolutionDoesNotExist = "Specified solution file {0} does not exist, or there is no solution file in the directory."; - public const string SolutionAlreadyContainsAProject = "Solution {0} already contains project {1}."; - + /// add p2p public const string ReferenceDoesNotExist = "Reference {0} does not exist."; public const string ReferenceIsInvalid = "Reference `{0}` is invalid."; @@ -125,28 +123,25 @@ public const string ProjectIsInvalid = "Project `{0}` is invalid."; public const string SpecifyAtLeastOneProjectToAdd = "You must specify at least one project to add."; public const string ProjectAddedToTheSolution = "Project `{0}` added to the solution."; - public const string SolutionAlreadyHasAProject = "Solution {0} already contains project {1}."; + public const string SolutionAlreadyContainsProject = "Solution {0} already contains project {1}."; /// del p2p public const string ReferenceNotFoundInTheProject = "Specified reference {0} does not exist in project {1}."; public const string ReferenceRemoved = "Reference `{0}` deleted from the project."; - public const string SpecifyAtLeastOneReferenceToRemove = "You must specify at least one reference to delete. Please run dotnet delete --help for more information."; + public const string SpecifyAtLeastOneReferenceToRemove = "You must specify at least one reference to remove."; public const string ReferenceDeleted = "Reference `{0}` deleted."; - public const string SpecifyAtLeastOneReferenceToDelete = "You must specify at least one reference to delete. Please run dotnet delete --help for more information."; - + /// del pkg public const string PackageReferenceNotFoundInTheProject = "Package reference `{0}` could not be found in the project."; public const string PackageReferenceRemoved = "Reference `{0}` deleted from the project."; - public const string SpecifyAtLeastOnePackageReferenceToRemove = "You must specify at least one reference to delete. Please run dotnet delete --help for more information."; + public const string SpecifyAtLeastOnePackageReferenceToRemove = "You must specify at least one package reference to remove."; public const string PackageReferenceDeleted = "Package reference `{0}` deleted."; - public const string SpecifyAtLeastOnePackageReferenceToDelete = "You must specify at least one package reference to delete."; /// del sln public const string ProjectNotFoundInTheSolution = "Project `{0}` could not be found in the solution."; public const string ProjectRemoved = "Project `{0}` removed from solution."; public const string SpecifyAtLeastOneProjectToRemove = "You must specify at least one project to remove."; public const string ProjectDeleted = "Project `{0}` deleted from solution."; - public const string SpecifyAtLeastOneProjectToDelete = "You must specify at least one project to delete from solution."; /// list public const string NoReferencesFound = "There are no {0} references in project {1}. ;; {0} is the type of the item being requested (project, package, p2p) and {1} is the object operated on (a project file or a solution file). "; diff --git a/src/dotnet/MsbuildProject.cs b/src/dotnet/MsbuildProject.cs index f53d084da..8d2ec6879 100644 --- a/src/dotnet/MsbuildProject.cs +++ b/src/dotnet/MsbuildProject.cs @@ -115,7 +115,7 @@ namespace Microsoft.DotNet.Tools ProjectItemGroupElement itemGroup = ProjectRootElement.FindUniformOrCreateItemGroupWithCondition( ProjectItemElementType, framework); - foreach (var @ref in refs.Select((r) => NormalizeSlashes(r))) + foreach (var @ref in refs.Select((r) => PathUtility.GetPathWithBackSlashes(r))) { if (ProjectRootElement.HasExistingItemWithCondition(framework, @ref)) { @@ -151,40 +151,6 @@ namespace Microsoft.DotNet.Tools return ProjectRootElement.GetAllItemsWithElementType(ProjectItemElementType); } - public void ConvertPathsToRelative(ref List references) - { - references = references.Select((r) => - PathUtility.GetRelativePath( - ProjectDirectory, - Path.GetFullPath(r))) - .ToList(); - } - - public static string NormalizeSlashes(string path) - { - return path.Replace('/', '\\'); - } - - public static void EnsureAllReferencesExist(List references) - { - var notExisting = new List(); - foreach (var r in references) - { - if (!File.Exists(r)) - { - notExisting.Add(r); - } - } - - if (notExisting.Count > 0) - { - throw new GracefulException( - string.Join( - Environment.NewLine, - notExisting.Select((r) => string.Format(CommonLocalizableStrings.ReferenceDoesNotExist, r)))); - } - } - public IEnumerable GetTargetFrameworks() { if (_cachedTfms != null) diff --git a/src/dotnet/MsbuildProjectExtensions.cs b/src/dotnet/MsbuildProjectExtensions.cs index e093781ea..06192e864 100644 --- a/src/dotnet/MsbuildProjectExtensions.cs +++ b/src/dotnet/MsbuildProjectExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.Build.Construction; using Microsoft.DotNet.ProjectJsonMigration; +using Microsoft.DotNet.Tools.Common; using System.Collections.Generic; using System.Linq; @@ -100,7 +101,7 @@ namespace Microsoft.DotNet.Tools private static string NormalizeIncludeForComparison(string include) { - return MsbuildProject.NormalizeSlashes(include.ToLower()); + return PathUtility.GetPathWithBackSlashes(include.ToLower()); } } } diff --git a/src/dotnet/SlnFileFactory.cs b/src/dotnet/SlnFileFactory.cs new file mode 100644 index 000000000..0ae26f317 --- /dev/null +++ b/src/dotnet/SlnFileFactory.cs @@ -0,0 +1,86 @@ +// 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; +using System.IO; +using System.Linq; +using Microsoft.DotNet.Cli.Sln.Internal; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Tools.Common +{ + public static class SlnFileFactory + { + public static SlnFile CreateFromFileOrDirectory(string fileOrDirectory) + { + if (File.Exists(fileOrDirectory)) + { + return FromFile(fileOrDirectory); + } + else + { + return FromDirectory(fileOrDirectory); + } + } + + private static SlnFile FromFile(string solutionPath) + { + SlnFile slnFile = null; + try + { + slnFile = SlnFile.Read(solutionPath); + } + catch + { + throw new GracefulException(CommonLocalizableStrings.InvalidSolution, solutionPath); + } + return slnFile; + } + + private static SlnFile FromDirectory(string solutionDirectory) + { + DirectoryInfo dir; + try + { + dir = new DirectoryInfo(solutionDirectory); + if (!dir.Exists) + { + throw new GracefulException( + CommonLocalizableStrings.CouldNotFindSolutionOrDirectory, + solutionDirectory); + } + } + catch (ArgumentException) + { + throw new GracefulException( + CommonLocalizableStrings.CouldNotFindSolutionOrDirectory, + solutionDirectory); + } + + FileInfo[] files = dir.GetFiles("*.sln"); + if (files.Length == 0) + { + throw new GracefulException( + CommonLocalizableStrings.CouldNotFindSolutionIn, + solutionDirectory); + } + + if (files.Length > 1) + { + throw new GracefulException( + CommonLocalizableStrings.MoreThanOneSolutionInDirectory, + solutionDirectory); + } + + FileInfo solutionFile = files.Single(); + if (!solutionFile.Exists) + { + throw new GracefulException( + CommonLocalizableStrings.CouldNotFindSolutionIn, + solutionDirectory); + } + + return FromFile(solutionFile.FullName); + } + } +} diff --git a/src/dotnet/commands/dotnet-add/Program.cs b/src/dotnet/commands/dotnet-add/Program.cs index 4f9b87395..e7fec0003 100644 --- a/src/dotnet/commands/dotnet-add/Program.cs +++ b/src/dotnet/commands/dotnet-add/Program.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Tools.Add.ProjectToProjectReference; +using Microsoft.DotNet.Tools.Add.ProjectToSolution; namespace Microsoft.DotNet.Tools.Add { @@ -16,6 +17,7 @@ namespace Microsoft.DotNet.Tools.Add internal override List> SubCommands => new List> { + AddProjectToSolutionCommand.CreateApplication, AddProjectToProjectReferenceCommand.CreateApplication, }; diff --git a/src/dotnet/commands/dotnet-add/dotnet-add-p2p/Program.cs b/src/dotnet/commands/dotnet-add/dotnet-add-p2p/Program.cs index 9d1451711..cbc20c1f4 100644 --- a/src/dotnet/commands/dotnet-add/dotnet-add-p2p/Program.cs +++ b/src/dotnet/commands/dotnet-add/dotnet-add-p2p/Program.cs @@ -55,7 +55,7 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference string frameworkString = frameworkOption.Value(); List references = app.RemainingArguments; - MsbuildProject.EnsureAllReferencesExist(references); + PathUtility.EnsureAllPathsExist(references, CommonLocalizableStrings.ReferenceDoesNotExist); IEnumerable refs = references.Select((r) => MsbuildProject.FromFile(projects, r)); if (frameworkString == null) @@ -95,11 +95,12 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference } } - msbuildProj.ConvertPathsToRelative(ref references); + var relativePathReferences = references.Select((r) => + PathUtility.GetRelativePath(msbuildProj.ProjectDirectory, Path.GetFullPath(r))).ToList(); int numberOfAddedReferences = msbuildProj.AddProjectToProjectReferences( frameworkOption.Value(), - references); + relativePathReferences); if (numberOfAddedReferences != 0) { diff --git a/src/dotnet/commands/dotnet-add/dotnet-add-proj/LocalizableStrings.cs b/src/dotnet/commands/dotnet-add/dotnet-add-proj/LocalizableStrings.cs new file mode 100644 index 000000000..a3cc25377 --- /dev/null +++ b/src/dotnet/commands/dotnet-add/dotnet-add-proj/LocalizableStrings.cs @@ -0,0 +1,14 @@ +// 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. + +namespace Microsoft.DotNet.Tools.Add.ProjectToSolution +{ + internal class LocalizableStrings + { + public const string AppFullName = ".NET Add Project to Solution Command"; + + public const string AppDescription = "Command to add project to solution"; + + public const string AppHelpText = "Projects to add to solution"; + } +} diff --git a/src/dotnet/commands/dotnet-add/dotnet-add-proj/Program.cs b/src/dotnet/commands/dotnet-add/dotnet-add-proj/Program.cs new file mode 100644 index 000000000..b2baba9fe --- /dev/null +++ b/src/dotnet/commands/dotnet-add/dotnet-add-proj/Program.cs @@ -0,0 +1,131 @@ +// 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.DotNet.Cli.CommandLine; +using Microsoft.DotNet.Cli.Sln.Internal; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Tools.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.DotNet.Tools.Add.ProjectToSolution +{ + public class AddProjectToSolutionCommand + { + internal static CommandLineApplication CreateApplication(CommandLineApplication parentApp) + { + CommandLineApplication app = parentApp.Command("project", throwOnUnexpectedArg: false); + app.FullName = LocalizableStrings.AppFullName; + app.Description = LocalizableStrings.AppDescription; + app.HandleRemainingArguments = true; + app.ArgumentSeparatorHelpText = LocalizableStrings.AppHelpText; + + app.HelpOption("-h|--help"); + + app.OnExecute(() => + { + try + { + if (!parentApp.Arguments.Any()) + { + throw new GracefulException(CommonLocalizableStrings.RequiredArgumentNotPassed, Constants.ProjectOrSolutionArgumentName); + } + + var projectOrDirectory = parentApp.Arguments.First().Value; + if (string.IsNullOrEmpty(projectOrDirectory)) + { + projectOrDirectory = PathUtility.EnsureTrailingSlash(Directory.GetCurrentDirectory()); + } + + SlnFile slnFile = SlnFileFactory.CreateFromFileOrDirectory(projectOrDirectory); + + if (app.RemainingArguments.Count == 0) + { + throw new GracefulException(CommonLocalizableStrings.SpecifyAtLeastOneProjectToAdd); + } + + List projectPaths = app.RemainingArguments; + PathUtility.EnsureAllPathsExist(projectPaths, CommonLocalizableStrings.ProjectDoesNotExist); + var relativeProjectPaths = projectPaths.Select((p) => + PathUtility.GetRelativePath( + PathUtility.EnsureTrailingSlash(slnFile.BaseDirectory), + Path.GetFullPath(p))).ToList(); + + int preAddProjectCount = slnFile.Projects.Count; + foreach (var project in relativeProjectPaths) + { + AddProject(slnFile, project); + } + + if (slnFile.Projects.Count > preAddProjectCount) + { + slnFile.Write(); + } + + return 0; + } + catch (GracefulException e) + { + Reporter.Error.WriteLine(e.Message.Red()); + app.ShowHelp(); + return 1; + } + }); + + return app; + } + + private static void AddProject(SlnFile slnFile, string projectPath) + { + var projectPathNormalized = PathUtility.GetPathWithBackSlashes(projectPath); + + if (slnFile.Projects.Any((p) => + string.Equals(p.FilePath, projectPathNormalized, StringComparison.OrdinalIgnoreCase))) + { + Reporter.Output.WriteLine(string.Format( + CommonLocalizableStrings.SolutionAlreadyContainsProject, + slnFile.FullPath, + projectPath)); + } + else + { + string projectGuidString = null; + if (File.Exists(projectPath)) + { + var projectElement = ProjectRootElement.Open( + projectPath, + new ProjectCollection(), + preserveFormatting: true); + + var projectGuidProperty = projectElement.Properties.Where((p) => + string.Equals(p.Name, "ProjectGuid", StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + + if (projectGuidProperty != null) + { + projectGuidString = projectGuidProperty.Value; + } + } + + var projectGuid = (projectGuidString == null) + ? Guid.NewGuid() + : new Guid(projectGuidString); + + var slnProject = new SlnProject + { + Id = projectGuid.ToString("B").ToUpper(), + TypeGuid = ProjectTypeGuids.CPSProjectTypeGuid, + Name = Path.GetFileNameWithoutExtension(projectPath), + FilePath = projectPathNormalized + }; + + slnFile.Projects.Add(slnProject); + Reporter.Output.WriteLine( + string.Format(CommonLocalizableStrings.ProjectAddedToTheSolution, projectPath)); + } + } + } +} diff --git a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs index 947d2729b..ac746a5ad 100644 --- a/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs +++ b/src/dotnet/commands/dotnet-migrate/MigrateCommand.cs @@ -19,8 +19,6 @@ namespace Microsoft.DotNet.Tools.Migrate { public partial class MigrateCommand { - private const string CSharpProjectTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"; - private SlnFile _slnFile; private readonly DirectoryInfo _workspaceDirectory; private readonly DirectoryInfo _backupDirectory; @@ -125,7 +123,7 @@ namespace Microsoft.DotNet.Tools.Migrate if (csprojFiles.Count() == 1) { project.FilePath = Path.Combine(Path.GetDirectoryName(project.FilePath), csprojFiles.First().Name); - project.TypeGuid = CSharpProjectTypeGuid; + project.TypeGuid = ProjectTypeGuids.CSharpProjectTypeGuid; } } diff --git a/test/Microsoft.DotNet.Cli.Sln.Internal.Tests/Microsoft.DotNet.Cli.Sln.Internal.Tests.cs b/test/Microsoft.DotNet.Cli.Sln.Internal.Tests/Microsoft.DotNet.Cli.Sln.Internal.Tests.cs index fe18cc5cd..084ac7c91 100644 --- a/test/Microsoft.DotNet.Cli.Sln.Internal.Tests/Microsoft.DotNet.Cli.Sln.Internal.Tests.cs +++ b/test/Microsoft.DotNet.Cli.Sln.Internal.Tests/Microsoft.DotNet.Cli.Sln.Internal.Tests.cs @@ -14,7 +14,7 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.Tests public class GivenAnSlnFile : TestBase { [Fact] - public void It_reads_an_sln_file() + public void WhenGivenAValidPathItReadsAnSlnFile() { var solutionDirectory = TestAssetsManager.CreateTestInstance("TestAppWithSln", callingMethod: "p").Path; @@ -39,7 +39,7 @@ namespace Microsoft.DotNet.Cli.Sln.Internal.Tests } [Fact] - public void It_writes_an_sln_file() + public void WhenGivenAValidPathItReadsModifiesThenWritesAnSln() { var solutionDirectory = TestAssetsManager.CreateTestInstance("TestAppWithSln", callingMethod: "p").Path; diff --git a/test/dotnet-add-proj.Tests/GivenDotnetAddProj.cs b/test/dotnet-add-proj.Tests/GivenDotnetAddProj.cs new file mode 100644 index 000000000..9e61470b6 --- /dev/null +++ b/test/dotnet-add-proj.Tests/GivenDotnetAddProj.cs @@ -0,0 +1,203 @@ +// 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 FluentAssertions; +using Microsoft.Build.Construction; +using Microsoft.DotNet.Cli.Sln.Internal; +using Microsoft.DotNet.Tools.Test.Utilities; +using System; +using System.IO; +using System.Linq; +using Xunit; + +namespace Microsoft.DotNet.Cli.Add.Proj.Tests +{ + public class GivenDotnetAddProj : TestBase + { + [Theory] + [InlineData("--help")] + [InlineData("-h")] + public void WhenHelpOptionIsPassedItPrintsUsage(string helpArg) + { + var cmd = new DotnetCommand() + .ExecuteWithCapturedOutput($"add project {helpArg}"); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("Usage"); + } + + [Theory] + [InlineData("idontexist.sln")] + [InlineData("ihave?invalidcharacters")] + [InlineData("ihaveinv@lidcharacters")] + [InlineData("ihaveinvalid/characters")] + [InlineData("ihaveinvalidchar\\acters")] + public void WhenNonExistingSolutionIsPassedItPrintsErrorAndUsage(string solutionName) + { + var cmd = new DotnetCommand() + .ExecuteWithCapturedOutput($"add {solutionName} project p.csproj"); + cmd.Should().Fail(); + cmd.StdErr.Should().Contain("Could not find"); + cmd.StdOut.Should().Contain("Usage:"); + } + + [Fact] + public void WhenInvalidSolutionIsPassedItPrintsErrorAndUsage() + { + var projectDirectory = TestAssetsManager.CreateTestInstance("InvalidSolution") + .WithLockFiles() + .Path; + + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput($"add InvalidSolution.sln project {projectToAdd}"); + cmd.Should().Fail(); + cmd.StdErr.Should().Contain("Invalid solution "); + cmd.StdOut.Should().Contain("Usage:"); + } + + [Fact] + public void WhenInvalidSolutionIsFoundItPrintsErrorAndUsage() + { + var projectDirectory = TestAssetsManager.CreateTestInstance("InvalidSolution") + .WithLockFiles() + .Path; + + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput($"add project {projectToAdd}"); + cmd.Should().Fail(); + cmd.StdErr.Should().Contain("Invalid solution "); + cmd.StdOut.Should().Contain("Usage:"); + } + + [Fact] + public void WhenNoProjectIsPassedItPrintsErrorAndUsage() + { + var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppWithSlnAndCsprojFiles") + .WithLockFiles() + .Path; + + var cmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput(@"add App.sln project"); + cmd.Should().Fail(); + cmd.StdErr.Should().Contain("You must specify at least one project to add."); + cmd.StdOut.Should().Contain("Usage:"); + } + + [Fact] + public void WhenNoSolutionExistsInTheDirectoryItPrintsErrorAndUsage() + { + var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppWithSlnAndCsprojFiles") + .WithLockFiles() + .Path; + + var cmd = new DotnetCommand() + .WithWorkingDirectory(Path.Combine(projectDirectory, "App")) + .ExecuteWithCapturedOutput(@"add project App.csproj"); + cmd.Should().Fail(); + cmd.StdErr.Should().Contain("does not exist"); + cmd.StdOut.Should().Contain("Usage:"); + } + + [Fact] + public void WhenMoreThanOneSolutionExistsInTheDirectoryItPrintsErrorAndUsage() + { + var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppWithMultipleSlnFiles") + .WithLockFiles() + .Path; + + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput($"add project {projectToAdd}"); + cmd.Should().Fail(); + cmd.StdErr.Should().Contain("more than one"); + cmd.StdOut.Should().Contain("Usage"); + } + + [Theory] + [InlineData("TestAppWithSlnAndCsprojFiles", "")] + [InlineData("TestAppWithSlnAndCsprojProjectGuidFiles", "{84A45D44-B677-492D-A6DA-B3A71135AB8E}")] + public void WhenValidProjectIsPassedItGetsNormalizedAndAddedAndSlnBuilds( + string testAsset, + string projectGuid) + { + var projectDirectory = TestAssetsManager.CreateTestInstance(testAsset) + .WithLockFiles() + .Path; + + var projectToAdd = "Lib/Lib.csproj"; + var normalizedProjectPath = @"Lib\Lib.csproj"; + var cmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput($"add App.sln project {projectToAdd}"); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the solution"); + cmd.StdErr.Should().BeEmpty(); + + var slnFile = SlnFile.Read(Path.Combine(projectDirectory, "App.sln")); + var matchingProjects = slnFile.Projects + .Where((p) => p.Name == "Lib") + .ToList(); + + matchingProjects.Count.Should().Be(1); + var slnProject = matchingProjects[0]; + slnProject.FilePath.Should().Be(normalizedProjectPath); + slnProject.TypeGuid.Should().Be(ProjectTypeGuids.CPSProjectTypeGuid); + if (!string.IsNullOrEmpty(projectGuid)) + { + slnProject.Id.Should().Be(projectGuid); + } + + var restoreCmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .Execute($"restore {Path.Combine("App", "App.csproj")}"); + + var buildCmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .Execute("build App.sln"); + buildCmd.Should().Pass(); + } + + [Fact] + public void WhenSolutionAlreadyContainsProjectItDoesntDuplicate() + { + var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppWithSlnAndExistingCsprojReferences") + .WithLockFiles() + .Path; + + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput($"add App.sln project {projectToAdd}"); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("already contains project"); + cmd.StdErr.Should().BeEmpty(); + } + + [Fact] + public void WhenPassedMultipleProjectsAndOneOfthemDoesNotExistItCancelsWholeOperation() + { + var projectDirectory = TestAssetsManager.CreateTestInstance("TestAppWithSlnAndCsprojFiles") + .WithLockFiles() + .Path; + + var slnFullPath = Path.Combine(projectDirectory, "App.sln"); + var contentBefore = File.ReadAllText(slnFullPath); + + var projectToAdd = Path.Combine("Lib", "Lib.csproj"); + var cmd = new DotnetCommand() + .WithWorkingDirectory(projectDirectory) + .ExecuteWithCapturedOutput($"add App.sln project {projectToAdd} idonotexist.csproj"); + cmd.Should().Fail(); + cmd.StdErr.Should().Contain("does not exist"); + cmd.StdErr.Should().NotMatchRegex("(.*does not exist.*){2,}"); + + File.ReadAllText(slnFullPath) + .Should().BeEquivalentTo(contentBefore); + } + } +} diff --git a/test/dotnet-add-proj.Tests/MSBuild.exe b/test/dotnet-add-proj.Tests/MSBuild.exe new file mode 100644 index 000000000..9bda258ef --- /dev/null +++ b/test/dotnet-add-proj.Tests/MSBuild.exe @@ -0,0 +1 @@ +https://github.com/Microsoft/msbuild/issues/927 diff --git a/test/dotnet-add-proj.Tests/MSBuild.exe.config b/test/dotnet-add-proj.Tests/MSBuild.exe.config new file mode 100644 index 000000000..9bda258ef --- /dev/null +++ b/test/dotnet-add-proj.Tests/MSBuild.exe.config @@ -0,0 +1 @@ +https://github.com/Microsoft/msbuild/issues/927 diff --git a/test/dotnet-add-proj.Tests/dotnet-add-proj.Tests.csproj b/test/dotnet-add-proj.Tests/dotnet-add-proj.Tests.csproj new file mode 100644 index 000000000..d302ba5c4 --- /dev/null +++ b/test/dotnet-add-proj.Tests/dotnet-add-proj.Tests.csproj @@ -0,0 +1,51 @@ + + + + + + netcoreapp1.0 + true + dotnet-add-proj.Tests + $(PackageTargetFallback);dotnet5.4;portable-net451+win8 + $(DefineConstants);DISABLE_TRACE + + + + + + + + + + + + + + + + + + + + $(CLI_NETSDK_Version) + All + + + 15.0.0-preview-20161024-02 + + + 2.2.0-beta4-build1194 + + + 1.0.1 + + + 2.2.0-beta4-build3444 + + + $(CLI_MSBuild_Version) + + + + +