diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithDoubledRef/WithDoubledRef.cs b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithDoubledRef/WithDoubledRef.cs new file mode 100644 index 000000000..0ba94d2f9 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithDoubledRef/WithDoubledRef.cs @@ -0,0 +1,3 @@ +public class WithDoubledRef +{ +} \ No newline at end of file diff --git a/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithDoubledRef/WithDoubledRef.csproj b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithDoubledRef/WithDoubledRef.csproj new file mode 100644 index 000000000..a60b1e748 --- /dev/null +++ b/TestAssets/NonRestoredTestProjects/DotnetAddP2PProjects/WithDoubledRef/WithDoubledRef.csproj @@ -0,0 +1,27 @@ + + + + Library + net451;netcoreapp1.0 + + + + + + + + 1.0.0-alpha-20161104-2 + All + + + + + 1.0.1 + + + + + + + + diff --git a/build/test/TestProjects.targets b/build/test/TestProjects.targets index b263c5c9a..60fce21dc 100644 --- a/build/test/TestProjects.targets +++ b/build/test/TestProjects.targets @@ -51,7 +51,8 @@ Include="test$(PathSeparator)ArgumentsReflector$(PathSeparator)ArgumentsReflector.csproj; test$(PathSeparator)Microsoft.DotNet.Tools.Tests.Utilities$(PathSeparator)Microsoft.DotNet.Tools.Tests.Utilities.csproj; test$(PathSeparator)TestingAbstractions$(PathSeparator)TestAppWithFullPdbs$(PathSeparator)TestAppWithFullPdbs.csproj; - test$(PathSeparator)TestingAbstractions$(PathSeparator)TestAppWithPortablePdbs$(PathSeparator)TestAppWithPortablePdbs.csproj" /> + test$(PathSeparator)TestingAbstractions$(PathSeparator)TestAppWithPortablePdbs$(PathSeparator)TestAppWithPortablePdbs.csproj; + test$(PathSeparator)Msbuild.Tests.Utilities$(PathSeparator)Msbuild.Tests.Utilities.csproj" /> diff --git a/src/dotnet/DispatchCommand.cs b/src/dotnet/DispatchCommand.cs new file mode 100644 index 000000000..7878fcfdc --- /dev/null +++ b/src/dotnet/DispatchCommand.cs @@ -0,0 +1,76 @@ +// 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; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.DotNet.Tools +{ + public abstract class DispatchCommand + { + protected abstract string HelpText { get; } + protected abstract Dictionary> BuiltInCommands { get; } + + public int Run(string[] args) + { + DebugHelper.HandleDebugSwitch(ref args); + + if (args.Length == 0 || args[0] == "--help" || args[0] == "-h") + { + Reporter.Output.WriteLine(HelpText); + return 0; + } + + string commandObject; + string command; + if (IsValidCommandName(args[0])) + { + command = args[0]; + commandObject = GetCurrentDirectoryWithDirSeparator(); + args = args.Skip(1).Prepend(commandObject).ToArray(); + } + else if (args.Length == 1) + { + Reporter.Error.WriteLine(string.Format(LocalizableStrings.RequiredArgumentNotPassed, "").Red()); + Reporter.Output.WriteLine(HelpText); + return 1; + } + else + { + commandObject = args[0]; + command = args[1]; + + args = args.Skip(2).Prepend(commandObject).ToArray(); + } + + Func builtin; + if (BuiltInCommands.TryGetValue(command, out builtin)) + { + return builtin(args); + } + + Reporter.Error.WriteLine(string.Format(LocalizableStrings.RequiredArgumentIsInvalid, "").Red()); + Reporter.Output.WriteLine(HelpText); + return 1; + } + + private bool IsValidCommandName(string s) + { + return BuiltInCommands.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/src/dotnet/LocalizableStrings.cs b/src/dotnet/LocalizableStrings.cs index 64ec79d3f..5a7a4f6a8 100644 --- a/src/dotnet/LocalizableStrings.cs +++ b/src/dotnet/LocalizableStrings.cs @@ -22,6 +22,9 @@ namespace Microsoft.DotNet.Tools public const string ProjectAlreadyHasAreference = "Project already has a reference to `{0}`."; public const string ReferenceAddedToTheProject = "Reference `{0}` added to the project."; public const string ReferenceDoesNotExist = "Reference `{0}` does not exist."; - public const string SpecifyAtLeastOneReference = "You must specify at least one reference to add."; + public const string SpecifyAtLeastOneReferenceToAdd = "You must specify at least one reference to add."; + public const string SpecifyAtLeastOneReferenceToRemove = "You must specify at least one reference to remove."; + public const string ProjectReferenceCouldNotBeFound = "Project reference `{0}` could not be found."; + public const string ProjectReferenceRemoved = "Project reference `{0}` removed."; } } diff --git a/src/dotnet/MsbuildProject.cs b/src/dotnet/MsbuildProject.cs new file mode 100644 index 000000000..0d0f06473 --- /dev/null +++ b/src/dotnet/MsbuildProject.cs @@ -0,0 +1,229 @@ +// 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.Utils; +using Microsoft.DotNet.Tools.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.DotNet.Tools +{ + internal class MsbuildProject + { + const string ProjectItemElementType = "ProjectReference"; + + public ProjectRootElement Project { get; private set; } + public string ProjectDirectory { get; private set; } + + private MsbuildProject(ProjectRootElement project) + { + Project = project; + ProjectDirectory = PathUtility.EnsureTrailingSlash(Project.DirectoryPath); + } + + public static MsbuildProject FromFileOrDirectory(string fileOrDirectory) + { + if (File.Exists(fileOrDirectory)) + { + return FromFile(fileOrDirectory); + } + else + { + return FromDirectory(fileOrDirectory); + } + } + + public static MsbuildProject FromFile(string projectPath) + { + if (!File.Exists(projectPath)) + { + throw new GracefulException(LocalizableStrings.ProjectDoesNotExist, projectPath); + } + + var project = TryOpenProject(projectPath); + if (project == null) + { + throw new GracefulException(LocalizableStrings.ProjectIsInvalid, projectPath); + } + + return new MsbuildProject(project); + } + + public static MsbuildProject FromDirectory(string projectDirectory) + { + DirectoryInfo dir; + try + { + dir = new DirectoryInfo(projectDirectory); + } + catch (ArgumentException) + { + throw new GracefulException(LocalizableStrings.CouldNotFindProjectOrDirectory, projectDirectory); + } + + if (!dir.Exists) + { + throw new GracefulException(LocalizableStrings.CouldNotFindProjectOrDirectory, projectDirectory); + } + + FileInfo[] files = dir.GetFiles("*proj"); + if (files.Length == 0) + { + throw new GracefulException(LocalizableStrings.CouldNotFindAnyProjectInDirectory, projectDirectory); + } + + if (files.Length > 1) + { + throw new GracefulException(LocalizableStrings.MoreThanOneProjectInDirectory, projectDirectory); + } + + FileInfo projectFile = files.First(); + + if (!projectFile.Exists) + { + throw new GracefulException(LocalizableStrings.CouldNotFindAnyProjectInDirectory, projectDirectory); + } + + var project = TryOpenProject(projectFile.FullName); + if (project == null) + { + throw new GracefulException(LocalizableStrings.FoundInvalidProject, projectFile.FullName); + } + + return new MsbuildProject(project); + } + + public int AddProjectToProjectReferences(string framework, IEnumerable refs) + { + int numberOfAddedReferences = 0; + + ProjectItemGroupElement itemGroup = Project.FindUniformOrCreateItemGroupWithCondition(ProjectItemElementType, framework); + foreach (var @ref in refs.Select((r) => NormalizeSlashes(r))) + { + if (Project.HasExistingItemWithCondition(framework, @ref)) + { + Reporter.Output.WriteLine(string.Format(LocalizableStrings.ProjectAlreadyHasAreference, @ref)); + continue; + } + + numberOfAddedReferences++; + itemGroup.AppendChild(Project.CreateItemElement(ProjectItemElementType, @ref)); + + Reporter.Output.WriteLine(string.Format(LocalizableStrings.ReferenceAddedToTheProject, @ref)); + } + + return numberOfAddedReferences; + } + + public int RemoveProjectToProjectReferences(string framework, IEnumerable refs) + { + int totalNumberOfRemovedReferences = 0; + + foreach (var @ref in refs) + { + totalNumberOfRemovedReferences += RemoveProjectToProjectReferenceAlternatives(framework, @ref); + } + + return totalNumberOfRemovedReferences; + } + + 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(LocalizableStrings.ReferenceDoesNotExist, r)))); + } + } + + private int RemoveProjectToProjectReferenceAlternatives(string framework, string reference) + { + int numberOfRemovedRefs = 0; + foreach (var r in GetIncludeAlternativesForRemoval(reference)) + { + foreach (var existingItem in Project.FindExistingItemsWithCondition(framework, r)) + { + ProjectElementContainer itemGroup = existingItem.Parent; + itemGroup.RemoveChild(existingItem); + if (itemGroup.Children.Count == 0) + { + itemGroup.Parent.RemoveChild(itemGroup); + } + + numberOfRemovedRefs++; + Reporter.Output.WriteLine(string.Format(LocalizableStrings.ProjectReferenceRemoved, r)); + } + } + + if (numberOfRemovedRefs == 0) + { + Reporter.Output.WriteLine(string.Format(LocalizableStrings.ProjectReferenceCouldNotBeFound, reference)); + } + + return numberOfRemovedRefs; + } + + // Easiest way to explain rationale for this function is on the example. Let's consider following directory structure: + // .../a/b/p.proj + // .../a/d/ref.proj + // .../a/e/f/ + // Project = /some/path/a/b/p.proj + // + // We do not know the format of passed reference so + // path references to consider for removal are following: + // - full path to ref.proj [/some/path/a/d/ref.proj] + // - string which is passed as reference is relative to project [../d/ref.proj] + // - string which is passed as reference is relative to current dir [../../d/ref.proj] + private IEnumerable GetIncludeAlternativesForRemoval(string reference) + { + // We do not care about duplicates in case when i.e. reference is already full path + var ret = new List(); + ret.Add(reference); + + string fullPath = Path.GetFullPath(reference); + ret.Add(fullPath); + ret.Add(PathUtility.GetRelativePath(ProjectDirectory, fullPath)); + + return ret; + } + + // There is ProjectRootElement.TryOpen but it does not work as expected + // I.e. it returns null for some valid projects + private static ProjectRootElement TryOpenProject(string filename) + { + try + { + return ProjectRootElement.Open(filename, new ProjectCollection(), preserveFormatting: true); + } + catch (Microsoft.Build.Exceptions.InvalidProjectFileException) + { + return null; + } + } + } +} diff --git a/src/dotnet/commands/dotnet-add-p2p/MsbuildProjectExtensions.cs b/src/dotnet/MsbuildProjectExtensions.cs similarity index 81% rename from src/dotnet/commands/dotnet-add-p2p/MsbuildProjectExtensions.cs rename to src/dotnet/MsbuildProjectExtensions.cs index 6385bd40a..8abffa674 100644 --- a/src/dotnet/commands/dotnet-add-p2p/MsbuildProjectExtensions.cs +++ b/src/dotnet/MsbuildProjectExtensions.cs @@ -1,9 +1,12 @@ -using Microsoft.Build.Construction; +// 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.DotNet.ProjectJsonMigration; using System.Collections.Generic; using System.Linq; -namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference +namespace Microsoft.DotNet.Tools { internal static class MsbuildProjectExtensions { @@ -66,7 +69,16 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference public static bool HasInclude(this ProjectItemElement el, string include) { - return el.Includes().Contains(include); + include = NormalizeIncludeForComparison(include); + foreach (var i in el.Includes()) + { + if (include == NormalizeIncludeForComparison(i)) + { + return true; + } + } + + return false; } private static bool TryGetFrameworkConditionString(string framework, out string condition) @@ -80,5 +92,10 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference condition = $"'$(TargetFramework)' == '{framework}'"; return true; } + + private static string NormalizeIncludeForComparison(string include) + { + return MsbuildProject.NormalizeSlashes(include.ToLower()); + } } } diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index bd802401e..4072ab395 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -18,6 +18,7 @@ using Microsoft.DotNet.Tools.New; using Microsoft.DotNet.Tools.NuGet; using Microsoft.DotNet.Tools.Pack; using Microsoft.DotNet.Tools.Publish; +using Microsoft.DotNet.Tools.Remove; using Microsoft.DotNet.Tools.Restore; using Microsoft.DotNet.Tools.RestoreProjectJson; using Microsoft.DotNet.Tools.Run; @@ -31,7 +32,7 @@ namespace Microsoft.DotNet.Cli { private static Dictionary> s_builtIns = new Dictionary> { - ["add"] = AddCommand.Run, + ["add"] = (new AddCommand()).Run, ["build"] = BuildCommand.Run, ["clean"] = CleanCommand.Run, ["help"] = HelpCommand.Run, @@ -41,6 +42,7 @@ namespace Microsoft.DotNet.Cli ["nuget"] = NuGetCommand.Run, ["pack"] = PackCommand.Run, ["publish"] = PublishCommand.Run, + ["remove"] = (new RemoveCommand()).Run, ["restore"] = RestoreCommand.Run, ["restore-projectjson"] = RestoreProjectJsonCommand.Run, ["run"] = RunCommand.Run, diff --git a/src/dotnet/commands/dotnet-add-p2p/Program.cs b/src/dotnet/commands/dotnet-add-p2p/Program.cs index 6a724620d..c6ada41cc 100644 --- a/src/dotnet/commands/dotnet-add-p2p/Program.cs +++ b/src/dotnet/commands/dotnet-add-p2p/Program.cs @@ -1,15 +1,9 @@ // 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.Collections.Generic; using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Utils; -using System; -using System.IO; -using System.Linq; -using Microsoft.Build.Construction; -using Microsoft.Build.Evaluation; -using Microsoft.DotNet.Tools.Common; +using System.Collections.Generic; namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference { @@ -52,41 +46,27 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference throw new GracefulException(LocalizableStrings.RequiredArgumentNotPassed, ""); } - ProjectRootElement project; - string projectDir; - if (File.Exists(projectArgument.Value)) - { - project = GetProjectFromFileOrThrow(projectArgument.Value); - projectDir = new FileInfo(projectArgument.Value).DirectoryName; - } - else - { - project = GetProjectFromDirectoryOrThrow(projectArgument.Value); - projectDir = projectArgument.Value; - } - - projectDir = PathUtility.EnsureTrailingSlash(projectDir); + var msbuildProj = MsbuildProject.FromFileOrDirectory(projectArgument.Value); if (app.RemainingArguments.Count == 0) { - throw new GracefulException(LocalizableStrings.SpecifyAtLeastOneReference); + throw new GracefulException(LocalizableStrings.SpecifyAtLeastOneReferenceToAdd); } List references = app.RemainingArguments; if (!forceOption.HasValue()) { - EnsureAllReferencesExist(references); - ConvertPathsToRelative(projectDir, ref references); + MsbuildProject.EnsureAllReferencesExist(references); + msbuildProj.ConvertPathsToRelative(ref references); } - int numberOfAddedReferences = AddProjectToProjectReference( - project, + int numberOfAddedReferences = msbuildProj.AddProjectToProjectReferences( frameworkOption.Value(), references); if (numberOfAddedReferences != 0) { - project.Save(); + msbuildProj.Project.Save(); } return 0; @@ -103,133 +83,5 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference return 1; } } - - internal 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(LocalizableStrings.ReferenceDoesNotExist, r)))); - } - } - - internal static void ConvertPathsToRelative(string root, ref List references) - { - root = PathUtility.EnsureTrailingSlash(Path.GetFullPath(root)); - references = references.Select((r) => PathUtility.GetRelativePath(root, Path.GetFullPath(r))).ToList(); - } - - // There is ProjectRootElement.TryOpen but it does not work as expected - // I.e. it returns null for some valid projects - internal static ProjectRootElement TryOpenProject(string filename) - { - try - { - return ProjectRootElement.Open(filename, new ProjectCollection(), preserveFormatting: true); - } - catch (Microsoft.Build.Exceptions.InvalidProjectFileException) - { - return null; - } - } - - internal static ProjectRootElement GetProjectFromFileOrThrow(string filename) - { - if (!File.Exists(filename)) - { - throw new GracefulException(LocalizableStrings.ProjectDoesNotExist, filename); - } - - var project = TryOpenProject(filename); - if (project == null) - { - throw new GracefulException(LocalizableStrings.ProjectIsInvalid, filename); - } - - return project; - } - - internal static ProjectRootElement GetProjectFromDirectoryOrThrow(string directory) - { - DirectoryInfo dir; - try - { - dir = new DirectoryInfo(directory); - } - catch (ArgumentException) - { - throw new GracefulException(LocalizableStrings.CouldNotFindProjectOrDirectory, directory); - } - - if (!dir.Exists) - { - throw new GracefulException(LocalizableStrings.CouldNotFindProjectOrDirectory, directory); - } - - FileInfo[] files = dir.GetFiles("*proj"); - if (files.Length == 0) - { - throw new GracefulException(LocalizableStrings.CouldNotFindAnyProjectInDirectory, directory); - } - - if (files.Length > 1) - { - throw new GracefulException(LocalizableStrings.MoreThanOneProjectInDirectory, directory); - } - - FileInfo projectFile = files.First(); - - if (!projectFile.Exists) - { - throw new GracefulException(LocalizableStrings.CouldNotFindAnyProjectInDirectory, directory); - } - - var ret = TryOpenProject(projectFile.FullName); - if (ret == null) - { - throw new GracefulException(LocalizableStrings.FoundInvalidProject, projectFile.FullName); - } - - return ret; - } - - private static string NormalizeSlashesForMsbuild(string path) - { - return path.Replace('/', '\\'); - } - - internal static int AddProjectToProjectReference(ProjectRootElement root, string framework, IEnumerable refs) - { - int numberOfAddedReferences = 0; - const string ProjectItemElementType = "ProjectReference"; - - ProjectItemGroupElement itemGroup = root.FindUniformOrCreateItemGroupWithCondition(ProjectItemElementType, framework); - foreach (var @ref in refs.Select((r) => NormalizeSlashesForMsbuild(r))) - { - if (root.HasExistingItemWithCondition(framework, @ref)) - { - Reporter.Output.WriteLine(string.Format(LocalizableStrings.ProjectAlreadyHasAreference, @ref)); - continue; - } - - numberOfAddedReferences++; - itemGroup.AppendChild(root.CreateItemElement(ProjectItemElementType, @ref)); - - Reporter.Output.WriteLine(string.Format(LocalizableStrings.ReferenceAddedToTheProject, @ref)); - } - - return numberOfAddedReferences; - } } } diff --git a/src/dotnet/commands/dotnet-add/Program.cs b/src/dotnet/commands/dotnet-add/Program.cs index 78cb27b6b..d8afe2593 100644 --- a/src/dotnet/commands/dotnet-add/Program.cs +++ b/src/dotnet/commands/dotnet-add/Program.cs @@ -14,9 +14,9 @@ using Microsoft.DotNet.Tools.Add.ProjectToProjectReference; namespace Microsoft.DotNet.Tools.Add { - public class AddCommand + public class AddCommand : DispatchCommand { - public const string HelpText = @".NET Add Command + protected override string HelpText => @".NET Add Command Usage: dotnet add [options] [[--] ...]] @@ -33,68 +33,9 @@ Args: Commands: p2p Add project to project (p2p) reference to a project"; - private static Dictionary> s_builtIns = new Dictionary> + protected override Dictionary> BuiltInCommands => new Dictionary> { ["p2p"] = AddProjectToProjectReferenceCommand.Run, }; - - public static int Run(string[] args) - { - DebugHelper.HandleDebugSwitch(ref args); - - if (args.Length == 0 || args[0] == "--help" || args[0] == "-h") - { - Reporter.Output.WriteLine(HelpText); - return 0; - } - - string commandObject; - string command; - if (IsValidCommandName(args[0])) - { - command = args[0]; - commandObject = GetCurrentDirectoryWithDirSeparator(); - args = args.Skip(1).Prepend(commandObject).ToArray(); - } - else if (args.Length == 1) - { - Reporter.Error.WriteLine(string.Format(LocalizableStrings.RequiredArgumentNotPassed, "").Red()); - Reporter.Output.WriteLine(HelpText); - return 1; - } - else - { - 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(string.Format(LocalizableStrings.RequiredArgumentIsInvalid, "").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/src/dotnet/commands/dotnet-help/HelpCommand.cs b/src/dotnet/commands/dotnet-help/HelpCommand.cs index 5dadb6496..728c2da65 100644 --- a/src/dotnet/commands/dotnet-help/HelpCommand.cs +++ b/src/dotnet/commands/dotnet-help/HelpCommand.cs @@ -37,6 +37,7 @@ Commands: Project modification commands: add Add items to the project + remove Remove items from the project Advanced Commands: nuget Provides additional NuGet commands diff --git a/src/dotnet/commands/dotnet-remove-p2p/Program.cs b/src/dotnet/commands/dotnet-remove-p2p/Program.cs new file mode 100644 index 000000000..6e55512d8 --- /dev/null +++ b/src/dotnet/commands/dotnet-remove-p2p/Program.cs @@ -0,0 +1,77 @@ +// 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.CommandLine; +using Microsoft.DotNet.Cli.Utils; +using System.Collections.Generic; + +namespace Microsoft.DotNet.Tools.Remove.ProjectToProjectReference +{ + public class RemoveProjectToProjectReferenceCommand + { + public static int Run(string[] args) + { + DebugHelper.HandleDebugSwitch(ref args); + + CommandLineApplication app = new CommandLineApplication(throwOnUnexpectedArg: false) + { + Name = "dotnet remove p2p", + FullName = ".NET Remove Project to Project (p2p) reference Command", + Description = "Command to remove project to project (p2p) reference", + AllowArgumentSeparator = true, + ArgumentSeparatorHelpText = "Project to project references to remove" + }; + + app.HelpOption("-h|--help"); + + CommandArgument projectArgument = app.Argument( + "", + "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 ", + "Remove reference only when targetting a specific framework", + CommandOptionType.SingleValue); + + app.OnExecute(() => { + if (string.IsNullOrEmpty(projectArgument.Value)) + { + throw new GracefulException(LocalizableStrings.RequiredArgumentNotPassed, ""); + } + + var msbuildProj = MsbuildProject.FromFileOrDirectory(projectArgument.Value); + + if (app.RemainingArguments.Count == 0) + { + throw new GracefulException(LocalizableStrings.SpecifyAtLeastOneReferenceToRemove); + } + + List references = app.RemainingArguments; + + int numberOfRemovedReferences = msbuildProj.RemoveProjectToProjectReferences( + frameworkOption.Value(), + references); + + if (numberOfRemovedReferences != 0) + { + msbuildProj.Project.Save(); + } + + return 0; + }); + + try + { + return app.Execute(args); + } + catch (GracefulException e) + { + Reporter.Error.WriteLine(e.Message.Red()); + app.ShowHelp(); + return 1; + } + } + } +} diff --git a/src/dotnet/commands/dotnet-remove/Program.cs b/src/dotnet/commands/dotnet-remove/Program.cs new file mode 100644 index 000000000..25cf64678 --- /dev/null +++ b/src/dotnet/commands/dotnet-remove/Program.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.Collections.Generic; +using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.DotNet.Cli.Utils; +using System; +using System.IO; +using System.Linq; +using Microsoft.Build.Construction; +using Microsoft.DotNet.ProjectJsonMigration; +using NuGet.Frameworks; +using Microsoft.DotNet.Tools.Remove.ProjectToProjectReference; + +namespace Microsoft.DotNet.Tools.Remove +{ + public class RemoveCommand : DispatchCommand + { + protected override string HelpText => @".NET Remove Command; + +Usage: dotnet remove [options] [[--] ...]] + +Options: + -h|--help Show help information + +Arguments: + The object of the operation. If a project file is not specified, it defaults to the current directory. + Command to be executed on . + +Args: + Any extra arguments passed to the command. Use `dotnet add --help` to get help about these arguments. + +Commands: + p2p Remove project to project (p2p) reference from a project"; + + protected override Dictionary> BuiltInCommands => new Dictionary> + { + ["p2p"] = RemoveProjectToProjectReferenceCommand.Run, + }; + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/RemoveP2PCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/RemoveP2PCommand.cs new file mode 100644 index 000000000..b28c96c62 --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/RemoveP2PCommand.cs @@ -0,0 +1,29 @@ +// 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 RemoveP2PCommand : TestCommand + { + private string _projectName = null; + + public RemoveP2PCommand() + : base("dotnet") + { + } + + public override CommandResult Execute(string args = "") + { + args = $"remove {_projectName} p2p {args}"; + return base.ExecuteWithCapturedOutput(args); + } + + public RemoveP2PCommand WithProject(string projectName) + { + _projectName = projectName; + return this; + } + } +} diff --git a/test/Msbuild.Tests.Utilities/Msbuild.Tests.Utilities.csproj b/test/Msbuild.Tests.Utilities/Msbuild.Tests.Utilities.csproj new file mode 100644 index 000000000..e6861cc32 --- /dev/null +++ b/test/Msbuild.Tests.Utilities/Msbuild.Tests.Utilities.csproj @@ -0,0 +1,60 @@ + + + + netcoreapp1.0 + true + Msbuild.Tests.Utilities + $(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 + + + 1.6.0 + + + 4.1.1 + + + 4.0.0 + + + 2.2.0-beta4-build3444 + + + 15.1.0-preview-000370-00 + + + + $(DefineConstants);RELEASE + + + diff --git a/test/dotnet-add-p2p.Tests/ProjDir.cs b/test/Msbuild.Tests.Utilities/ProjDir.cs similarity index 93% rename from test/dotnet-add-p2p.Tests/ProjDir.cs rename to test/Msbuild.Tests.Utilities/ProjDir.cs index 2f449d630..b29adae8b 100644 --- a/test/dotnet-add-p2p.Tests/ProjDir.cs +++ b/test/Msbuild.Tests.Utilities/ProjDir.cs @@ -5,9 +5,9 @@ using System.IO; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; -namespace Microsoft.DotNet.Cli.Add.P2P.Tests +namespace Msbuild.Tests.Utilities { - internal class ProjDir + public class ProjDir { public ProjDir(string path) { diff --git a/test/dotnet-add-p2p.Tests/ProjectRootElementExtensions.cs b/test/Msbuild.Tests.Utilities/ProjectRootElementExtensions.cs similarity index 92% rename from test/dotnet-add-p2p.Tests/ProjectRootElementExtensions.cs rename to test/Msbuild.Tests.Utilities/ProjectRootElementExtensions.cs index e53609c73..bd960bb92 100644 --- a/test/dotnet-add-p2p.Tests/ProjectRootElementExtensions.cs +++ b/test/Msbuild.Tests.Utilities/ProjectRootElementExtensions.cs @@ -6,9 +6,9 @@ using Microsoft.DotNet.ProjectJsonMigration; using System.Linq; using System.Collections.Generic; -namespace Microsoft.DotNet.Cli.Add.P2P.Tests +namespace Msbuild.Tests.Utilities { - internal static class ProjectRootElementExtensions + public static class ProjectRootElementExtensions { public static int NumberOfItemGroupsWithConditionContaining( this ProjectRootElement root, @@ -57,7 +57,8 @@ namespace Microsoft.DotNet.Cli.Add.P2P.Tests string itemType, string includePattern) { - return root.Items.Where((it) => it.ItemType == itemType && it.Include.Contains(includePattern)); + return root.Items.Where((it) => it.ItemType == itemType && it.Include.Contains(includePattern) + && it.ConditionChain().Count() == 0); } public static int NumberOfProjectReferencesWithIncludeContaining( diff --git a/test/dotnet-add-p2p.Tests/TestSetup.cs b/test/Msbuild.Tests.Utilities/TestSetup.cs similarity index 90% rename from test/dotnet-add-p2p.Tests/TestSetup.cs rename to test/Msbuild.Tests.Utilities/TestSetup.cs index 7fe1e3cf8..eaf2c7d79 100644 --- a/test/dotnet-add-p2p.Tests/TestSetup.cs +++ b/test/Msbuild.Tests.Utilities/TestSetup.cs @@ -3,9 +3,9 @@ using System.IO; -namespace Microsoft.DotNet.Cli.Add.P2P.Tests +namespace Msbuild.Tests.Utilities { - internal class TestSetup + public class TestSetup { public const string TestGroup = "NonRestoredTestProjects"; public const string ProjectName = "DotnetAddP2PProjects"; @@ -13,6 +13,7 @@ namespace Microsoft.DotNet.Cli.Add.P2P.Tests public string TestRoot { get; private set; } private const string ValidRef = "ValidRef"; + public string ValidRefDir => Path.Combine(TestRoot, ValidRef); public string ValidRefCsprojName => $"{ValidRef}.csproj"; public string ValidRefCsprojRelPath => Path.Combine(ValidRef, ValidRefCsprojName); public string ValidRefCsprojPath => Path.Combine(TestRoot, ValidRefCsprojRelPath); diff --git a/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs b/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs index 862a8483b..bffc256dc 100644 --- a/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs +++ b/test/dotnet-add-p2p.Tests/GivenDotnetAddP2P.cs @@ -4,6 +4,7 @@ using FluentAssertions; using Microsoft.Build.Construction; using Microsoft.DotNet.Tools.Test.Utilities; +using Msbuild.Tests.Utilities; using System; using System.IO; using Xunit; diff --git a/test/dotnet-add-p2p.Tests/dotnet-add-p2p.Tests.csproj b/test/dotnet-add-p2p.Tests/dotnet-add-p2p.Tests.csproj index 338f7cb62..a119359ac 100644 --- a/test/dotnet-add-p2p.Tests/dotnet-add-p2p.Tests.csproj +++ b/test/dotnet-add-p2p.Tests/dotnet-add-p2p.Tests.csproj @@ -26,6 +26,7 @@ true + diff --git a/test/dotnet-remove-p2p.Tests/GivenDotnetRemoveP2P.cs b/test/dotnet-remove-p2p.Tests/GivenDotnetRemoveP2P.cs new file mode 100644 index 000000000..7a3086cdd --- /dev/null +++ b/test/dotnet-remove-p2p.Tests/GivenDotnetRemoveP2P.cs @@ -0,0 +1,438 @@ +// 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.Tools.Test.Utilities; +using Msbuild.Tests.Utilities; +using System; +using System.IO; +using Xunit; + +namespace Microsoft.DotNet.Cli.Remove.P2P.Tests +{ + public class GivenDotnetRemoveP2P : TestBase + { + const string FrameworkNet451Arg = "-f net451"; + const string ConditionFrameworkNet451 = "== 'net451'"; + const string FrameworkNetCoreApp10Arg = "-f netcoreapp1.0"; + const string ConditionFrameworkNetCoreApp10 = "== 'netcoreapp1.0'"; + + private TestSetup Setup([System.Runtime.CompilerServices.CallerMemberName] string callingMethod = nameof(Setup), string identifier = "") + { + return new TestSetup( + TestAssets.Get(TestSetup.TestGroup, TestSetup.ProjectName) + .CreateInstance(callingMethod: callingMethod, identifier: identifier) + .WithSourceFiles() + .Root + .FullName); + } + + private ProjDir NewDir([System.Runtime.CompilerServices.CallerMemberName] string callingMethod = nameof(NewDir), string identifier = "") + { + return new ProjDir(TestAssetsManager.CreateTestDirectory(callingMethod: callingMethod, identifier: identifier).Path); + } + + private ProjDir NewLib([System.Runtime.CompilerServices.CallerMemberName] string callingMethod = nameof(NewDir), string identifier = "") + { + var dir = NewDir(callingMethod: callingMethod, identifier: identifier); + + try + { + new NewCommand() + .WithWorkingDirectory(dir.Path) + .ExecuteWithCapturedOutput("-t Lib") + .Should().Pass(); + } + catch (System.ComponentModel.Win32Exception e) + { + throw new Exception($"Intermittent error in `dotnet new` occurred when running it in dir `{dir.Path}`\nException:\n{e}"); + } + + return dir; + } + + private ProjDir GetLibRef(TestSetup setup) + { + return new ProjDir(setup.LibDir); + } + + private ProjDir AddLibRef(TestSetup setup, ProjDir proj, string additionalArgs = "") + { + var ret = GetLibRef(setup); + new AddP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(proj.CsProjPath) + .Execute($"{additionalArgs} \"{ret.CsProjPath}\"") + .Should().Pass(); + + return ret; + } + + private ProjDir AddValidRef(TestSetup setup, ProjDir proj, string frameworkArg = "") + { + var ret = new ProjDir(setup.ValidRefDir); + new AddP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(proj.CsProjPath) + .Execute($"{frameworkArg} \"{ret.CsProjPath}\"") + .Should().Pass(); + + return ret; + } + + [Theory] + [InlineData("--help")] + [InlineData("-h")] + public void WhenHelpOptionIsPassedItPrintsUsage(string helpArg) + { + var cmd = new RemoveP2PCommand().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) + { + var setup = Setup(); + + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(projName) + .Execute($"\"{setup.ValidRefCsprojPath}\""); + 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"; + var setup = Setup(); + + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(projName) + .Execute($"\"{setup.ValidRefCsprojPath}\""); + cmd.ExitCode.Should().NotBe(0); + cmd.StdErr.Should().Contain("Invalid project"); + cmd.StdOut.Should().Contain("Usage"); + } + + [Fact] + public void WhenMoreThanOneProjectExistsInTheDirectoryItPrintsErrorAndUsage() + { + var setup = Setup(); + + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(Path.Combine(setup.TestRoot, "MoreThanOne")) + .Execute($"\"{setup.ValidRefCsprojRelToOtherProjPath}\""); + cmd.ExitCode.Should().NotBe(0); + cmd.StdErr.Should().Contain("more than one"); + cmd.StdOut.Should().Contain("Usage"); + } + + [Fact] + public void WhenNoProjectsExistsInTheDirectoryItPrintsErrorAndUsage() + { + var setup = Setup(); + + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .Execute($"\"{setup.ValidRefCsprojPath}\""); + cmd.ExitCode.Should().NotBe(0); + cmd.StdErr.Should().Contain("not find any"); + cmd.StdOut.Should().Contain("Usage"); + } + + [Fact] + public void ItRemovesRefWithoutCondAndPrintsStatus() + { + var lib = NewLib(); + var setup = Setup(); + var libref = AddLibRef(setup, lib); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"\"{libref.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(libref.Name).Should().Be(0); + } + + [Fact] + public void ItRemovesRefWithCondAndPrintsStatus() + { + var lib = NewLib(); + var setup = Setup(); + var libref = AddLibRef(setup, lib, FrameworkNet451Arg); + + int condBefore = lib.CsProj().NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"{FrameworkNet451Arg} \"{libref.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451).Should().Be(condBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(libref.Name, ConditionFrameworkNet451).Should().Be(0); + } + + [Fact] + public void WhenTwoDifferentRefsArePresentItDoesNotRemoveBoth() + { + var lib = NewLib(); + var setup = Setup(); + var libref = AddLibRef(setup, lib); + var validref = AddValidRef(setup, lib); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"\"{libref.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + cmd.StdOut.Should().NotContain(validref.Name); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore); + csproj.NumberOfProjectReferencesWithIncludeContaining(libref.Name).Should().Be(0); + } + + [Fact] + public void WhenRefWithoutCondIsNotThereItPrintsMessage() + { + var lib = NewLib(); + var setup = Setup(); + var libref = GetLibRef(setup); + + string csprojContetntBefore = lib.CsProjContent(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"\"{libref.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("could not be found"); + lib.CsProjContent().Should().BeEquivalentTo(csprojContetntBefore); + } + + [Fact] + public void WhenRefWithCondIsNotThereItPrintsMessage() + { + var lib = NewLib(); + var setup = Setup(); + var libref = GetLibRef(setup); + + string csprojContetntBefore = lib.CsProjContent(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"{FrameworkNet451Arg} \"{libref.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().Contain("could not be found"); + lib.CsProjContent().Should().BeEquivalentTo(csprojContetntBefore); + } + + [Fact] + public void WhenRefWithAndWithoutCondArePresentAndRemovingNoCondItDoesNotRemoveOther() + { + var lib = NewLib(); + var setup = Setup(); + var librefCond = AddLibRef(setup, lib, FrameworkNet451Arg); + var librefNoCond = AddLibRef(setup, lib); + + var csprojBefore = lib.CsProj(); + int noCondBefore = csprojBefore.NumberOfItemGroupsWithoutCondition(); + int condBefore = csprojBefore.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"\"{librefNoCond.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(librefNoCond.Name).Should().Be(0); + + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451).Should().Be(condBefore); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(librefCond.Name, ConditionFrameworkNet451).Should().Be(1); + } + + [Fact] + public void WhenRefWithAndWithoutCondArePresentAndRemovingCondItDoesNotRemoveOther() + { + var lib = NewLib(); + var setup = Setup(); + var librefCond = AddLibRef(setup, lib, FrameworkNet451Arg); + var librefNoCond = AddLibRef(setup, lib); + + var csprojBefore = lib.CsProj(); + int noCondBefore = csprojBefore.NumberOfItemGroupsWithoutCondition(); + int condBefore = csprojBefore.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"{FrameworkNet451Arg} \"{librefCond.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore); + csproj.NumberOfProjectReferencesWithIncludeContaining(librefNoCond.Name).Should().Be(1); + + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451).Should().Be(condBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(librefCond.Name, ConditionFrameworkNet451).Should().Be(0); + } + + [Fact] + public void WhenRefWithDifferentCondIsPresentItDoesNotRemoveIt() + { + var lib = NewLib(); + var setup = Setup(); + var librefCondNet451 = AddLibRef(setup, lib, FrameworkNet451Arg); + var librefCondNetCoreApp10 = AddLibRef(setup, lib, FrameworkNetCoreApp10Arg); + + var csprojBefore = lib.CsProj(); + int condNet451Before = csprojBefore.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451); + int condNetCoreApp10Before = csprojBefore.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNetCoreApp10); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"{FrameworkNet451Arg} \"{librefCondNet451.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNet451).Should().Be(condNet451Before - 1); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(librefCondNet451.Name, ConditionFrameworkNet451).Should().Be(0); + + csproj.NumberOfItemGroupsWithConditionContaining(ConditionFrameworkNetCoreApp10).Should().Be(condNetCoreApp10Before); + csproj.NumberOfProjectReferencesWithIncludeAndConditionContaining(librefCondNetCoreApp10.Name, ConditionFrameworkNetCoreApp10).Should().Be(1); + } + + [Fact] + public void WhenDuplicateReferencesArePresentItRemovesThemAll() + { + var setup = Setup(); + var proj = new ProjDir(Path.Combine(setup.TestRoot, "WithDoubledRef")); + var libref = GetLibRef(setup); + + int noCondBefore = proj.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(proj.CsProjPath) + .Execute($"\"{libref.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + + var csproj = proj.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(libref.Name).Should().Be(0); + } + + [Fact] + public void WhenPassingRefWithRelPathItRemovesRefWithAbsolutePath() + { + var setup = Setup(); + var lib = GetLibRef(setup); + var libref = AddValidRef(setup, lib, "--force"); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(lib.Path) + .WithProject(lib.CsProjPath) + .Execute($"\"{setup.ValidRefCsprojRelToOtherProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(libref.Name).Should().Be(0); + } + + [Fact] + public void WhenPassingRefWithRelPathToProjectItRemovesRefWithPathRelToProject() + { + var setup = Setup(); + var lib = GetLibRef(setup); + var libref = AddValidRef(setup, lib); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"\"{setup.ValidRefCsprojRelToOtherProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(libref.Name).Should().Be(0); + } + + [Fact] + public void WhenPassingRefWithAbsolutePathItRemovesRefWithRelPath() + { + var setup = Setup(); + var lib = GetLibRef(setup); + var libref = AddValidRef(setup, lib); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"\"{setup.ValidRefCsprojPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(libref.Name).Should().Be(0); + } + + [Fact] + public void WhenPassingMultipleReferencesItRemovesThemAll() + { + var lib = NewLib(); + var setup = Setup(); + var libref = AddLibRef(setup, lib); + var validref = AddValidRef(setup, lib); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"\"{libref.CsProjPath}\" \"{validref.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(libref.Name).Should().Be(0); + csproj.NumberOfProjectReferencesWithIncludeContaining(validref.Name).Should().Be(0); + } + + [Fact] + public void WhenPassingMultipleReferencesAndOneOfThemDoesNotExistItRemovesOne() + { + var lib = NewLib(); + var setup = Setup(); + var libref = GetLibRef(setup); + var validref = AddValidRef(setup, lib); + + int noCondBefore = lib.CsProj().NumberOfItemGroupsWithoutCondition(); + var cmd = new RemoveP2PCommand() + .WithWorkingDirectory(setup.TestRoot) + .WithProject(lib.CsProjPath) + .Execute($"\"{libref.CsProjPath}\" \"{validref.CsProjPath}\""); + cmd.Should().Pass(); + cmd.StdOut.Should().MatchRegex("(^|[\r\n])Project reference[^\r\n]*removed.($|[\r\n])"); + cmd.StdOut.Should().Contain("could not be found"); + var csproj = lib.CsProj(); + csproj.NumberOfItemGroupsWithoutCondition().Should().Be(noCondBefore - 1); + csproj.NumberOfProjectReferencesWithIncludeContaining(validref.Name).Should().Be(0); + } + } +} diff --git a/test/dotnet-remove-p2p.Tests/MSBuild.exe b/test/dotnet-remove-p2p.Tests/MSBuild.exe new file mode 100644 index 000000000..2b4d0f999 --- /dev/null +++ b/test/dotnet-remove-p2p.Tests/MSBuild.exe @@ -0,0 +1 @@ +https://github.com/Microsoft/msbuild/issues/927 \ No newline at end of file diff --git a/test/dotnet-remove-p2p.Tests/MSBuild.exe.config b/test/dotnet-remove-p2p.Tests/MSBuild.exe.config new file mode 100644 index 000000000..2b4d0f999 --- /dev/null +++ b/test/dotnet-remove-p2p.Tests/MSBuild.exe.config @@ -0,0 +1 @@ +https://github.com/Microsoft/msbuild/issues/927 \ No newline at end of file diff --git a/test/dotnet-remove-p2p.Tests/dotnet-remove-p2p.Tests.csproj b/test/dotnet-remove-p2p.Tests/dotnet-remove-p2p.Tests.csproj new file mode 100644 index 000000000..5cd354df0 --- /dev/null +++ b/test/dotnet-remove-p2p.Tests/dotnet-remove-p2p.Tests.csproj @@ -0,0 +1,67 @@ + + + + netcoreapp1.0 + true + dotnet-remove-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 + + + 4.0.0 + + + + $(DefineConstants);RELEASE + + +