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)
+
+
+
+
+