diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Broken/Broken.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Broken/Broken.csproj new file mode 100644 index 000000000..3203e50a0 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Broken/Broken.csproj @@ -0,0 +1,14 @@ + + + + + Library + net451;netcoreapp1.0 + + + + + + 1.0.0-alpha-20161104-2 + All + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/EmptyItemGroup/EmptyItemGroup.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/EmptyItemGroup/EmptyItemGroup.cs new file mode 100644 index 000000000..4c665f47a --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/EmptyItemGroup/EmptyItemGroup.cs @@ -0,0 +1,3 @@ +public class EmptyItemGroup +{ +} \ No newline at end of file diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib3/Lib3.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/EmptyItemGroup/EmptyItemGroup.csproj similarity index 73% rename from TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib3/Lib3.csproj rename to TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/EmptyItemGroup/EmptyItemGroup.csproj index 3466eb925..3db580ead 100644 --- a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib3/Lib3.csproj +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/EmptyItemGroup/EmptyItemGroup.csproj @@ -18,12 +18,8 @@ 1.0.1 - - $(DefineConstants);NETCOREAPP1_0 - - - $(DefineConstants);NET451 - - + + + - \ No newline at end of file + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib/Lib.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib/Lib.cs new file mode 100644 index 000000000..e4708deab --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib/Lib.cs @@ -0,0 +1,4 @@ +// Dummy reference - it can be used as an existing reference in any of the projects +public class Lib +{ +} \ No newline at end of file diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib/Lib.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib/Lib.csproj new file mode 100644 index 000000000..b61db6f8d --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib/Lib.csproj @@ -0,0 +1,22 @@ + + + + + Library + net451;netcoreapp1.0 + + + + + + 1.0.0-alpha-20161104-2 + All + + + + + 1.0.1 + + + + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib2/Lib2.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib2/Lib2.cs deleted file mode 100644 index 9452473dc..000000000 --- a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib2/Lib2.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using NSLib4; - -namespace NSLib2 -{ - public class Lib2 - { -#if NETCOREAPP1_0 - public static void HelloNCA() - { - Console.WriteLine("Hello World from Lib2! (netcoreapp)"); - Lib4.HelloNCA(); - } -#endif - -#if NET451 - public static void HelloNet451() - { - Console.WriteLine("Hello World from Lib2! (net45)"); - Lib4.HelloNet451(); - } -#endif - } -} diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib3/Lib3.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib3/Lib3.cs deleted file mode 100644 index b86a60de4..000000000 --- a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib3/Lib3.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace NSLib3 -{ - public class Lib3 - { -#if NETCOREAPP1_0 - public static void HelloNCA() - { - Console.WriteLine("Hello World from Lib3! (netcoreapp)"); - } -#endif - -#if NET451 - public static void HelloNet451() - { - Console.WriteLine("Hello World from Lib3! (net45)"); - } -#endif - } -} diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/MoreThanOne/SomeSourceFile.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/MoreThanOne/SomeSourceFile.cs new file mode 100644 index 000000000..3e8c40581 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/MoreThanOne/SomeSourceFile.cs @@ -0,0 +1,8 @@ +using System; + +namespace SomeNS +{ + public class SomeClass + { + } +} diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/MoreThanOne/a.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/MoreThanOne/a.csproj new file mode 100644 index 000000000..7a03821ea --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/MoreThanOne/a.csproj @@ -0,0 +1,20 @@ + + + + + Library + netcoreapp1.0 + + + + + + 1.0.0-alpha-20161104-2 + All + + + 1.0.1 + + + + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/MoreThanOne/b.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/MoreThanOne/b.csproj new file mode 100644 index 000000000..7a03821ea --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/MoreThanOne/b.csproj @@ -0,0 +1,20 @@ + + + + + Library + netcoreapp1.0 + + + + + + 1.0.0-alpha-20161104-2 + All + + + 1.0.1 + + + + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/ValidRef/ValidRef.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/ValidRef/ValidRef.cs new file mode 100644 index 000000000..f250057d8 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/ValidRef/ValidRef.cs @@ -0,0 +1,5 @@ +// Dummy reference - it should not be referenced by any other project directly +// it should be used as a reference passed to the dotnet add p2p +public class ValidRef +{ +} \ No newline at end of file diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/ValidRef/ValidRef.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/ValidRef/ValidRef.csproj new file mode 100644 index 000000000..b61db6f8d --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/ValidRef/ValidRef.csproj @@ -0,0 +1,22 @@ + + + + + Library + net451;netcoreapp1.0 + + + + + + 1.0.0-alpha-20161104-2 + All + + + + + 1.0.1 + + + + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondOnItem/WithExistingRefCondOnItem.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondOnItem/WithExistingRefCondOnItem.cs new file mode 100644 index 000000000..15d976dc2 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondOnItem/WithExistingRefCondOnItem.cs @@ -0,0 +1,3 @@ +public class WithExistingRefCondOnItem +{ +} \ No newline at end of file diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib2/Lib2.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondOnItem/WithExistingRefCondOnItem.csproj similarity index 59% rename from TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib2/Lib2.csproj rename to TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondOnItem/WithExistingRefCondOnItem.csproj index e4d8997f8..a85516e34 100644 --- a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib2/Lib2.csproj +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondOnItem/WithExistingRefCondOnItem.csproj @@ -19,18 +19,7 @@ - - + - - - - - $(DefineConstants);NETCOREAPP1_0 - - - $(DefineConstants);NET451 - - - \ No newline at end of file + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondWhitespaces/WithExistingRefCondWhitespaces.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondWhitespaces/WithExistingRefCondWhitespaces.cs new file mode 100644 index 000000000..4de50b9d8 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondWhitespaces/WithExistingRefCondWhitespaces.cs @@ -0,0 +1,3 @@ +public class WithExistingRefCondWhitespaces +{ +} \ No newline at end of file diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondWhitespaces/WithExistingRefCondWhitespaces.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondWhitespaces/WithExistingRefCondWhitespaces.csproj new file mode 100644 index 000000000..79aca8ec9 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithExistingRefCondWhitespaces/WithExistingRefCondWhitespaces.csproj @@ -0,0 +1,25 @@ + + + + + Library + net451;netcoreapp1.0 + + + + + + 1.0.0-alpha-20161104-2 + All + + + + + 1.0.1 + + + + + + + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefCondNonUniform/WithRefCondNonUniform.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefCondNonUniform/WithRefCondNonUniform.cs new file mode 100644 index 000000000..d827b8018 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefCondNonUniform/WithRefCondNonUniform.cs @@ -0,0 +1,3 @@ +public class WithRefCondNonUniform +{ +} \ No newline at end of file diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefCondNonUniform/WithRefCondNonUniform.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefCondNonUniform/WithRefCondNonUniform.csproj new file mode 100644 index 000000000..0f5e7d1b7 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefCondNonUniform/WithRefCondNonUniform.csproj @@ -0,0 +1,26 @@ + + + + + Library + net451;netcoreapp1.0 + + + + + + 1.0.0-alpha-20161104-2 + All + + + + + 1.0.1 + + + + + + + + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefNoCondNonUniform/WithRefNoCondNonUniform.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefNoCondNonUniform/WithRefNoCondNonUniform.cs new file mode 100644 index 000000000..c71d17552 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefNoCondNonUniform/WithRefNoCondNonUniform.cs @@ -0,0 +1,3 @@ +public class WithRefNoCondNonUniform +{ +} \ No newline at end of file diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefNoCondNonUniform/WithRefNoCondNonUniform.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefNoCondNonUniform/WithRefNoCondNonUniform.csproj new file mode 100644 index 000000000..a4f9328b0 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithRefNoCondNonUniform/WithRefNoCondNonUniform.csproj @@ -0,0 +1,26 @@ + + + + + Library + net451;netcoreapp1.0 + + + + + + 1.0.0-alpha-20161104-2 + All + + + + + 1.0.1 + + + + + + + + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/App/App.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/AppE2E/App.csproj similarity index 99% rename from TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/App/App.csproj rename to TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/AppE2E/App.csproj index 2df913261..a476f96b3 100644 --- a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/App/App.csproj +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/AppE2E/App.csproj @@ -27,6 +27,5 @@ $(DefineConstants);NET451 - - \ No newline at end of file + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/App/Program.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/AppE2E/Program.cs similarity index 100% rename from TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/App/Program.cs rename to TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/AppE2E/Program.cs diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib1/Lib1.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib1/Lib1.cs similarity index 100% rename from TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib1/Lib1.cs rename to TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib1/Lib1.cs diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib1/Lib1.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib1/Lib1.csproj similarity index 99% rename from TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib1/Lib1.csproj rename to TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib1/Lib1.csproj index 3466eb925..a5ea660c6 100644 --- a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib1/Lib1.csproj +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib1/Lib1.csproj @@ -24,6 +24,5 @@ $(DefineConstants);NET451 - - \ No newline at end of file + diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib4/Lib4.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib4/Lib4.cs similarity index 100% rename from TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib4/Lib4.cs rename to TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib4/Lib4.cs diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib4/Lib4.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib4/Lib4.csproj similarity index 99% rename from TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib4/Lib4.csproj rename to TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib4/Lib4.csproj index 76b8b884d..55cecb538 100644 --- a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/Lib4/Lib4.csproj +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/_remove_me_/Lib4/Lib4.csproj @@ -27,6 +27,5 @@ $(DefineConstants);NET451 - - \ No newline at end of file + diff --git a/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs b/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs index 2d0d9bbfd..1cd3440a5 100644 --- a/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs +++ b/src/Microsoft.DotNet.ProjectJsonMigration/MSBuildExtensions.cs @@ -27,14 +27,18 @@ namespace Microsoft.DotNet.ProjectJsonMigration // Different includes if (item.IntersectIncludes(otherItem).Count() != item.Includes().Count()) { +#if !DISABLE_TRACE MigrationTrace.Instance.WriteLine($"{nameof(MSBuildExtensions)}.{nameof(IsEquivalentTo)} includes not equivalent."); +#endif return false; } // Different Excludes if (item.IntersectExcludes(otherItem).Count() != item.Excludes().Count()) { +#if !DISABLE_TRACE MigrationTrace.Instance.WriteLine($"{nameof(MSBuildExtensions)}.{nameof(IsEquivalentTo)} excludes not equivalent."); +#endif return false; } @@ -46,7 +50,9 @@ namespace Microsoft.DotNet.ProjectJsonMigration // Different remove if (item.Remove != otherItem.Remove) { +#if !DISABLE_TRACE MigrationTrace.Instance.WriteLine($"{nameof(MSBuildExtensions)}.{nameof(IsEquivalentTo)} removes not equivalent."); +#endif return false; } @@ -61,13 +67,17 @@ namespace Microsoft.DotNet.ProjectJsonMigration var otherMetadata = itemToCompare.GetMetadataWithName(metadata.Name); if (otherMetadata == null) { +#if !DISABLE_TRACE MigrationTrace.Instance.WriteLine($"{nameof(MSBuildExtensions)}.{nameof(IsEquivalentTo)} metadata doesn't exist {{ {metadata.Name} {metadata.Value} }}"); +#endif return false; } if (!metadata.ValueEquals(otherMetadata)) { +#if !DISABLE_TRACE MigrationTrace.Instance.WriteLine($"{nameof(MSBuildExtensions)}.{nameof(IsEquivalentTo)} metadata has another value {{ {metadata.Name} {metadata.Value} {otherMetadata.Value} }}"); +#endif return false; } } @@ -197,7 +207,9 @@ namespace Microsoft.DotNet.ProjectJsonMigration if (existingMetadata == default(ProjectMetadataElement)) { +#if !DISABLE_TRACE MigrationTrace.Instance.WriteLine($"{nameof(AddMetadata)}: Adding metadata to {item.ItemType} item: {{ {metadata.Name}, {metadata.Value} }}"); +#endif item.AddMetadata(metadata.Name, metadata.Value); } } diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 5265a75e2..46241701f 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -3,12 +3,12 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Text; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Configurer; using Microsoft.DotNet.PlatformAbstractions; +using Microsoft.DotNet.Tools.Add; using Microsoft.DotNet.Tools.Build; using Microsoft.DotNet.Tools.Clean; using Microsoft.DotNet.Tools.Help; @@ -24,7 +24,6 @@ using Microsoft.DotNet.Tools.Run; using Microsoft.DotNet.Tools.Test; using Microsoft.DotNet.Tools.VSTest; using NuGet.Frameworks; -using Microsoft.DotNet.Tools.Add; namespace Microsoft.DotNet.Cli { @@ -32,6 +31,7 @@ namespace Microsoft.DotNet.Cli { private static Dictionary> s_builtIns = new Dictionary> { + ["add"] = AddCommand.Run, ["build"] = BuildCommand.Run, ["clean"] = CleanCommand.Run, ["help"] = HelpCommand.Run, @@ -46,7 +46,6 @@ namespace Microsoft.DotNet.Cli ["run"] = RunCommand.Run, ["test"] = TestCommand.Run, ["vstest"] = VSTestCommand.Run, - ["add"] = AddCommand.Run, }; public static int Main(string[] args) diff --git a/src/dotnet/commands/dotnet-add-p2p/MsbuildProjectExtensions.cs b/src/dotnet/commands/dotnet-add-p2p/MsbuildProjectExtensions.cs new file mode 100644 index 000000000..1e3bd9488 --- /dev/null +++ b/src/dotnet/commands/dotnet-add-p2p/MsbuildProjectExtensions.cs @@ -0,0 +1,84 @@ +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference +{ + internal static class MsbuildProjectExtensions + { + public static bool IsConditionalOnFramework(this ProjectElement el, string framework) + { + string conditionStr; + if (!TryGetFrameworkConditionString(framework, out conditionStr)) + { + return el.ConditionChain().Count == 0; + } + + var condChain = el.ConditionChain(); + return condChain.Count == 1 && condChain.First().Trim() == conditionStr; + } + + public static ProjectItemGroupElement LastItemGroup(this ProjectRootElement root) + { + return root.ItemGroupsReversed.FirstOrDefault(); + } + + public static ProjectItemGroupElement FindUniformOrCreateItemGroupWithCondition(this ProjectRootElement root, string projectItemElementType, string framework) + { + var lastMatchingItemGroup = FindExistingUniformItemGroupWithCondition(root, projectItemElementType, framework); + + if (lastMatchingItemGroup != null) + { + return lastMatchingItemGroup; + } + + ProjectItemGroupElement ret = root.CreateItemGroupElement(); + string condStr; + if (TryGetFrameworkConditionString(framework, out condStr)) + { + ret.Condition = condStr; + } + + root.InsertAfterChild(ret, root.LastItemGroup()); + return ret; + } + + public static ProjectItemGroupElement FindExistingUniformItemGroupWithCondition(this ProjectRootElement root, string projectItemElementType, string framework) + { + return root.ItemGroupsReversed.FirstOrDefault((ig) => ig.IsConditionalOnFramework(framework) && ig.IsUniformItemElementType(projectItemElementType)); + } + + public static bool IsUniformItemElementType(this ProjectItemGroupElement group, string projectItemElementType) + { + return group.Items.All((it) => it.ItemType == projectItemElementType); + } + + public static IEnumerable FindExistingItemsWithCondition(this ProjectRootElement root, string framework, string include) + { + return root.Items.Where((el) => el.IsConditionalOnFramework(framework) && el.HasInclude(include)); + } + + public static bool HasExistingItemWithCondition(this ProjectRootElement root, string framework, string include) + { + return root.FindExistingItemsWithCondition(framework, include).Count() != 0; + } + + public static bool HasInclude(this ProjectItemElement el, string include) + { + return el.Includes().Contains(include); + } + + private static bool TryGetFrameworkConditionString(string framework, out string condition) + { + if (string.IsNullOrEmpty(framework)) + { + condition = null; + return false; + } + + condition = $"'$(TargetFramework)' == '{framework}'"; + return true; + } + } +} diff --git a/src/dotnet/commands/dotnet-add-p2p/Program.cs b/src/dotnet/commands/dotnet-add-p2p/Program.cs index 611e5ee7d..42dd41997 100644 --- a/src/dotnet/commands/dotnet-add-p2p/Program.cs +++ b/src/dotnet/commands/dotnet-add-p2p/Program.cs @@ -8,7 +8,7 @@ using System; using System.IO; using System.Linq; using Microsoft.Build.Construction; -using Microsoft.DotNet.ProjectJsonMigration; +using Microsoft.Build.Evaluation; namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference { @@ -22,31 +22,40 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference { Name = "dotnet add p2p", FullName = ".NET Add Project to Project (p2p) reference Command", - Description = "Command to add project to project (p2p) reference" + Description = "Command to add project to project (p2p) reference", + AllowArgumentSeparator = true, + ArgumentSeparatorHelpText = "Project to project references to add" }; + app.HelpOption("-h|--help"); CommandArgument projectArgument = app.Argument("", - "The MSBuild project file to modify. If a project file is not specified," + + "The project file to modify. If a project file is not specified," + " it searches the current working directory for an MSBuild file that has a file extension that ends in `proj` and uses that file."); CommandOption frameworkOption = app.Option("-f|--framework ", "Add reference only when targetting a specific framework", CommandOptionType.SingleValue); - CommandOption referenceOption = app.Option("-r|--reference ", "Add project to project to ", CommandOptionType.MultipleValue); - app.OnExecute(() => { - ProjectRootElement project = projectArgument.Value != null ? - GetProjectFromFileOrThrow(projectArgument.Value) : - GetProjectFromCurrentDirectoryOrThrow(); + if (projectArgument.Value == null) + { + throw new GracefulException("Argument is required."); + } - if (referenceOption.Values.Count == 0) + ProjectRootElement project = File.Exists(projectArgument.Value) ? + GetProjectFromFileOrThrow(projectArgument.Value) : + GetProjectFromDirectoryOrThrow(projectArgument.Value); + + if (app.RemainingArguments.Count == 0) { throw new GracefulException("You must specify at least one reference to add."); } - AddProjectToProjectReference(project, frameworkOption.Value(), referenceOption.Values); + int numberOfAddedReferences = AddProjectToProjectReference(project, frameworkOption.Value(), app.RemainingArguments); - //project.Save(); + if (numberOfAddedReferences != 0) + { + project.Save(); + } return 0; }); @@ -57,7 +66,8 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference } catch (GracefulException e) { - Reporter.Error.WriteLine(e.Message); + Reporter.Error.WriteLine(e.Message.Red()); + app.ShowHelp(); return 1; } } @@ -68,7 +78,7 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference { try { - return ProjectRootElement.Open(filename); + return ProjectRootElement.Open(filename, new ProjectCollection(), preserveFormatting: true); } catch (Microsoft.Build.Exceptions.InvalidProjectFileException) { @@ -86,19 +96,34 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference var project = TryOpenProject(filename); if (project == null) { - throw new GracefulException($"Invalid MSBuild project `{filename}`."); + throw new GracefulException($"Invalid project `{filename}`."); } return project; } - public static ProjectRootElement GetProjectFromCurrentDirectoryOrThrow() + // TODO: improve errors + public static ProjectRootElement GetProjectFromDirectoryOrThrow(string directory) { - DirectoryInfo currDir = new DirectoryInfo(Directory.GetCurrentDirectory()); - FileInfo[] files = currDir.GetFiles("*proj"); + DirectoryInfo dir; + try + { + dir = new DirectoryInfo(directory); + } + catch (ArgumentException) + { + throw new GracefulException($"Could not find project or directory `{directory}`."); + } + + if (!dir.Exists) + { + throw new GracefulException($"Could not find project or directory `{directory}`."); + } + + FileInfo[] files = dir.GetFiles("*proj"); if (files.Length == 0) { - throw new GracefulException("Could not find any MSBuild project in the current directory."); + throw new GracefulException($"Could not find any project in `{directory}`."); } if (files.Length > 1) @@ -110,123 +135,40 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference if (!projectFile.Exists) { - throw new GracefulException("Could not find any project in the current directory."); + throw new GracefulException($"Could not find any project in `{directory}`."); } var ret = TryOpenProject(projectFile.FullName); if (ret == null) { - throw new GracefulException($"Found an MSBuild project `{projectFile.FullName}` in the current directory but it is invalid."); + throw new GracefulException($"Found a project `{projectFile.FullName}` but it is invalid."); } return ret; } - public static Func AndPred(params Func[] preds) - { - return (el) => preds.All((pred) => pred == null || pred(el)); - } - - public static string GetFrameworkConditionString(string framework) - { - if (string.IsNullOrEmpty(framework)) - { - return null; - } - - return $" '$(TargetFramework)' == '{framework}' "; - } - - public static Func FrameworkPred(string framework) where T : ProjectElement - { - string conditionStr = GetFrameworkConditionString(framework); - if (conditionStr == null) - { - return (ig) => { - var condChain = ig.ConditionChain(); - return condChain.Count == 0; - }; - } - - conditionStr = conditionStr.Trim(); - return (ig) => { - var condChain = ig.ConditionChain(); - return condChain.Count == 1 && condChain.First().Trim() == conditionStr; - }; - } - - public static Func UniformItemElementTypePred(string projectItemElementType) - { - return (ig) => ig.Items.All((it) => it.ItemType == projectItemElementType); - } - - public static Func IncludePred(string include) - { - return (it) => it.Include == include; - } - - public static ProjectItemElement[] FindExistingItemsWithCondition(ProjectRootElement root, string framework, string include) - { - return root.Items - .Where( - AndPred( - FrameworkPred(framework), - IncludePred(include))) - .ToArray(); - } - - public static ProjectItemGroupElement FindExistingUniformItemGroupWithCondition(ProjectRootElement root, string projectItemElementType, string framework) - { - return root.ItemGroupsReversed - .FirstOrDefault( - AndPred( - // When adding more predicates which operate on ItemGroup.Condition - // some slightly more advanced logic need to be used: - // i.e. ConditionPred(FrameworkConditionPred(framework), RuntimeConditionPred(runtime)) - // FrameworkConditionPred and RuntimeConditionPred would need to operate on a single condition - // and ConditionPred would need to check if whole Condition Chain is satisfied - FrameworkPred(framework), - UniformItemElementTypePred(projectItemElementType))); - } - - public static ProjectItemGroupElement FindUniformOrCreateItemGroupWithCondition(ProjectRootElement root, string projectItemElementType, string framework) - { - var lastMatchingItemGroup = FindExistingUniformItemGroupWithCondition(root, projectItemElementType, framework); - - if (lastMatchingItemGroup != null) - { - return lastMatchingItemGroup; - } - - ProjectItemGroupElement ret = root.CreateItemGroupElement(); - string condStr = GetFrameworkConditionString(framework); - if (condStr != null) - { - ret.Condition = condStr; - } - - root.AppendChild(ret); - return ret; - } - - public static void AddProjectToProjectReference(ProjectRootElement root, string framework, IEnumerable refs) + public static int AddProjectToProjectReference(ProjectRootElement root, string framework, IEnumerable refs) { + int numberOfAddedReferences = 0; const string ProjectItemElementType = "ProjectReference"; ProjectItemGroupElement ig = null; foreach (var @ref in refs) { - if (FindExistingItemsWithCondition(root, framework, @ref).Length == 0) + if (root.HasExistingItemWithCondition(framework, @ref)) { - Reporter.Output.WriteLine($"Item {ProjectItemElementType} including `{@ref}` is already present."); + Reporter.Output.WriteLine($"Project already has a reference to `{@ref}`."); continue; } - ig = ig ?? FindUniformOrCreateItemGroupWithCondition(root, ProjectItemElementType, framework); + numberOfAddedReferences++; + ig = ig ?? root.FindUniformOrCreateItemGroupWithCondition(ProjectItemElementType, framework); ig.AppendChild(root.CreateItemElement(ProjectItemElementType, @ref)); - Reporter.Output.WriteLine($"Item {ProjectItemElementType} including `{@ref}` added to project."); + Reporter.Output.WriteLine($"Reference `{@ref}` added to the project."); } + + return numberOfAddedReferences; } } } diff --git a/src/dotnet/commands/dotnet-add/Program.cs b/src/dotnet/commands/dotnet-add/Program.cs index faf32a201..1e3b2b29a 100644 --- a/src/dotnet/commands/dotnet-add/Program.cs +++ b/src/dotnet/commands/dotnet-add/Program.cs @@ -16,15 +16,22 @@ namespace Microsoft.DotNet.Tools.Add { public class AddCommand { - public const string CommandName = "dotnet-add"; - public const string UsageText = @"Usage: dotnet add [command] [arguments] + public const string HelpText = @".NET Add Command + +Usage: dotnet add [options] [object] [[--] ...]] Arguments: - [command] The command to execute - [arguments] Arguments to pass to the command + The object of the operation. If a project file is not specified, it defaults to the current directory. + Command to be executed on . + +Options: + -h|--help Show help information + +Args: + Any extra arguments passed to the command. Commands: - p2p Add project to project (p2p) reference to a project"; + p2p Add project to project (p2p) reference to a project"; private static Dictionary> s_builtIns = new Dictionary> { @@ -37,33 +44,57 @@ Commands: if (args.Length == 0 || args[0] == "--help" || args[0] == "-h") { - Reporter.Output.WriteLine(UsageText); - return 1; + Reporter.Output.WriteLine(HelpText); + return 0; } - if (args[0].StartsWith("-")) + string commandObject; + string command; + if (IsValidCommandName(args[0])) { - Reporter.Error.WriteLine($"Unknown option: {args[0]}"); - Reporter.Output.WriteLine(UsageText); - return 1; + command = args[0]; + commandObject = GetCurrentDirectoryWithDirSeparator(); + args = args.Skip(1).Prepend(commandObject).ToArray(); } - - string command = args[0]; - Func builtIn; - args = args.Skip(1).ToArray(); - if (s_builtIns.TryGetValue(command, out builtIn)) + else if (args.Length == 1) { - return builtIn(args); + Reporter.Error.WriteLine("Required argument was not passed.".Red()); + Reporter.Output.WriteLine(HelpText); + return 1; } else { - CommandResult result = Command.Create( - $"{CommandName}-{command}", - args, - FrameworkConstants.CommonFrameworks.NetStandardApp15) - .Execute(); - return result.ExitCode; + commandObject = args[0]; + command = args[1]; + + args = args.Skip(2).Prepend(commandObject).ToArray(); } + + Func builtin; + if (s_builtIns.TryGetValue(command, out builtin)) + { + return builtin(args); + } + + Reporter.Error.WriteLine("Required argument is invalid.".Red()); + Reporter.Output.WriteLine(HelpText); + return 1; + } + + private static bool IsValidCommandName(string s) + { + return s_builtIns.ContainsKey(s); + } + + private static string GetCurrentDirectoryWithDirSeparator() + { + string ret = Directory.GetCurrentDirectory(); + if (ret[ret.Length - 1] != Path.DirectorySeparatorChar) + { + ret += Path.DirectorySeparatorChar; + } + + return ret; } } } diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/AddP2PCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/AddP2PCommand.cs new file mode 100644 index 000000000..9a7f2b517 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/AddP2PCommand.cs @@ -0,0 +1,30 @@ +// 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.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Tools.Test.Utilities +{ + public sealed class AddP2PCommand : TestCommand + { + private string _projectName = null; + + public AddP2PCommand() + : base("dotnet") + { + + } + + public override CommandResult Execute(string args = "") + { + args = $"add {_projectName} p2p {args}"; + return base.ExecuteWithCapturedOutput(args); + } + + public AddP2PCommand WithProject(string projectName) + { + _projectName = projectName; + return this; + } + } +} diff --git a/test/dotnet-add-p2p.Tests/Extensions.cs b/test/dotnet-add-p2p.Tests/Extensions.cs new file mode 100644 index 000000000..cbea6cca6 --- /dev/null +++ b/test/dotnet-add-p2p.Tests/Extensions.cs @@ -0,0 +1,90 @@ +// 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 System; +using System.Linq; +using Microsoft.DotNet.ProjectJsonMigration; +using System.Collections.Generic; + +namespace Microsoft.DotNet.Cli.Add.P2P.Tests +{ + internal static class Extensions + { + //public static int CountOccurrances(this string s, string pattern) + //{ + // int ret = 0; + // for (int i = s.IndexOf(pattern); i != -1; i = s.IndexOf(pattern, i + 1)) + // { + // ret++; + // } + + // return ret; + //} + + //public static int NumberOfLinesWith(this string s, params string[] patterns) + //{ + // int ret = 0; + // string[] lines = s.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + // foreach (var line in lines) + // { + // bool shouldCount = true; + + // foreach (var p in patterns) + // { + // if (!line.Contains(p)) + // { + // shouldCount = false; + // break; + // } + // } + + // if (shouldCount) + // { + // ret++; + // } + // } + + // return ret; + //} + + public static int NumberOfItemGroupsWithConditionContaining(this ProjectRootElement root, string patternInCondition) + { + return root.ItemGroups.Count((ig) => ig.Condition.Contains(patternInCondition)); + } + + public static int NumberOfItemGroupsWithoutCondition(this ProjectRootElement root) + { + return root.ItemGroups.Count((ig) => string.IsNullOrEmpty(ig.Condition)); + } + + public static IEnumerable ItemsWithIncludeAndConditionContaining(this ProjectRootElement root, string itemType, string includePattern, string patternInCondition) + { + return root.Items.Where((it) => + { + if (it.ItemType != itemType || !it.Include.Contains(includePattern)) + { + return false; + } + + var condChain = it.ConditionChain(); + return condChain.Count == 1 && condChain.First().Contains(patternInCondition); + }); + } + + public static int NumberOfProjectReferencesWithIncludeAndConditionContaining(this ProjectRootElement root, string includePattern, string patternInCondition) + { + return root.ItemsWithIncludeAndConditionContaining("ProjectReference", includePattern, patternInCondition).Count(); + } + + public static IEnumerable ItemsWithIncludeContaining(this ProjectRootElement root, string itemType, string includePattern) + { + return root.Items.Where((it) => it.ItemType == itemType && it.Include.Contains(includePattern)); + } + + public static int NumberOfProjectReferencesWithIncludeContaining(this ProjectRootElement root, string includePattern) + { + return root.ItemsWithIncludeContaining("ProjectReference", includePattern).Count(); + } + } +} \ No newline at end of file diff --git a/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs b/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs new file mode 100644 index 000000000..27a01aede --- /dev/null +++ b/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs @@ -0,0 +1,473 @@ +// 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 Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; +using FluentAssertions; + +namespace Microsoft.DotNet.Cli.Add.P2P.Tests +{ + public class GivenDotnetAddP2P : TestBase + { + const string FrameworkNet451Arg = "-f net451"; + const string ConditionFrameworkNet451 = "== 'net451'"; + const string FrameworkNetCoreApp10Arg = "-f netcoreapp1.0"; + const string ConditionFrameworkNetCoreApp10 = "== 'netcoreapp1.0'"; + private static readonly string ValidRef = "ValidRef"; + private static readonly string ValidRefCsproj = $"{ValidRef}.csproj"; + private static readonly string ValidRefPath = Path.Combine("..", ValidRef, ValidRefCsproj); + + private static readonly string LibRef = "Lib"; + private static readonly string LibRefCsproj = $"{LibRef}.csproj"; + private static readonly string LibRefPath = Path.Combine("..", LibRef, LibRefCsproj); + + private static readonly string AppPath = Path.Combine("App", "App.csproj"); + private static readonly string Lib1Path = Path.Combine("Lib1", "Lib1.csproj"); + private static readonly string Lib2Path = Path.Combine("Lib2", "Lib2.csproj"); + private static readonly string Lib3Path = Path.Combine("Lib3", "Lib3.csproj"); + private static readonly string Lib4Path = Path.Combine("Lib4", "Lib4.csproj"); + + private string Setup([System.Runtime.CompilerServices.CallerMemberName] string callingMethod = nameof(Setup), string identifier = "") + { + const string TestGroup = "NonRestoredTestProjects"; + const string ProjectName = "DotnetAddP2PProjects"; + return GetTestGroupTestAssetsManager(TestGroup).CreateTestInstance(ProjectName, callingMethod: callingMethod, identifier: identifier).Path; + } + + private ProjDir NewDir([System.Runtime.CompilerServices.CallerMemberName] string callingMethod = nameof(NewDir), string identifier = "") + { + return new ProjDir(TestAssetsManager, callingMethod, identifier: identifier); + } + + private ProjDir NewLib([System.Runtime.CompilerServices.CallerMemberName] string callingMethod = nameof(NewDir), string identifier = "") + { + var dir = new ProjDir(TestAssetsManager, callingMethod, identifier: identifier); + + try + { + new NewCommand() + .WithWorkingDirectory(dir.Path) + .ExecuteWithCapturedOutput("-t Lib") + .Should().Pass(); + } + catch (System.ComponentModel.Win32Exception e) + { + throw new Exception($"DIDIDIDIDOIDIR: {dir.Path}\n{e}"); + } + + return dir; + } + + [Theory] + [InlineData("--help")] + [InlineData("-h")] + public void WhenHelpOptionIsPassedItPrintsUsage(string helpArg) + { + var cmd = new AddP2PCommand().Execute(helpArg); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("Usage"); + } + + [Theory] + [InlineData("idontexist.csproj")] + [InlineData("ihave?inv@lid/char\\acters")] + public void WhenNonExistingProjectIsPassedItPrintsErrorAndUsage(string projName) + { + string testRoot = NewDir().Path; + var cmd = new AddP2PCommand() + .WithWorkingDirectory(testRoot) + .WithProject(projName) + .Execute($"\"{ValidRefPath}\""); + cmd.ExitCode.Should().NotBe(0); + cmd.StdErr.Should().Contain("Could not find"); + cmd.StdOut.Should().Contain("Usage"); + } + + [Fact] + + public void WhenBrokenProjectIsPassedItPrintsErrorAndUsage() + { + string projName = "Broken/Broken.csproj"; + string testRoot = Setup(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(testRoot) + .WithProject(projName) + .Execute($"\"{ValidRefPath}\""); + cmd.ExitCode.Should().NotBe(0); + cmd.StdErr.Should().Contain("Invalid project"); + cmd.StdOut.Should().Contain("Usage"); + } + + [Fact] + public void WhenMoreThanOneProjectExistsInTheDirectoryItPrintsErrorAndUsage() + { + string testRoot = Setup(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(Path.Combine(testRoot, "MoreThanOne")) + .Execute($"\"{ValidRefPath}\""); + cmd.ExitCode.Should().NotBe(0); + cmd.StdErr.Should().Contain("more than one"); + cmd.StdOut.Should().Contain("Usage"); + } + + [Fact] + public void WhenNoProjectsExistsInTheDirectoryItPrintsErrorAndUsage() + { + string testRoot = Setup(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(testRoot) + .Execute($"\"{ValidRefPath}\""); + cmd.ExitCode.Should().NotBe(0); + cmd.StdErr.Should().Contain("not find any"); + cmd.StdOut.Should().Contain("Usage"); + } + + [Fact] + public void ItAddsRefWithoutCondAndPrintsStatus() + { + var lib = NewLib(); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"\"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + cmd.StdErr.Should().BeEmpty(); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(ValidRefCsproj).Should().Be(1); + } + + [Fact] + public void ItAddsRefWithCondAndPrintsStatus() + { + var lib = NewLib(); + + int condBefore = lib.CsProj().NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"{FrameworkNet451Arg} \"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + cmd.StdErr.Should().BeEmpty(); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451).Should().Be(condBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(ValidRefCsproj, ConditionFrameworkNet451).Should().Be(1); + } + + [Fact] + public void WhenRefWithoutCondIsPresentItAddsDifferentRefWithoutCond() + { + var lib = NewLib(); + + new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"\"{LibRefPath}\"") + .Should().Pass(); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"\"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore); + csproj.NumberOfProjectReferencesWithIncludeContaining(ValidRefCsproj).Should().Be(1); + } + + [Fact] + public void WhenRefWithCondIsPresentItAddsDifferentRefWithCond() + { + var lib = NewLib(); + + new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"{FrameworkNet451Arg} \"{LibRefPath}\"") + .Should().Pass(); + + int condBefore = lib.CsProj().NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"{FrameworkNet451Arg} \"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451).Should().Be(condBefore); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(ValidRefCsproj, ConditionFrameworkNet451).Should().Be(1); + } + + [Fact] + public void WhenRefWithCondIsPresentItAddsRefWithDifferentCond() + { + var lib = NewLib(); + + new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"{FrameworkNetCoreApp10Arg} \"{ValidRefPath}\"") + .Should().Pass(); + + int condBefore = lib.CsProj().NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"{FrameworkNet451Arg} \"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451).Should().Be(condBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(ValidRefCsproj, ConditionFrameworkNet451).Should().Be(1); + } + + [Fact] + public void WhenRefWithConditionIsPresentItAddsDifferentRefWithoutCond() + { + var lib = NewLib(); + + new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"{FrameworkNet451Arg} \"{LibRefPath}\"") + .Should().Pass(); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"\"{ValidRefPath}\""); + cmd.Should().Pass(); + + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(ValidRefCsproj).Should().Be(1); + } + + [Fact] + public void WhenRefWithNoCondAlreadyExistsItDoesntDuplicate() + { + var lib = NewLib(); + + new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"\"{ValidRefPath}\"") + .Should().Pass(); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"\"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("already has a reference"); + + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore); + csproj.NumberOfProjectReferencesWithIncludeContaining(ValidRefCsproj).Should().Be(1); + } + + [Fact] + public void WhenRefWithCondOnItemAlreadyExistsItDoesntDuplicate() + { + string testRoot = Setup(); + + string projDir = Path.Combine(testRoot, "WithExistingRefCondOnItem"); + string projName = Path.Combine(projDir, "WithExistingRefCondOnItem.csproj"); + string contentBefore = File.ReadAllText(projName); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(projDir) + .WithProject(projName) + .Execute($"{FrameworkNet451Arg} \"{LibRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("already has a reference"); + File.ReadAllText(projName).Should().BeEquivalentTo(contentBefore); + } + + [Fact] + public void WhenRefWithCondOnItemGroupAlreadyExistsItDoesntDuplicate() + { + var lib = NewLib(); + + new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"{FrameworkNet451Arg} \"{ValidRefPath}\"") + .Should().Pass(); + + var csprojContentBefore = lib.CsProjContent(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"{FrameworkNet451Arg} \"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("already has a reference"); + lib.CsProjContent().Should().BeEquivalentTo(csprojContentBefore); + } + + [Fact] + public void WhenRefWithCondWithWhitespaceOnItemGroupExistsItDoesntDuplicate() + { + string testRoot = Setup(); + + string projDir = Path.Combine(testRoot, "WithExistingRefCondWhitespaces"); + string projName = Path.Combine(projDir, "WithExistingRefCondWhitespaces.csproj"); + string contentBefore = File.ReadAllText(projName); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(projDir) + .WithProject(projName) + .Execute($"{FrameworkNet451Arg} \"{LibRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("already has a reference"); + File.ReadAllText(projName).Should().BeEquivalentTo(contentBefore); + } + + [Fact] + public void WhenRefWithoutCondAlreadyExistsInNonUniformItemGroupItDoesntDuplicate() + { + string testRoot = Setup(); + + string projDir = Path.Combine(testRoot, "WithRefNoCondNonUniform"); + string projName = Path.Combine(projDir, "WithRefNoCondNonUniform.csproj"); + string contentBefore = File.ReadAllText(projName); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(projDir) + .WithProject(projName) + .Execute($"\"{LibRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("already has a reference"); + File.ReadAllText(projName).Should().BeEquivalentTo(contentBefore); + } + + [Fact] + public void WhenRefWithoutCondAlreadyExistsInNonUniformItemGroupItAddsDifferentRefInDifferentGroup() + { + string testRoot = Setup(); + + var proj = new ProjDir(Path.Combine(testRoot, "WithRefNoCondNonUniform")); + + int noCondBefore = proj.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(proj.Path) + .WithProject(proj.CsProjPath) + .Execute($"\"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + var csproj = proj.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(ValidRefPath).Should().Be(1); + } + + [Fact] + public void WhenRefWithCondAlreadyExistsInNonUniformItemGroupItDoesntDuplicate() + { + string testRoot = Setup(); + + string projDir = Path.Combine(testRoot, "WithRefCondNonUniform"); + string projName = Path.Combine(projDir, "WithRefCondNonUniform.csproj"); + string contentBefore = File.ReadAllText(projName); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(projDir) + .WithProject(projName) + .Execute($"{FrameworkNet451Arg} \"{LibRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("already has a reference"); + File.ReadAllText(projName).Should().BeEquivalentTo(contentBefore); + } + + [Fact] + public void WhenRefWithCondAlreadyExistsInNonUniformItemGroupItAddsDifferentRefInDifferentGroup() + { + string testRoot = Setup(); + + var proj = new ProjDir(Path.Combine(testRoot, "WithRefCondNonUniform")); + + int condBefore = proj.CsProj().NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(proj.Path) + .WithProject(proj.CsProjPath) + .Execute($"{FrameworkNet451Arg} \"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + var csproj = proj.CsProj(); + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451).Should().Be(condBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(ValidRefPath, ConditionFrameworkNet451).Should().Be(1); + } + + [Fact] + public void WhenEmptyItemGroupPresentItAddsRefInIt() + { + string testRoot = Setup(); + + var proj = new ProjDir(Path.Combine(testRoot, "EmptyItemGroup")); + + int noCondBefore = proj.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(proj.Path) + .WithProject(proj.CsProjPath) + .Execute($"\"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project"); + var csproj = proj.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore); + csproj.NumberOfProjectReferencesWithIncludeContaining(ValidRefPath).Should().Be(1); + } + + [Fact] + public void ItAddsMultipleRefsNoCondToTheSameItemGroup() + { + var lib = NewLib(); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"\"{LibRefPath}\" \"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project").And.NotContain("already has a reference"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(ValidRefCsproj).Should().Be(1); + csproj.NumberOfProjectReferencesWithIncludeContaining(LibRefPath).Should().Be(1); + } + + [Fact] + public void ItAddsMultipleRefsWithCondToTheSameItemGroup() + { + var lib = NewLib(); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451); + var cmd = new AddP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjName) + .Execute($"{FrameworkNet451Arg} \"{LibRefPath}\" \"{ValidRefPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("added to the project").And.NotContain("already has a reference"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451).Should().Be(noCondBefore + 1); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(ValidRefCsproj, ConditionFrameworkNet451).Should().Be(1); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(LibRefPath, ConditionFrameworkNet451).Should().Be(1); + } + + [Fact(Skip = "Not finished")] + public void WhenProjectNameIsNotPassedItFindsItAndAddsReference() + { + throw new NotImplementedException(); + } + + [Fact(Skip = "Not finished")] + public void ItAddsRefBeforeImports() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/dotnet-add-p2p.Tests/ProjDir.cs b/test/dotnet-add-p2p.Tests/ProjDir.cs new file mode 100644 index 000000000..2b568ae78 --- /dev/null +++ b/test/dotnet-add-p2p.Tests/ProjDir.cs @@ -0,0 +1,41 @@ +// 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.IO; +using Microsoft.DotNet.TestFramework; +using Microsoft.Build.Construction; +using Microsoft.Build.Evaluation; + +namespace Microsoft.DotNet.Cli.Add.P2P.Tests +{ + internal class ProjDir + { + public ProjDir(TestAssetsManager tam, [System.Runtime.CompilerServices.CallerMemberName] string callingMethod = nameof(ProjDir), string identifier = "") + { + Path = tam.CreateTestDirectory(callingMethod: callingMethod, identifier: identifier).Path; + Name = new DirectoryInfo(Path).Name; + } + + public ProjDir(string path) + { + Path = path; + Name = new DirectoryInfo(Path).Name; + } + + public string Path { get; private set; } + public string Name { get; private set; } + public string CsProjName => $"{Name}.csproj"; + public string CsProjPath => System.IO.Path.Combine(Path, CsProjName); + + public string CsProjContent() + { + return File.ReadAllText(CsProjPath); + } + + public ProjectRootElement CsProj() + { + // Passing new collection to prevent using cached version + return ProjectRootElement.Open(CsProjPath, new ProjectCollection()); + } + } +} \ No newline at end of file diff --git a/test/dotnet-add-p2p.Tests/dotnet-add-p2p.Tests.csproj b/test/dotnet-add-p2p.Tests/dotnet-add-p2p.Tests.csproj new file mode 100644 index 000000000..338f7cb62 --- /dev/null +++ b/test/dotnet-add-p2p.Tests/dotnet-add-p2p.Tests.csproj @@ -0,0 +1,63 @@ + + + + netcoreapp1.0 + true + dotnet-add-p2p.Tests + $(PackageTargetFallback);dotnet5.4;portable-net451+win8 + $(DefineConstants);DISABLE_TRACE + + + + + src\Microsoft.DotNet.ProjectJsonMigration\MSBuildExtensions.cs + + + + + + + + true + + + true + + + true + + + + + true + + + + + 1.0.0-alpha-20161104-2 + All + + + 15.0.0-preview-20161024-02 + + + 2.2.0-beta4-build1194 + + + 1.0.1 + + + 4.1.1 + + + 2.2.0-beta4-build3444 + + + 15.1.0-preview-000370-00 + + + + $(DefineConstants);RELEASE + + +