reorg of code so that sharing is easier + stub for dotnet-remove-p2p

This commit is contained in:
Krzysztof Wicher 2016-11-29 09:44:39 -08:00
parent f49e29711f
commit 1edda43dd1
11 changed files with 380 additions and 212 deletions

View file

@ -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<string, Func<string[], int>> 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, "<command>").Red());
Reporter.Output.WriteLine(HelpText);
return 1;
}
else
{
commandObject = args[0];
command = args[1];
args = args.Skip(2).Prepend(commandObject).ToArray();
}
Func<string[], int> builtin;
if (BuiltInCommands.TryGetValue(command, out builtin))
{
return builtin(args);
}
Reporter.Error.WriteLine(string.Format(LocalizableStrings.RequiredArgumentIsInvalid, "<command>").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;
}
}
}

View file

@ -22,6 +22,7 @@ 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.";
}
}

View file

@ -3,7 +3,7 @@ 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
{

150
src/dotnet/P2PHelpers.cs Normal file
View file

@ -0,0 +1,150 @@
// 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 static class P2PHelpers
{
public static void EnsureAllReferencesExist(List<string> references)
{
var notExisting = new List<string>();
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))));
}
}
public static void ConvertPathsToRelative(string root, ref List<string> 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
public static ProjectRootElement TryOpenProject(string filename)
{
try
{
return ProjectRootElement.Open(filename, new ProjectCollection(), preserveFormatting: true);
}
catch (Microsoft.Build.Exceptions.InvalidProjectFileException)
{
return null;
}
}
public 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;
}
public 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;
}
public static string NormalizeSlashesForMsbuild(string path)
{
return path.Replace('/', '\\');
}
public static int AddProjectToProjectReference(ProjectRootElement root, string framework, IEnumerable<string> 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;
}
public static int RemoveProjectToProjectReference(ProjectRootElement root, string framework, IEnumerable<string> refs)
{
throw new NotImplementedException();
}
}
}

View file

@ -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<string, Func<string[], int>> s_builtIns = new Dictionary<string, Func<string[], int>>
{
["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,

View file

@ -1,15 +1,12 @@
// 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;
using System.IO;
namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference
{
@ -56,12 +53,12 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference
string projectDir;
if (File.Exists(projectArgument.Value))
{
project = GetProjectFromFileOrThrow(projectArgument.Value);
project = P2PHelpers.GetProjectFromFileOrThrow(projectArgument.Value);
projectDir = new FileInfo(projectArgument.Value).DirectoryName;
}
else
{
project = GetProjectFromDirectoryOrThrow(projectArgument.Value);
project = P2PHelpers.GetProjectFromDirectoryOrThrow(projectArgument.Value);
projectDir = projectArgument.Value;
}
@ -69,17 +66,17 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference
if (app.RemainingArguments.Count == 0)
{
throw new GracefulException(LocalizableStrings.SpecifyAtLeastOneReference);
throw new GracefulException(LocalizableStrings.SpecifyAtLeastOneReferenceToAdd);
}
List<string> references = app.RemainingArguments;
if (!forceOption.HasValue())
{
EnsureAllReferencesExist(references);
ConvertPathsToRelative(projectDir, ref references);
P2PHelpers.EnsureAllReferencesExist(references);
P2PHelpers.ConvertPathsToRelative(projectDir, ref references);
}
int numberOfAddedReferences = AddProjectToProjectReference(
int numberOfAddedReferences = P2PHelpers.AddProjectToProjectReference(
project,
frameworkOption.Value(),
references);
@ -103,133 +100,5 @@ namespace Microsoft.DotNet.Tools.Add.ProjectToProjectReference
return 1;
}
}
internal static void EnsureAllReferencesExist(List<string> references)
{
var notExisting = new List<string>();
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<string> 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<string> 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;
}
}
}

View file

@ -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] <object> <command> [[--] <arg>...]]
@ -33,68 +33,9 @@ Args:
Commands:
p2p Add project to project (p2p) reference to a project";
private static Dictionary<string, Func<string[], int>> s_builtIns = new Dictionary<string, Func<string[], int>>
protected override Dictionary<string, Func<string[], int>> BuiltInCommands => new Dictionary<string, Func<string[], int>>
{
["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, "<command>").Red());
Reporter.Output.WriteLine(HelpText);
return 1;
}
else
{
commandObject = args[0];
command = args[1];
args = args.Skip(2).Prepend(commandObject).ToArray();
}
Func<string[], int> builtin;
if (s_builtIns.TryGetValue(command, out builtin))
{
return builtin(args);
}
Reporter.Error.WriteLine(string.Format(LocalizableStrings.RequiredArgumentIsInvalid, "<command>").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;
}
}
}

View file

@ -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

View file

@ -0,0 +1,94 @@
// 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 Microsoft.Build.Construction;
using Microsoft.DotNet.Tools.Common;
using System.Collections.Generic;
using System.IO;
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(
"<PROJECT>",
"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 <FRAMEWORK>",
"Remove reference only when targetting a specific framework",
CommandOptionType.SingleValue);
app.OnExecute(() => {
if (string.IsNullOrEmpty(projectArgument.Value))
{
throw new GracefulException(LocalizableStrings.RequiredArgumentNotPassed, "<Project>");
}
ProjectRootElement project;
string projectDir;
if (File.Exists(projectArgument.Value))
{
project = P2PHelpers.GetProjectFromFileOrThrow(projectArgument.Value);
projectDir = new FileInfo(projectArgument.Value).DirectoryName;
}
else
{
project = P2PHelpers.GetProjectFromDirectoryOrThrow(projectArgument.Value);
projectDir = projectArgument.Value;
}
projectDir = PathUtility.EnsureTrailingSlash(projectDir);
if (app.RemainingArguments.Count == 0)
{
throw new GracefulException(LocalizableStrings.SpecifyAtLeastOneReferenceToRemove);
}
List<string> references = app.RemainingArguments;
int numberOfRemovedReferences = P2PHelpers.RemoveProjectToProjectReference(
project,
frameworkOption.Value(),
references);
if (numberOfRemovedReferences != 0)
{
project.Save();
}
return 0;
});
try
{
return app.Execute(args);
}
catch (GracefulException e)
{
Reporter.Error.WriteLine(e.Message.Red());
app.ShowHelp();
return 1;
}
}
}
}

View file

@ -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] <object> <command> [[--] <arg>...]]
Options:
-h|--help Show help information
Arguments:
<object> The object of the operation. If a project file is not specified, it defaults to the current directory.
<command> Command to be executed on <object>.
Args:
Any extra arguments passed to the command. Use `dotnet add <command> --help` to get help about these arguments.
Commands:
p2p Remove project to project (p2p) reference from a project";
protected override Dictionary<string, Func<string[], int>> BuiltInCommands => new Dictionary<string, Func<string[], int>>
{
["p2p"] = RemoveProjectToProjectReferenceCommand.Run,
};
}
}

View file

@ -1,6 +1,5 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<VersionPrefix>1.0.0-preview4</VersionPrefix>
<TargetFramework>netcoreapp1.0</TargetFramework>
@ -11,14 +10,12 @@
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">$(PackageTargetFallback);dotnet5.4</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" Exclude="commands\dotnet-new\CSharp_Console\**;commands\dotnet-new\CSharp_Lib\**;commands\dotnet-new\CSharp_Mstest\**;commands\dotnet-new\CSharp_Web\**;commands\dotnet-new\CSharp_Xunittest\**;bin\**;obj\**;**\*.xproj;packages\**" />
<EmbeddedResource Include="**\*.resx" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
<EmbeddedResource Include="commands\dotnet-new\CSharp_Console.zip;commands\dotnet-new\CSharp_Lib.zip;commands\dotnet-new\CSharp_Mstest.zip;commands\dotnet-new\CSharp_Xunittest.zip;commands\dotnet-new\CSharp_Web.zip;compiler\resources\**\*" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
<EmbeddedResource Include="commands\dotnet-new\FSharp_Console.zip;commands\dotnet-new\FSharp_Lib.zip;commands\dotnet-new\FSharp_Mstest.zip;commands\dotnet-new\FSharp_Xunittest.zip;commands\dotnet-new\FSharp_Web.zip" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Microsoft.DotNet.Configurer/Microsoft.DotNet.Configurer.csproj" />
<ProjectReference Include="../Microsoft.DotNet.ProjectJsonMigration/Microsoft.DotNet.ProjectJsonMigration.csproj" />
@ -26,7 +23,6 @@
<ProjectReference Include="../Microsoft.DotNet.Archive/Microsoft.DotNet.Archive.csproj" />
<ProjectReference Include="../Microsoft.DotNet.Cli.Utils/Microsoft.DotNet.Cli.Utils.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>1.0.0-alpha-20161104-2</Version>
@ -79,14 +75,11 @@
<Version>1.0.1-beta-000933</Version>
</PackageReference>
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">
<DefineConstants>$(DefineConstants);NETCOREAPP1_0</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>