add dotnet list p2ps (#4929)

* add stub for dotnet list p2ps

* apply review feedback

* PR feedback: consistent method modifiers

* apply missed review feedback

* add test coverage and do not treat no p2ps as error

* move private methods to the bottom, rename weird res name
This commit is contained in:
Krzysztof Wicher 2016-12-07 12:56:27 -08:00 committed by Piotr Puszkiewicz
parent d29925a689
commit 79e6126b2a
17 changed files with 448 additions and 3 deletions

View file

@ -19,6 +19,7 @@
public const string ProjectFile = "Project file";
public const string Reference = "Reference";
public const string ProjectReference = "Project reference";
public const string ProjectReferenceOneOrMore = "Project reference(s)";
public const string PackageReference = "Package reference";
public const string P2P = "Project to Project";
public const string P2PReference = "Project to Project reference";

View file

@ -14,7 +14,7 @@ namespace Microsoft.DotNet.Tools
protected abstract string HelpText { get; }
protected abstract Dictionary<string, Func<string[], int>> BuiltInCommands { get; }
public int Run(string[] args)
public int Start(string[] args)
{
DebugHelper.HandleDebugSwitch(ref args);

View file

@ -131,6 +131,11 @@ namespace Microsoft.DotNet.Tools
return totalNumberOfRemovedReferences;
}
public IEnumerable<ProjectItemElement> GetProjectToProjectReferences()
{
return Project.GetAllItemsWithElementType(ProjectItemElementType);
}
public void ConvertPathsToRelative(ref List<string> references)
{
references = references.Select((r) => PathUtility.GetRelativePath(ProjectDirectory, Path.GetFullPath(r))).ToList();

View file

@ -67,6 +67,11 @@ namespace Microsoft.DotNet.Tools
return root.FindExistingItemsWithCondition(framework, include).Count() != 0;
}
public static IEnumerable<ProjectItemElement> GetAllItemsWithElementType(this ProjectRootElement root, string projectItemElementType)
{
return root.Items.Where((it) => it.ItemType == projectItemElementType);
}
public static bool HasInclude(this ProjectItemElement el, string include)
{
include = NormalizeIncludeForComparison(include);

View file

@ -12,6 +12,7 @@ using Microsoft.DotNet.Tools.Add;
using Microsoft.DotNet.Tools.Build;
using Microsoft.DotNet.Tools.Clean;
using Microsoft.DotNet.Tools.Help;
using Microsoft.DotNet.Tools.List;
using Microsoft.DotNet.Tools.Migrate;
using Microsoft.DotNet.Tools.MSBuild;
using Microsoft.DotNet.Tools.New;
@ -32,17 +33,18 @@ namespace Microsoft.DotNet.Cli
{
private static Dictionary<string, Func<string[], int>> s_builtIns = new Dictionary<string, Func<string[], int>>
{
["add"] = (new AddCommand()).Run,
["add"] = AddCommand.Run,
["build"] = BuildCommand.Run,
["clean"] = CleanCommand.Run,
["help"] = HelpCommand.Run,
["list"] = ListCommand.Run,
["migrate"] = MigrateCommand.Run,
["msbuild"] = MSBuildCommand.Run,
["new"] = NewCommand.Run,
["nuget"] = NuGetCommand.Run,
["pack"] = PackCommand.Run,
["publish"] = PublishCommand.Run,
["remove"] = (new RemoveCommand()).Run,
["remove"] = RemoveCommand.Run,
["restore"] = RestoreCommand.Run,
["restore-projectjson"] = RestoreProjectJsonCommand.Run,
["run"] = RunCommand.Run,

View file

@ -37,5 +37,11 @@ Args:
{
["p2p"] = AddProjectToProjectReferenceCommand.Run,
};
public static int Run(string[] args)
{
var cmd = new AddCommand();
return cmd.Start(args);
}
}
}

View file

@ -38,6 +38,7 @@ namespace Microsoft.DotNet.Tools.Help
Project modification commands:
add Add items to the project
remove Remove items from the project
list List items in the project
{LocalizableStrings.AdvancedCommands}:
nuget {LocalizableStrings.NugetDefinition}

View file

@ -0,0 +1,25 @@
namespace Microsoft.DotNet.Tools.List
{
internal class LocalizableStrings
{
public const string Usage = "Usage";
public const string Arguments = "Arguments";
public const string ExtraArgs = "Args";
public const string ListCommandDescription = ".NET List Command";
public const string Commands = "Commands";
public const string CommandDefinition = "Command to be executed on <object>.";
public const string ExtraArgumentsDefinition = "Any extra arguments passed to the command. Use `dotnet list <command> --help` to get help about these arguments.";
public const string HelpDefinition = "Show help";
public const string ObjectDefinition = "The object of the operation. If a project file is not specified, it defaults to the current directory.";
public const string P2PsDefinition = "List project to project (p2p) references from a project";
}
}

View file

@ -0,0 +1,40 @@
// 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.Collections.Generic;
using Microsoft.DotNet.Tools.List.ProjectToProjectReferences;
namespace Microsoft.DotNet.Tools.List
{
public class ListCommand : DispatchCommand
{
protected override string HelpText => $@"{LocalizableStrings.ListCommandDescription}
{LocalizableStrings.Usage}: dotnet list [options] <object> <command> [[--] <arg>...]]
Options:
-h|--help {LocalizableStrings.HelpDefinition}
{LocalizableStrings.Arguments}:
<object> {LocalizableStrings.ObjectDefinition}
<command> {LocalizableStrings.CommandDefinition}
{LocalizableStrings.ExtraArgs}:
{LocalizableStrings.ExtraArgumentsDefinition}
{LocalizableStrings.Commands}:
p2ps {LocalizableStrings.P2PsDefinition}";
protected override Dictionary<string, Func<string[], int>> BuiltInCommands => new Dictionary<string, Func<string[], int>>
{
["p2ps"] = ListProjectToProjectReferencesCommand.Run,
};
public static int Run(string[] args)
{
var cmd = new ListCommand();
return cmd.Start(args);
}
}
}

View file

@ -0,0 +1,15 @@
namespace Microsoft.DotNet.Tools.List.ProjectToProjectReferences
{
internal class LocalizableStrings
{
public const string AppFullName = ".NET Core Project-to-Project dependency viewer";
public const string AppDescription = "Command to list project to project (p2p) references";
public const string ProjectArgumentValueName = "PROJECT";
public const string ProjectArgumentDescription = "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.";
public const string NoReferencesFound = "There are no {0} references in project {1}.\n{0} is the type of the item being requested (project, package, p2p) and {1} is the object operated on (a project file or a solution file). ";
}
}

View file

@ -0,0 +1,65 @@
// 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;
using System.Linq;
namespace Microsoft.DotNet.Tools.List.ProjectToProjectReferences
{
public class ListProjectToProjectReferencesCommand
{
public static int Run(string[] args)
{
DebugHelper.HandleDebugSwitch(ref args);
CommandLineApplication app = new CommandLineApplication(throwOnUnexpectedArg: false)
{
Name = "dotnet list p2ps",
FullName = LocalizableStrings.AppFullName,
Description = LocalizableStrings.AppDescription
};
app.HelpOption("-h|--help");
CommandArgument projectArgument = app.Argument($"<{LocalizableStrings.ProjectArgumentValueName}>", LocalizableStrings.ProjectArgumentDescription);
app.OnExecute(() => {
if (string.IsNullOrEmpty(projectArgument.Value))
{
throw new GracefulException(CommonLocalizableStrings.RequiredArgumentNotPassed, $"<{LocalizableStrings.ProjectArgumentValueName}>");
}
var msbuildProj = MsbuildProject.FromFileOrDirectory(projectArgument.Value);
var p2ps = msbuildProj.GetProjectToProjectReferences();
if (p2ps.Count() == 0)
{
Reporter.Output.WriteLine(string.Format(LocalizableStrings.NoReferencesFound, CommonLocalizableStrings.P2P, projectArgument.Value));
return 0;
}
Reporter.Output.WriteLine($"{CommonLocalizableStrings.ProjectReferenceOneOrMore}");
Reporter.Output.WriteLine(new string('-', CommonLocalizableStrings.ProjectReferenceOneOrMore.Length));
foreach (var p2p in p2ps)
{
Reporter.Output.WriteLine(p2p.Include);
}
return 0;
});
try
{
return app.Execute(args);
}
catch (GracefulException e)
{
Reporter.Error.WriteLine(e.Message.Red());
app.ShowHelp();
return 1;
}
}
}
}

View file

@ -37,5 +37,11 @@ Args:
{
["p2p"] = RemoveProjectToProjectReferenceCommand.Run,
};
public static int Run(string[] args)
{
var cmd = new RemoveCommand();
return cmd.Start(args);
}
}
}

View file

@ -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 ListP2PsCommand : TestCommand
{
private string _projectName = null;
public ListP2PsCommand()
: base("dotnet")
{
}
public override CommandResult Execute(string args = "")
{
args = $"list {_projectName} p2ps {args}";
return base.ExecuteWithCapturedOutput(args);
}
public ListP2PsCommand WithProject(string projectName)
{
_projectName = projectName;
return this;
}
}
}

View file

@ -0,0 +1,180 @@
// 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.List.P2P.Tests
{
public class GivenDotnetListP2Ps : TestBase
{
const string FrameworkNet451Arg = "-f net451";
const string ConditionFrameworkNet451 = "== 'net451'";
const string FrameworkNetCoreApp10Arg = "-f netcoreapp1.0";
const string ConditionFrameworkNetCoreApp10 = "== 'netcoreapp1.0'";
const string UsageText = "Usage: dotnet list p2ps";
[Theory]
[InlineData("--help")]
[InlineData("-h")]
public void WhenHelpOptionIsPassedItPrintsUsage(string helpArg)
{
var cmd = new ListP2PsCommand().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 ListP2PsCommand()
.WithWorkingDirectory(setup.TestRoot)
.WithProject(projName)
.Execute($"\"{setup.ValidRefCsprojPath}\"");
cmd.ExitCode.Should().NotBe(0);
cmd.StdErr.Should().Contain("Could not find");
cmd.StdOut.Should().Contain(UsageText);
}
[Fact]
public void WhenBrokenProjectIsPassedItPrintsErrorAndUsage()
{
string projName = "Broken/Broken.csproj";
var setup = Setup();
var cmd = new ListP2PsCommand()
.WithWorkingDirectory(setup.TestRoot)
.WithProject(projName)
.Execute($"\"{setup.ValidRefCsprojPath}\"");
cmd.ExitCode.Should().NotBe(0);
cmd.StdErr.Should().Contain(" is invalid.");
cmd.StdOut.Should().Contain(UsageText);
}
[Fact]
public void WhenMoreThanOneProjectExistsInTheDirectoryItPrintsErrorAndUsage()
{
var setup = Setup();
var cmd = new ListP2PsCommand()
.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(UsageText);
}
[Fact]
public void WhenNoProjectsExistsInTheDirectoryItPrintsErrorAndUsage()
{
var setup = Setup();
var cmd = new ListP2PsCommand()
.WithWorkingDirectory(setup.TestRoot)
.Execute($"\"{setup.ValidRefCsprojPath}\"");
cmd.ExitCode.Should().NotBe(0);
cmd.StdErr.Should().Contain("not find any");
cmd.StdOut.Should().Contain(UsageText);
}
[Fact]
public void WhenNoProjectReferencesArePresentInTheProjectItPrintsError()
{
var lib = NewLib();
var cmd = new ListP2PsCommand()
.WithProject(lib.CsProjPath)
.Execute();
cmd.Should().Pass();
cmd.StdOut.Should().Contain("There are no Project to Project references in project");
}
[Fact]
public void ItPrintsSingleReference()
{
var lib = NewLib();
string ref1 = "someref.csproj";
AddFakeRef(ref1, lib);
var cmd = new ListP2PsCommand()
.WithProject(lib.CsProjPath)
.Execute();
cmd.Should().Pass();
cmd.StdOut.Should().Contain("Project reference(s)");
cmd.StdOut.Should().Contain(ref1);
}
[Fact]
public void ItPrintsMultipleReferences()
{
var lib = NewLib();
string ref1 = "someref.csproj";
string ref2 = @"..\someref2.csproj";
string ref3 = @"..\abc\abc.csproj";
AddFakeRef(ref1, lib);
AddFakeRef(ref2, lib);
AddFakeRef(ref3, lib);
var cmd = new ListP2PsCommand()
.WithProject(lib.CsProjPath)
.Execute();
cmd.Should().Pass();
cmd.StdOut.Should().Contain("Project reference(s)");
cmd.StdOut.Should().Contain(ref1);
cmd.StdOut.Should().Contain(ref2);
cmd.StdOut.Should().Contain(ref3);
}
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 void AddFakeRef(string path, ProjDir proj)
{
new AddP2PCommand()
.WithProject(proj.CsProjPath)
.Execute($"--force \"{path}\"")
.Should().Pass();
}
}
}

View file

@ -0,0 +1 @@
https://github.com/Microsoft/msbuild/issues/927

View file

@ -0,0 +1 @@
https://github.com/Microsoft/msbuild/issues/927

View file

@ -0,0 +1,63 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<AssemblyName>dotnet-list-p2ps.Tests</AssemblyName>
<PackageTargetFallback Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">$(PackageTargetFallback);dotnet5.4;portable-net451+win8</PackageTargetFallback>
<DefineConstants>$(DefineConstants);DISABLE_TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
<EmbeddedResource Include="**\*.resx" />
<EmbeddedResource Include="compiler\resources\**\*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.DotNet.Tools.Tests.Utilities\Microsoft.DotNet.Tools.Tests.Utilities.csproj" />
<ProjectReference Include="..\..\src\Microsoft.DotNet.TestFramework\Microsoft.DotNet.TestFramework.csproj">
<FromP2P>true</FromP2P>
</ProjectReference>
<ProjectReference Include="..\..\src\Microsoft.DotNet.Cli.Utils\Microsoft.DotNet.Cli.Utils.csproj">
<FromP2P>true</FromP2P>
</ProjectReference>
<ProjectReference Include="..\..\src\Microsoft.DotNet.InternalAbstractions\Microsoft.DotNet.InternalAbstractions.csproj">
<FromP2P>true</FromP2P>
</ProjectReference>
<ProjectReference Include="..\Msbuild.Tests.Utilities\Msbuild.Tests.Utilities.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
<Reference Include="System.Runtime">
<FromP2P>true</FromP2P>
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Sdk">
<Version>$(CLI_NETSDK_Version)</Version>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk">
<Version>15.0.0-preview-20161024-02</Version>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio">
<Version>2.2.0-beta4-build1194</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.App">
<Version>1.0.1</Version>
</PackageReference>
<PackageReference Include="System.Runtime.Serialization.Primitives">
<Version>4.1.1</Version>
</PackageReference>
<PackageReference Include="xunit">
<Version>2.2.0-beta4-build3444</Version>
</PackageReference>
<PackageReference Include="FluentAssertions">
<Version>4.0.0</Version>
</PackageReference>
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
</PropertyGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>