From 3a4ce0a86b0b4c8fbb4d16ea62e508d5ab29dede Mon Sep 17 00:00:00 2001 From: Sridhar Periyasamy Date: Wed, 2 Mar 2016 16:20:45 -0500 Subject: [PATCH 01/13] Add support for building dotnet CLI on RHEL. --- .../OutputStandardOutputAndError/project.json | 2 +- .../CurrentPlatform.cs | 15 +++++- .../Enumerations/BuildPlatform.cs | 5 +- scripts/dotnet-cli-build/CompileTargets.cs | 54 +++++++------------ scripts/obtain/install.sh | 2 + test/EndToEnd/EndToEndTest.cs | 17 +++--- 6 files changed, 48 insertions(+), 47 deletions(-) diff --git a/TestAssets/TestProjects/OutputStandardOutputAndError/project.json b/TestAssets/TestProjects/OutputStandardOutputAndError/project.json index f9b804dda..8c87d36c9 100644 --- a/TestAssets/TestProjects/OutputStandardOutputAndError/project.json +++ b/TestAssets/TestProjects/OutputStandardOutputAndError/project.json @@ -5,7 +5,7 @@ }, "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23728" + "NETStandard.Library": "1.0.0-rc2-23811" }, "frameworks": { diff --git a/scripts/Microsoft.DotNet.Cli.Build.Framework/CurrentPlatform.cs b/scripts/Microsoft.DotNet.Cli.Build.Framework/CurrentPlatform.cs index fb70d37c1..7fd8752ae 100644 --- a/scripts/Microsoft.DotNet.Cli.Build.Framework/CurrentPlatform.cs +++ b/scripts/Microsoft.DotNet.Cli.Build.Framework/CurrentPlatform.cs @@ -48,6 +48,15 @@ namespace Microsoft.DotNet.Cli.Build.Framework } } + public static bool IsRHEL + { + get + { + var osname = PlatformServices.Default.Runtime.OperatingSystem; + return string.Equals(osname, "rhel", StringComparison.OrdinalIgnoreCase); + } + } + private static BuildPlatform DetermineCurrentPlatform() { if (IsWindows) @@ -66,10 +75,14 @@ namespace Microsoft.DotNet.Cli.Build.Framework { return BuildPlatform.CentOS; } + else if (IsRHEL) + { + return BuildPlatform.RHEL; + } else { return default(BuildPlatform); } } } -} \ No newline at end of file +} diff --git a/scripts/Microsoft.DotNet.Cli.Build.Framework/Enumerations/BuildPlatform.cs b/scripts/Microsoft.DotNet.Cli.Build.Framework/Enumerations/BuildPlatform.cs index d594ca604..715795196 100644 --- a/scripts/Microsoft.DotNet.Cli.Build.Framework/Enumerations/BuildPlatform.cs +++ b/scripts/Microsoft.DotNet.Cli.Build.Framework/Enumerations/BuildPlatform.cs @@ -5,6 +5,7 @@ namespace Microsoft.DotNet.Cli.Build.Framework Windows = 1, OSX = 2, Ubuntu = 3, - CentOS = 4 + CentOS = 4, + RHEL = 5 } -} \ No newline at end of file +} diff --git a/scripts/dotnet-cli-build/CompileTargets.cs b/scripts/dotnet-cli-build/CompileTargets.cs index e62fba929..1664ce7d7 100644 --- a/scripts/dotnet-cli-build/CompileTargets.cs +++ b/scripts/dotnet-cli-build/CompileTargets.cs @@ -238,9 +238,9 @@ namespace Microsoft.DotNet.Cli.Build // Find toolchain package string packageId; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (CurrentPlatform.IsWindows) { - if (IsWinx86) + if (CurrentArchitecture.Isx86) { // https://github.com/dotnet/cli/issues/1550 c.Warn("Native compilation is not yet working on Windows x86"); @@ -249,24 +249,16 @@ namespace Microsoft.DotNet.Cli.Build packageId = "toolchain.win7-x64.Microsoft.DotNet.AppDep"; } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + else if (CurrentPlatform.IsUbuntu) { - var osname = PlatformServices.Default.Runtime.OperatingSystem; - if (string.Equals(osname, "ubuntu", StringComparison.OrdinalIgnoreCase)) - { - packageId = "toolchain.ubuntu.14.04-x64.Microsoft.DotNet.AppDep"; - } - else if (string.Equals(osname, "centos", StringComparison.OrdinalIgnoreCase)) - { - c.Warn("Native compilation is not yet working on CentOS"); - return c.Success(); - } - else - { - return c.Failed($"Unknown Linux Distro: {osname}"); - } + packageId = "toolchain.ubuntu.14.04-x64.Microsoft.DotNet.AppDep"; } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + else if (CurrentPlatform.IsCentOS || CurrentPlatform.IsRHEL) + { + c.Warn($"Native compilation is not yet working on {CurrentPlatform.Current}"); + return c.Success(); + } + else if (CurrentPlatform.IsOSX) { packageId = "toolchain.osx.10.10-x64.Microsoft.DotNet.AppDep"; } @@ -296,28 +288,20 @@ namespace Microsoft.DotNet.Cli.Build // Find crossgen string arch = PlatformServices.Default.Runtime.RuntimeArchitecture; string packageId; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (CurrentPlatform.IsWindows) { packageId = $"runtime.win7-{arch}.Microsoft.NETCore.Runtime.CoreCLR"; } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + else if (CurrentPlatform.IsUbuntu) { - var osname = PlatformServices.Default.Runtime.OperatingSystem; - if (string.Equals(osname, "ubuntu", StringComparison.OrdinalIgnoreCase)) - { - packageId = "runtime.ubuntu.14.04-x64.Microsoft.NETCore.Runtime.CoreCLR"; - } - else if (string.Equals(osname, "centos", StringComparison.OrdinalIgnoreCase)) - { - // CentOS runtime is in the runtime.rhel.7-x64... package. - packageId = "runtime.rhel.7-x64.Microsoft.NETCore.Runtime.CoreCLR"; - } - else - { - return c.Failed($"Unknown Linux Distro: {osname}"); - } + packageId = "runtime.ubuntu.14.04-x64.Microsoft.NETCore.Runtime.CoreCLR"; } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + else if (CurrentPlatform.IsCentOS || CurrentPlatform.IsRHEL) + { + // CentOS runtime is in the runtime.rhel.7-x64... package. + packageId = "runtime.rhel.7-x64.Microsoft.NETCore.Runtime.CoreCLR"; + } + else if (CurrentPlatform.IsOSX) { packageId = "runtime.osx.10.10-x64.Microsoft.NETCore.Runtime.CoreCLR"; } diff --git a/scripts/obtain/install.sh b/scripts/obtain/install.sh index d973f9ff8..362e7a57c 100755 --- a/scripts/obtain/install.sh +++ b/scripts/obtain/install.sh @@ -83,6 +83,8 @@ current_os() echo "ubuntu" elif [ "$(cat /etc/*-release | grep -cim1 centos)" -eq 1 ]; then echo "centos" + elif [ "$(cat /etc/*-release | grep -cim1 rhel)" -eq 1 ]; then + echo "rhel.7" fi fi } diff --git a/test/EndToEnd/EndToEndTest.cs b/test/EndToEnd/EndToEndTest.cs index 1f734e3bb..f6f5663be 100644 --- a/test/EndToEnd/EndToEndTest.cs +++ b/test/EndToEnd/EndToEndTest.cs @@ -83,9 +83,9 @@ namespace Microsoft.DotNet.Tests.EndToEnd [Fact] public void TestDotnetBuildNativeRyuJit() { - if(IsCentOS()) + if(IsCentOSorRHEL()) { - Console.WriteLine("Skipping native compilation tests on CentOS - https://github.com/dotnet/cli/issues/453"); + Console.WriteLine("Skipping native compilation tests on CentOS/RHEL - https://github.com/dotnet/cli/issues/453"); return; } @@ -105,9 +105,9 @@ namespace Microsoft.DotNet.Tests.EndToEnd [Fact] public void TestDotnetBuildNativeCpp() { - if(IsCentOS()) + if(IsCentOSorRHEL()) { - Console.WriteLine("Skipping native compilation tests on CentOS - https://github.com/dotnet/cli/issues/453"); + Console.WriteLine("Skipping native compilation tests on CentOS/RHEL - https://github.com/dotnet/cli/issues/453"); return; } @@ -127,9 +127,9 @@ namespace Microsoft.DotNet.Tests.EndToEnd [Fact] public void TestDotnetCompileNativeCppIncremental() { - if (IsCentOS()) + if (IsCentOSorRHEL()) { - Console.WriteLine("Skipping native compilation tests on CentOS - https://github.com/dotnet/cli/issues/453"); + Console.WriteLine("Skipping native compilation tests on CentOS/RHEL - https://github.com/dotnet/cli/issues/453"); return; } @@ -236,7 +236,7 @@ namespace Microsoft.DotNet.Tests.EndToEnd Directory.SetCurrentDirectory(currentDirectory); } - private bool IsCentOS() + private bool IsCentOSorRHEL() { if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { @@ -244,7 +244,8 @@ namespace Microsoft.DotNet.Tests.EndToEnd if(File.Exists(OSIDFILE)) { - return File.ReadAllText(OSIDFILE).ToLower().Contains("centos"); + string osidcontent = File.ReadAllText(OSIDFILE).ToLower(); + return osidcontent.Contains("centos") || osidcontent.Contains("rhel"); } } From d7bb28dd68b95e2fb95cc61446ebe85079961fe0 Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Thu, 3 Mar 2016 17:39:04 -0800 Subject: [PATCH 02/13] Update run-build.sh --- scripts/run-build.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/run-build.sh b/scripts/run-build.sh index ee11ecdf4..ff822cf98 100755 --- a/scripts/run-build.sh +++ b/scripts/run-build.sh @@ -14,6 +14,8 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli done DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" +source "$DIR/common/_prettyprint.sh" + while [[ $# > 0 ]]; do lowerI="$(echo $1 | awk '{print tolower($0)}')" case $lowerI in From 2ea26645e6ce6dfea844d3669674e8d5b6e886d7 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Mon, 7 Mar 2016 12:01:07 -0800 Subject: [PATCH 03/13] Add ability to specify `outputPath` when creating a `ProjectContext`s `AssemblyLoadContext`. - Prior to this change `outputPath` would always default to `null` which didn't work well with `VisualStudio` tooling. The tooling enables the user to specify a project's `outputPath` but more importantly defaults the project's `outputPath` to the root directory (instead of the project directory). --- .../LoaderProjectContextExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.ProjectModel.Loader/LoaderProjectContextExtensions.cs b/src/Microsoft.DotNet.ProjectModel.Loader/LoaderProjectContextExtensions.cs index bb4a2e22e..342b2aef1 100644 --- a/src/Microsoft.DotNet.ProjectModel.Loader/LoaderProjectContextExtensions.cs +++ b/src/Microsoft.DotNet.ProjectModel.Loader/LoaderProjectContextExtensions.cs @@ -10,7 +10,10 @@ namespace Microsoft.DotNet.ProjectModel.Loader { public static class LoaderProjectContextExtensions { - public static AssemblyLoadContext CreateLoadContext(this ProjectContext context, string configuration = "Debug") + public static AssemblyLoadContext CreateLoadContext( + this ProjectContext context, + string configuration = "Debug", + string outputPath = null) { var exporter = context.CreateExporter(configuration); var assemblies = new Dictionary(AssemblyNameComparer.OrdinalIgnoreCase); @@ -38,7 +41,7 @@ namespace Microsoft.DotNet.ProjectModel.Loader dllImports, // Add the project's output directory path to ensure project-to-project references get located - new[] { context.GetOutputPaths(configuration).CompilationOutputPath }); + new[] { context.GetOutputPaths(configuration, outputPath: outputPath).CompilationOutputPath }); } private class AssemblyNameComparer : IEqualityComparer From 42cc39252e60bd610899c1cdbcb55ad567d678e8 Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 24 Feb 2016 16:05:55 -0800 Subject: [PATCH 04/13] Refactor CommandResolver into individual CommandResolver Implementation classes. Write Unit Tests covering Composite DefaultCommandResolver and ScriptCommandResolver. baseline Baseline2 --- .../AbstractPathBasedCommandResolver.cs | 60 +++++ .../AppBaseCommandResolver.cs | 30 +++ .../CommandResolutionStrategy.cs | 26 ++ .../CommandResolverArguments.cs | 26 ++ .../CompositeCommandResolver.cs | 51 ++++ .../DefaultCommandResolver.cs | 46 ++++ .../GenericPlatformCommandSpecFactory.cs | 27 ++ .../CommandResolution/ICommandResolver.cs | 12 + .../IPackagedCommandSpecFactory.cs | 21 ++ .../IPlatformCommandSpecFactory.cs | 23 ++ .../PackagedCommandSpecFactory.cs | 119 +++++++++ .../CommandResolution/PathCommandResolver.cs | 29 +++ .../ProjectDependenciesCommandResolver.cs | 179 +++++++++++++ .../ProjectPathCommandResolver.cs | 37 +++ .../ProjectToolsCommandResolver.cs | 141 +++++++++++ .../ProjectToolsPackageCommandResolver.cs | 141 +++++++++++ .../RootedCommandResolver.cs | 34 +++ .../ScriptCommandResolver.cs | 43 ++++ .../WindowsExePreferredCommandSpecFactory.cs | 79 ++++++ .../CommandResolutionStrategy.cs | 20 -- .../CommandResolver.cs | 170 +++---------- src/Microsoft.DotNet.Cli.Utils/CommandSpec.cs | 2 +- src/Microsoft.DotNet.Cli.Utils/Env.cs | 61 +---- .../EnvironmentProvider.cs | 97 +++++++ .../Extensions/CollectionsExtensions.cs | 16 ++ .../IEnvironmentProvider.cs | 20 ++ .../CommandResolverTestUtils.cs | 25 ++ .../GivenACompositeCommandResolver.cs | 86 +++++++ .../GivenADefaultCommandResolver.cs | 206 +++++++++++++++ .../GivenAPathCommandResolver.cs | 0 .../GivenAProjectDependencyCommandResolver.cs | 0 .../GivenAProjectPathCommandResolver.cs | 239 ++++++++++++++++++ .../GivenAProjectToolsCommandResolver.cs | 0 .../GivenARootedCommandResolver.cs | 111 ++++++++ .../GivenAScriptCommandResolver.cs | 199 +++++++++++++++ .../GivenAnAppBaseCommandResolver.cs | 184 ++++++++++++++ .../project.json | 7 +- 37 files changed, 2361 insertions(+), 206 deletions(-) create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/AbstractPathBasedCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/AppBaseCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolutionStrategy.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/CompositeCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/GenericPlatformCommandSpecFactory.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/ICommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPackagedCommandSpecFactory.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPlatformCommandSpecFactory.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/PackagedCommandSpecFactory.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/PathCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectPathCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsPackageCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/RootedCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolver.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/WindowsExePreferredCommandSpecFactory.cs delete mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolutionStrategy.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/Extensions/CollectionsExtensions.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/CommandResolverTestUtils.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/GivenACompositeCommandResolver.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/GivenAPathCommandResolver.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectDependencyCommandResolver.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectPathCommandResolver.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/GivenARootedCommandResolver.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs create mode 100644 test/Microsoft.DotNet.Cli.Utils.Tests/GivenAnAppBaseCommandResolver.cs diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AbstractPathBasedCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AbstractPathBasedCommandResolver.cs new file mode 100644 index 000000000..883aa16a5 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AbstractPathBasedCommandResolver.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; +namespace Microsoft.DotNet.Cli.Utils +{ + public abstract class AbstractPathBasedCommandResolver : ICommandResolver + { + protected IEnvironmentProvider _environment; + protected IPlatformCommandSpecFactory _commandSpecFactory; + + public AbstractPathBasedCommandResolver(IEnvironmentProvider environment, + IPlatformCommandSpecFactory commandSpecFactory) + { + if (environment == null) + { + throw new ArgumentNullException("environment"); + } + + if (commandSpecFactory == null) + { + throw new ArgumentNullException("commandSpecFactory"); + } + + _environment = environment; + _commandSpecFactory = commandSpecFactory; + } + + public CommandSpec Resolve(CommandResolverArguments commandResolverArguments) + { + if (commandResolverArguments.CommandName == null) + { + return null; + } + + var commandPath = ResolveCommandPath(commandResolverArguments); + + if (commandPath == null) + { + return null; + } + + return _commandSpecFactory.CreateCommandSpec( + commandResolverArguments.CommandName, + commandResolverArguments.CommandArguments.EmptyIfNull(), + commandPath, + GetCommandResolutionStrategy(), + _environment); + } + + internal abstract string ResolveCommandPath(CommandResolverArguments commandResolverArguments); + internal abstract CommandResolutionStrategy GetCommandResolutionStrategy(); + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AppBaseCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AppBaseCommandResolver.cs new file mode 100644 index 000000000..26668183f --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AppBaseCommandResolver.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; +namespace Microsoft.DotNet.Cli.Utils +{ + public class AppBaseCommandResolver : AbstractPathBasedCommandResolver + { + public AppBaseCommandResolver(IEnvironmentProvider environment, + IPlatformCommandSpecFactory commandSpecFactory) : base(environment, commandSpecFactory) { } + + internal override string ResolveCommandPath(CommandResolverArguments commandResolverArguments) + { + return _environment.GetCommandPathFromRootPath( + PlatformServices.Default.Application.ApplicationBasePath, + commandResolverArguments.CommandName); + } + + internal override CommandResolutionStrategy GetCommandResolutionStrategy() + { + return CommandResolutionStrategy.BaseDirectory; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolutionStrategy.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolutionStrategy.cs new file mode 100644 index 000000000..c3059c8a0 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolutionStrategy.cs @@ -0,0 +1,26 @@ +namespace Microsoft.DotNet.Cli.Utils +{ + public enum CommandResolutionStrategy + { + // command loaded from project dependencies nuget package + ProjectDependenciesPackage, + + // command loaded from project tools nuget package + ProjectToolsPackage, + + // command loaded from the same directory as the executing assembly + BaseDirectory, + + // command loaded from the same directory as a project.json file + ProjectLocal, + + // command loaded from PATH environment variable + Path, + + // command loaded from rooted path + RootedPath, + + // command not found + None + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs new file mode 100644 index 000000000..2e2c5ee93 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class CommandResolverArguments + { + public string CommandName { get; set; } + + public IEnumerable CommandArguments { get; set; } + + public NuGetFramework Framework { get; set; } + + public string ProjectDirectory { get; set; } + + public string Configuration { get; set; } + + public IEnumerable InferredExtensions { get; set; } + + public IEnvironmentProvider Environment { get; set; } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CompositeCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CompositeCommandResolver.cs new file mode 100644 index 000000000..71bb589dc --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CompositeCommandResolver.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class CompositeCommandResolver : ICommandResolver + { + private IList _orderedCommandResolvers; + + public IEnumerable OrderedCommandResolvers + { + get + { + return _orderedCommandResolvers; + } + } + + public CompositeCommandResolver() + { + _orderedCommandResolvers = new List(); + } + + public void AddCommandResolver(ICommandResolver commandResolver) + { + _orderedCommandResolvers.Add(commandResolver); + } + + public CommandSpec Resolve(CommandResolverArguments commandResolverArguments) + { + foreach (var commandResolver in _orderedCommandResolvers) + { + var commandSpec = commandResolver.Resolve(commandResolverArguments); + + if (commandSpec != null) + { + return commandSpec; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs new file mode 100644 index 000000000..1fdcb83ff --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class DefaultCommandResolver : CompositeCommandResolver + { + public static DefaultCommandResolver Create() + { + var environment = new EnvironmentProvider(); + var packagedCommandSpecFactory = new PackagedCommandSpecFactory(); + + var platformCommandSpecFactory = default(IPlatformCommandSpecFactory); + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows) + { + platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + } + else + { + platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); + } + + return new DefaultCommandResolver(environment, packagedCommandSpecFactory, platformCommandSpecFactory); + } + + public DefaultCommandResolver( + IEnvironmentProvider environment, + IPackagedCommandSpecFactory packagedCommandSpecFactory, + IPlatformCommandSpecFactory platformCommandSpecFactory) : base() + { + AddCommandResolver(new RootedCommandResolver()); + AddCommandResolver(new ProjectDependenciesCommandResolver(environment, packagedCommandSpecFactory)); + AddCommandResolver(new ProjectToolsCommandResolver(packagedCommandSpecFactory)); + AddCommandResolver(new AppBaseCommandResolver(environment, platformCommandSpecFactory)); + AddCommandResolver(new PathCommandResolver(environment, platformCommandSpecFactory)); + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/GenericPlatformCommandSpecFactory.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/GenericPlatformCommandSpecFactory.cs new file mode 100644 index 000000000..eea6365e4 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/GenericPlatformCommandSpecFactory.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class GenericPlatformCommandSpecFactory : IPlatformCommandSpecFactory + { + public CommandSpec CreateCommandSpec( + string commandName, + IEnumerable args, + string commandPath, + CommandResolutionStrategy resolutionStrategy, + IEnvironmentProvider environment) + { + var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); + return new CommandSpec(commandPath, escapedArgs, resolutionStrategy); + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ICommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ICommandResolver.cs new file mode 100644 index 000000000..25b983598 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ICommandResolver.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Cli.Utils +{ + public interface ICommandResolver + { + CommandSpec Resolve(CommandResolverArguments arguments); + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPackagedCommandSpecFactory.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPackagedCommandSpecFactory.cs new file mode 100644 index 000000000..34c14e56c --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPackagedCommandSpecFactory.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.DotNet.ProjectModel.Graph; + +namespace Microsoft.DotNet.Cli.Utils +{ + public interface IPackagedCommandSpecFactory + { + CommandSpec CreateCommandSpecFromLibrary( + LockFilePackageLibrary library, + string commandName, + IEnumerable commandArguments, + IEnumerable allowedExtensions, + string nugetPackagesRoot, + CommandResolutionStrategy commandResolutionStrategy, + string depsFilePath); + + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPlatformCommandSpecFactory.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPlatformCommandSpecFactory.cs new file mode 100644 index 000000000..1463bc9ec --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPlatformCommandSpecFactory.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public interface IPlatformCommandSpecFactory + { + CommandSpec CreateCommandSpec( + string commandName, + IEnumerable args, + string commandPath, + CommandResolutionStrategy resolutionStrategy, + IEnvironmentProvider environment); + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PackagedCommandSpecFactory.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PackagedCommandSpecFactory.cs new file mode 100644 index 000000000..ee5bd7ecb --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PackagedCommandSpecFactory.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class PackagedCommandSpecFactory : IPackagedCommandSpecFactory + { + public CommandSpec CreateCommandSpecFromLibrary( + LockFilePackageLibrary library, + string commandName, + IEnumerable commandArguments, + IEnumerable allowedExtensions, + string nugetPackagesRoot, + CommandResolutionStrategy commandResolutionStrategy, + string depsFilePath) + { + var packageDirectory = GetPackageDirectoryFullPath(library, nugetPackagesRoot); + + if (!Directory.Exists(packageDirectory)) + { + return null; + } + + var commandFile = GetCommandFileRelativePath(library, commandName, allowedExtensions); + + if (commandFile == null) + { + return null; + } + + var commandPath = Path.Combine(packageDirectory, commandFile); + + return CreateCommandSpecWrappingWithCorehostfDll( + commandPath, + commandArguments, + depsFilePath, + commandResolutionStrategy); + } + + private string GetPackageDirectoryFullPath(LockFilePackageLibrary library, string nugetPackagesRoot) + { + var packageDirectory = new VersionFolderPathResolver(nugetPackagesRoot) + .GetInstallPath(library.Name, library.Version); + + return packageDirectory; + } + + private string GetCommandFileRelativePath( + LockFilePackageLibrary library, + string commandName, + IEnumerable allowedExtensions) + { + // TODO: Should command names be case sensitive? + return library.Files + .Where(f => Path.GetFileNameWithoutExtension(f) == commandName) + .Where(e => allowedExtensions.Contains(Path.GetExtension(e))) + .FirstOrDefault(); + } + + private CommandSpec CreateCommandSpecWrappingWithCorehostfDll( + string commandPath, + IEnumerable commandArguments, + string depsFilePath, + CommandResolutionStrategy commandResolutionStrategy) + { + var commandExtension = Path.GetExtension(commandPath); + + if (commandExtension == FileNameSuffixes.DotNet.DynamicLib) + { + return CreatePackageCommandSpecUsingCorehost( + commandPath, + commandArguments, + depsFilePath, + commandResolutionStrategy); + } + + return CreateCommandSpec(commandPath, commandArguments, commandResolutionStrategy); + } + + private CommandSpec CreatePackageCommandSpecUsingCorehost( + string commandPath, + IEnumerable commandArguments, + string depsFilePath, + CommandResolutionStrategy commandResolutionStrategy) + { + var corehost = CoreHost.HostExePath; + + var arguments = new List(); + arguments.Add(commandPath); + + if (depsFilePath != null) + { + arguments.Add($"--depsfile:{depsFilePath}"); + } + + arguments.AddRange(commandArguments); + + return CreateCommandSpec(corehost, arguments, commandResolutionStrategy); + } + + private CommandSpec CreateCommandSpec( + string commandPath, + IEnumerable commandArguments, + CommandResolutionStrategy commandResolutionStrategy) + { + var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(commandArguments); + + return new CommandSpec(commandPath, escapedArgs, commandResolutionStrategy); + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PathCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PathCommandResolver.cs new file mode 100644 index 000000000..a1e63c801 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PathCommandResolver.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class PathCommandResolver : AbstractPathBasedCommandResolver + { + public PathCommandResolver(IEnvironmentProvider environment, + IPlatformCommandSpecFactory commandSpecFactory) : base(environment, commandSpecFactory) { } + + internal override string ResolveCommandPath(CommandResolverArguments commandResolverArguments) + { + return _environment.GetCommandPath(commandResolverArguments.CommandName); + } + + internal override CommandResolutionStrategy GetCommandResolutionStrategy() + { + return CommandResolutionStrategy.Path; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs new file mode 100644 index 000000000..0c96fc2b2 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class ProjectDependenciesCommandResolver : ICommandResolver + { + private static readonly CommandResolutionStrategy s_commandResolutionStrategy = + CommandResolutionStrategy.ProjectDependenciesPackage; + + private IEnvironmentProvider _environment; + private IPackagedCommandSpecFactory _packagedCommandSpecFactory; + + public ProjectDependenciesCommandResolver( + IEnvironmentProvider environment, + IPackagedCommandSpecFactory packagedCommandSpecFactory) + { + if (environment == null) + { + throw new ArgumentNullException("environment"); + } + + if (packagedCommandSpecFactory == null) + { + throw new ArgumentNullException("packagedCommandSpecFactory"); + } + + _environment = environment; + _packagedCommandSpecFactory = packagedCommandSpecFactory; + } + + public CommandSpec Resolve(CommandResolverArguments commandResolverArguments) + { + if (commandResolverArguments.Framework == null + || commandResolverArguments.ProjectDirectory == null + || commandResolverArguments.Configuration == null + || commandResolverArguments.CommandName == null) + { + return null; + } + + return ResolveFromProjectDependencies( + commandResolverArguments.ProjectDirectory, + commandResolverArguments.Framework, + commandResolverArguments.Configuration, + commandResolverArguments.CommandName, + commandResolverArguments.CommandArguments); + } + + private CommandSpec ResolveFromProjectDependencies( + string projectDirectory, + NuGetFramework framework, + string configuration, + string commandName, + IEnumerable commandArguments) + { + var allowedExtensions = GetAllowedCommandExtensionsFromEnvironment(_environment); + + var projectContext = GetProjectContextFromDirectory( + projectDirectory, + framework); + + if (projectContext == null) + { + return null; + } + + var depsFilePath = projectContext.GetOutputPaths(configuration).RuntimeFiles.Deps; + + var dependencyLibraries = GetAllDependencyLibraries(projectContext); + + return ResolveFromDependencyLibraries( + dependencyLibraries, + depsFilePath, + commandName, + allowedExtensions, + commandArguments, + projectContext); + } + + private CommandSpec ResolveFromDependencyLibraries( + IEnumerable dependencyLibraries, + string depsFilePath, + string commandName, + IEnumerable allowedExtensions, + IEnumerable commandArguments, + ProjectContext projectContext) + { + foreach (var dependencyLibrary in dependencyLibraries) + { + var commandSpec = ResolveFromDependencyLibrary( + dependencyLibrary, + depsFilePath, + commandName, + allowedExtensions, + commandArguments, + projectContext); + + if (commandSpec != null) + { + return commandSpec; + } + } + + return null; + } + + private CommandSpec ResolveFromDependencyLibrary( + LockFilePackageLibrary dependencyLibrary, + string depsFilePath, + string commandName, + IEnumerable allowedExtensions, + IEnumerable commandArguments, + ProjectContext projectContext) + { + return _packagedCommandSpecFactory.CreateCommandSpecFromLibrary( + dependencyLibrary, + commandName, + commandArguments, + allowedExtensions, + projectContext.PackagesDirectory, + s_commandResolutionStrategy, + depsFilePath); + } + + private IEnumerable GetAllDependencyLibraries( + ProjectContext projectContext) + { + return projectContext.LibraryManager.GetLibraries() + .Where(l => l.GetType() == typeof(PackageDescription)) + .Select(l => l as PackageDescription) + .Select(p => p.Library); + } + + private ProjectContext GetProjectContextFromDirectory(string directory, NuGetFramework framework) + { + if (directory == null || framework == null) + { + return null; + } + + var projectRootPath = directory; + + if (!File.Exists(Path.Combine(projectRootPath, Project.FileName))) + { + return null; + } + + var projectContext = ProjectContext.Create( + projectRootPath, + framework, + PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers()); + + if (projectContext.RuntimeIdentifier == null) + { + return null; + } + + return projectContext; + } + + private IEnumerable GetAllowedCommandExtensionsFromEnvironment(IEnvironmentProvider environment) + { + var allowedCommandExtensions = new List(); + allowedCommandExtensions.AddRange(environment.ExecutableExtensions); + allowedCommandExtensions.Add(FileNameSuffixes.DotNet.DynamicLib); + + return allowedCommandExtensions; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectPathCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectPathCommandResolver.cs new file mode 100644 index 000000000..98809a5c5 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectPathCommandResolver.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class ProjectPathCommandResolver : AbstractPathBasedCommandResolver + { + public ProjectPathCommandResolver(IEnvironmentProvider environment, + IPlatformCommandSpecFactory commandSpecFactory) : base(environment, commandSpecFactory) { } + + internal override string ResolveCommandPath(CommandResolverArguments commandResolverArguments) + { + if (commandResolverArguments.ProjectDirectory == null) + { + return null; + } + + return _environment.GetCommandPathFromRootPath( + commandResolverArguments.ProjectDirectory, + commandResolverArguments.CommandName, + commandResolverArguments.InferredExtensions.EmptyIfNull()); + } + + internal override CommandResolutionStrategy GetCommandResolutionStrategy() + { + return CommandResolutionStrategy.ProjectLocal; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs new file mode 100644 index 000000000..20649af71 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class ProjectToolsCommandResolver : ICommandResolver + { + private static readonly NuGetFramework s_toolPackageFramework = FrameworkConstants.CommonFrameworks.NetStandardApp15; + private static readonly CommandResolutionStrategy s_commandResolutionStrategy = + CommandResolutionStrategy.ProjectToolsPackage; + + private List _allowedCommandExtensions; + private IPackagedCommandSpecFactory _packagedCommandSpecFactory; + + public ProjectToolsCommandResolver(IPackagedCommandSpecFactory packagedCommandSpecFactory) + { + _packagedCommandSpecFactory = packagedCommandSpecFactory; + + _allowedCommandExtensions = new List() + { + FileNameSuffixes.DotNet.DynamicLib + }; + } + + public CommandSpec Resolve(CommandResolverArguments commandResolverArguments) + { + return ResolveFromProjectTools( + commandResolverArguments.CommandName, + commandResolverArguments.CommandArguments, + commandResolverArguments.ProjectDirectory); + } + + private CommandSpec ResolveFromProjectTools( + string commandName, + IEnumerable args, + string projectDirectory) + { + var projectContext = GetProjectContextFromDirectory(projectDirectory, s_toolPackageFramework); + + if (projectContext == null) + { + return null; + } + + var toolsLibraries = projectContext.ProjectFile.Tools.EmptyIfNull(); + + return ResolveCommandSpecFromAllToolLibraries( + toolsLibraries, + commandName, + args, + projectContext); + } + + private CommandSpec ResolveCommandSpecFromAllToolLibraries( + IEnumerable toolsLibraries, + string commandName, + IEnumerable args, + ProjectContext projectContext) + { + foreach (var toolLibrary in toolsLibraries) + { + var commandSpec = ResolveCommandSpecFromToolLibrary(toolLibrary, commandName, args, projectContext); + + if (commandSpec != null) + { + return commandSpec; + } + } + + return null; + } + + private CommandSpec ResolveCommandSpecFromToolLibrary( + LibraryRange toolLibrary, + string commandName, + IEnumerable args, + ProjectContext projectContext) + { + //todo: change this for new resolution strategy + var lockFilePath = Path.Combine( + projectContext.ProjectDirectory, + "artifacts", "Tools", toolLibrary.Name, + "project.lock.json"); + + if (!File.Exists(lockFilePath)) + { + return null; + } + + var lockFile = LockFileReader.Read(lockFilePath); + + var lockFilePackageLibrary = lockFile.PackageLibraries.FirstOrDefault(l => l.Name == toolLibrary.Name); + + var nugetPackagesRoot = projectContext.PackagesDirectory; + + return _packagedCommandSpecFactory.CreateCommandSpecFromLibrary( + lockFilePackageLibrary, + commandName, + args, + _allowedCommandExtensions, + projectContext.PackagesDirectory, + s_commandResolutionStrategy, + null); + } + + private ProjectContext GetProjectContextFromDirectory(string directory, NuGetFramework framework) + { + if (directory == null || framework == null) + { + return null; + } + + var projectRootPath = directory; + + if (!File.Exists(Path.Combine(projectRootPath, Project.FileName))) + { + return null; + } + + var projectContext = ProjectContext.Create( + projectRootPath, + framework, + PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers()); + + if (projectContext.RuntimeIdentifier == null) + { + return null; + } + + return projectContext; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsPackageCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsPackageCommandResolver.cs new file mode 100644 index 000000000..fca3b52d2 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsPackageCommandResolver.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class ProjectToolsCommandResolver : ICommandResolver + { + private static readonly NuGetFramework s_toolPackageFramework = FrameworkConstants.CommonFrameworks.DnxCore50; + private static readonly CommandResolutionStrategy s_commandResolutionStrategy = + CommandResolutionStrategy.ProjectToolsPackage; + + private List _allowedCommandExtensions; + private IPackagedCommandSpecFactory _packagedCommandSpecFactory; + + public ProjectToolsCommandResolver(IPackagedCommandSpecFactory packagedCommandSpecFactory) + { + _packagedCommandSpecFactory = packagedCommandSpecFactory; + + _allowedCommandExtensions = new List() + { + FileNameSuffixes.DotNet.DynamicLib + }; + } + + public CommandSpec Resolve(CommandResolverArguments commandResolverArguments) + { + return ResolveFromProjectTools( + commandResolverArguments.CommandName, + commandResolverArguments.CommandArguments, + commandResolverArguments.ProjectDirectory); + } + + private CommandSpec ResolveFromProjectTools( + string commandName, + IEnumerable args, + string projectDirectory) + { + var projectContext = GetProjectContextFromDirectory(projectDirectory, s_toolPackageFramework); + + if (projectContext == null) + { + return null; + } + + var toolsLibraries = projectContext.ProjectFile.Tools.EmptyIfNull(); + + return ResolveCommandSpecFromAllToolLibraries( + toolsLibraries, + commandName, + args, + projectContext); + } + + private CommandSpec ResolveCommandSpecFromAllToolLibraries( + IEnumerable toolsLibraries, + string commandName, + IEnumerable args, + ProjectContext projectContext) + { + foreach (var toolLibrary in toolsLibraries) + { + var commandSpec = ResolveCommandSpecFromToolLibrary(toolLibrary, commandName, args, projectContext); + + if (commandSpec != null) + { + return commandSpec; + } + } + + return null; + } + + private CommandSpec ResolveCommandSpecFromToolLibrary( + LibraryRange toolLibrary, + string commandName, + IEnumerable args, + ProjectContext projectContext) + { + //todo: change this for new resolution strategy + var lockFilePath = Path.Combine( + projectContext.ProjectDirectory, + "artifacts", "Tools", toolLibrary.Name, + "project.lock.json"); + + if (!File.Exists(lockFilePath)) + { + return null; + } + + var lockFile = LockFileReader.Read(lockFilePath); + + var lockFilePackageLibrary = lockFile.PackageLibraries.FirstOrDefault(l => l.Name == toolLibrary.Name); + + var nugetPackagesRoot = projectContext.PackagesDirectory; + + return _packagedCommandSpecFactory.CreateCommandSpecFromLibrary( + lockFilePackageLibrary, + commandName, + args, + _allowedCommandExtensions, + projectContext.PackagesDirectory, + s_commandResolutionStrategy, + null); + } + + private ProjectContext GetProjectContextFromDirectory(string directory, NuGetFramework framework) + { + if (directory == null || framework == null) + { + return null; + } + + var projectRootPath = directory; + + if (!File.Exists(Path.Combine(projectRootPath, Project.FileName))) + { + return null; + } + + var projectContext = ProjectContext.Create( + projectRootPath, + framework, + PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers()); + + if (projectContext.RuntimeIdentifier == null) + { + return null; + } + + return projectContext; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/RootedCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/RootedCommandResolver.cs new file mode 100644 index 000000000..a363e18ce --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/RootedCommandResolver.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class RootedCommandResolver : ICommandResolver + { + public CommandSpec Resolve(CommandResolverArguments commandResolverArguments) + { + if (commandResolverArguments.CommandName == null) + { + return null; + } + + if (Path.IsPathRooted(commandResolverArguments.CommandName)) + { + var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart( + commandResolverArguments.CommandArguments.EmptyIfNull()); + + return new CommandSpec(commandResolverArguments.CommandName, escapedArgs, CommandResolutionStrategy.RootedPath); + } + + return null; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolver.cs new file mode 100644 index 000000000..a29c3ecfd --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolver.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class ScriptCommandResolver : CompositeCommandResolver + { + public static ScriptCommandResolver Create() + { + var environment = new EnvironmentProvider(); + + var platformCommandSpecFactory = default(IPlatformCommandSpecFactory); + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows) + { + platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + } + else + { + platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); + } + + return new ScriptCommandResolver(environment, platformCommandSpecFactory); + } + + public ScriptCommandResolver( + IEnvironmentProvider environment, + IPlatformCommandSpecFactory platformCommandSpecFactory) + { + AddCommandResolver(new RootedCommandResolver()); + AddCommandResolver(new ProjectPathCommandResolver(environment, platformCommandSpecFactory)); + AddCommandResolver(new AppBaseCommandResolver(environment, platformCommandSpecFactory)); + AddCommandResolver(new PathCommandResolver(environment, platformCommandSpecFactory)); + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/WindowsExePreferredCommandSpecFactory.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/WindowsExePreferredCommandSpecFactory.cs new file mode 100644 index 000000000..aacd356b9 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/WindowsExePreferredCommandSpecFactory.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.Extensions.PlatformAbstractions; +using NuGet.Frameworks; +using NuGet.Packaging; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class WindowsExePreferredCommandSpecFactory : IPlatformCommandSpecFactory + { + public CommandSpec CreateCommandSpec( + string commandName, + IEnumerable args, + string commandPath, + CommandResolutionStrategy resolutionStrategy, + IEnvironmentProvider environment) + { + var useCmdWrapper = false; + + if (Path.GetExtension(commandPath).Equals(".cmd", StringComparison.OrdinalIgnoreCase)) + { + var preferredCommandPath = environment.GetCommandPath(commandName, ".exe"); + + if (preferredCommandPath == null) + { + useCmdWrapper = true; + } + else + { + commandPath = preferredCommandPath; + } + } + + return useCmdWrapper + ? CreateCommandSpecWrappedWithCmd(commandPath, args, resolutionStrategy) + : CreateCommandSpecFromExecutable(commandPath, args, resolutionStrategy); + } + + private CommandSpec CreateCommandSpecFromExecutable( + string command, + IEnumerable args, + CommandResolutionStrategy resolutionStrategy) + { + var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); + return new CommandSpec(command, escapedArgs, resolutionStrategy); + } + + private CommandSpec CreateCommandSpecWrappedWithCmd( + string command, + IEnumerable args, + CommandResolutionStrategy resolutionStrategy) + { + var comSpec = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe"; + + // Handle the case where ComSpec is already the command + if (command.Equals(comSpec, StringComparison.OrdinalIgnoreCase)) + { + command = args.FirstOrDefault(); + args = args.Skip(1); + } + + var cmdEscapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForCmdProcessStart(args); + + if (ArgumentEscaper.ShouldSurroundWithQuotes(command)) + { + command = $"\"{command}\""; + } + + var escapedArgString = $"/s /c \"{command} {cmdEscapedArgs}\""; + + return new CommandSpec(comSpec, escapedArgString, resolutionStrategy); + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolutionStrategy.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolutionStrategy.cs deleted file mode 100644 index 83589d989..000000000 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolutionStrategy.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Microsoft.DotNet.Cli.Utils -{ - public enum CommandResolutionStrategy - { - //command loaded from a nuget package - NugetPackage, - - //command loaded from the same directory as the executing assembly - BaseDirectory, - - //command loaded from the same directory as a project.json file - ProjectLocal, - - //command loaded from path - Path, - - //command not found - None - } -} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs index 0c5b67724..4eba9460f 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs @@ -13,6 +13,7 @@ namespace Microsoft.DotNet.Cli.Utils { internal static class CommandResolver { +<<<<<<< HEAD public static CommandSpec TryResolveCommandSpec( string commandName, IEnumerable args, @@ -130,148 +131,55 @@ namespace Microsoft.DotNet.Cli.Utils var commandLibrary = context.ProjectFile.Tools .FirstOrDefault(l => l.Name == commandName); +======= + private static DefaultCommandResolver _defaultCommandResolver; + private static ScriptCommandResolver _scriptCommandResolver; +>>>>>>> 9c4329a... Refactor CommandResolver into individual CommandResolver Implementation - if (commandLibrary == default(LibraryRange)) - { - return null; - } - - var lockPath = Path.Combine(context.ProjectDirectory, "artifacts", "Tools", commandName, - "project.lock.json"); - - if (!File.Exists(lockPath)) - { - return null; - } - - var lockFile = LockFileReader.Read(lockPath); - - var lib = lockFile.PackageLibraries.FirstOrDefault(l => l.Name == commandName); - var packageDir = new VersionFolderPathResolver(context.PackagesDirectory) - .GetInstallPath(lib.Name, lib.Version); - - return Directory.Exists(packageDir) - ? ConfigureCommandFromPackage(commandName, args, lib.Files, packageDir) - : null; - } - - private static CommandSpec ConfigureCommandFromPackage(string commandName, IEnumerable args, string packageDir) + public static CommandSpec TryResolveCommandSpec( + string commandName, + IEnumerable args, + NuGetFramework framework = null, + string configuration=Constants.DefaultConfiguration, + string outputPath=null) { - var commandPackage = new PackageFolderReader(packageDir); + var commandResolverArgs = new CommandResolverArguments + { + CommandName = commandName, + CommandArguments = args, + Framework = framework, + ProjectDirectory = Directory.GetCurrentDirectory(), + Configuration = configuration + }; - var files = commandPackage.GetFiles(); + if (_defaultCommandResolver == null) + { + _defaultCommandResolver = DefaultCommandResolver.Create(); + } - return ConfigureCommandFromPackage(commandName, args, files, packageDir); + return _defaultCommandResolver.Resolve(commandResolverArgs); } - - private static CommandSpec ConfigureCommandFromPackage(string commandName, IEnumerable args, - PackageDescription commandPackage, ProjectContext projectContext, string depsPath = null) + + public static CommandSpec TryResolveScriptCommandSpec( + string commandName, + IEnumerable args, + Project project, + string[] inferredExtensionList) { - var files = commandPackage.Library.Files; - - var packageRoot = projectContext.PackagesDirectory; - - var packagePath = commandPackage.Path; - - var packageDir = Path.Combine(packageRoot, packagePath); - - return ConfigureCommandFromPackage(commandName, args, files, packageDir, depsPath); - } - - private static CommandSpec ConfigureCommandFromPackage(string commandName, IEnumerable args, - IEnumerable files, string packageDir, string depsPath = null) - { - var fileName = string.Empty; - - var commandPath = files - .FirstOrDefault(f => Env.ExecutableExtensions.Contains(Path.GetExtension(f))); - - if (commandPath == null) + var commandResolverArgs = new CommandResolverArguments { - var dllPath = files - .Where(f => Path.GetFileName(f) == commandName + FileNameSuffixes.DotNet.DynamicLib) - .Select(f => Path.Combine(packageDir, f)) - .FirstOrDefault(); + CommandName = commandName, + CommandArguments = args, + ProjectDirectory = project.ProjectDirectory, + InferredExtensions = inferredExtensionList + }; - fileName = CoreHost.HostExePath; - - var additionalArgs = new List(); - additionalArgs.Add(dllPath); - - if (depsPath != null) - { - additionalArgs.Add($"--depsfile:{depsPath}"); - } - - args = additionalArgs.Concat(args); - } - else + if (_scriptCommandResolver == null) { - fileName = Path.Combine(packageDir, commandPath); + _scriptCommandResolver = ScriptCommandResolver.Create(); } - var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); - return new CommandSpec(fileName, escapedArgs, CommandResolutionStrategy.NugetPackage); - } - - private static CommandSpec CreateCommandSpecPreferringExe( - string commandName, - IEnumerable args, - string commandPath, - CommandResolutionStrategy resolutionStrategy) - { - var useComSpec = false; - - if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows && - Path.GetExtension(commandPath).Equals(".cmd", StringComparison.OrdinalIgnoreCase)) - { - var preferredCommandPath = Env.GetCommandPath(commandName, ".exe"); - - // Use cmd if we can't find an exe - if (preferredCommandPath == null) - { - useComSpec = true; - } - else - { - commandPath = preferredCommandPath; - } - } - - if (useComSpec) - { - return CreateCmdCommandSpec(commandPath, args, resolutionStrategy); - } - else - { - var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); - return new CommandSpec(commandPath, escapedArgs, resolutionStrategy); - } - } - - private static CommandSpec CreateCmdCommandSpec( - string command, - IEnumerable args, - CommandResolutionStrategy resolutionStrategy) - { - var comSpec = Environment.GetEnvironmentVariable("ComSpec"); - - // Handle the case where ComSpec is already the command - if (command.Equals(comSpec, StringComparison.OrdinalIgnoreCase)) - { - command = args.FirstOrDefault(); - args = args.Skip(1); - } - var cmdEscapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForCmdProcessStart(args); - - if (ArgumentEscaper.ShouldSurroundWithQuotes(command)) - { - command = $"\"{command}\""; - } - - var escapedArgString = $"/s /c \"{command} {cmdEscapedArgs}\""; - - return new CommandSpec(comSpec, escapedArgString, resolutionStrategy); + return _scriptCommandResolver.Resolve(commandResolverArgs); } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandSpec.cs b/src/Microsoft.DotNet.Cli.Utils/CommandSpec.cs index 4ab5e8441..5b941515c 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandSpec.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandSpec.cs @@ -1,6 +1,6 @@ namespace Microsoft.DotNet.Cli.Utils { - internal class CommandSpec + public class CommandSpec { public CommandSpec(string path, string args, CommandResolutionStrategy resolutionStrategy) { diff --git a/src/Microsoft.DotNet.Cli.Utils/Env.cs b/src/Microsoft.DotNet.Cli.Utils/Env.cs index 6fdac60f1..30a117bcc 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Env.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Env.cs @@ -9,74 +9,29 @@ namespace Microsoft.DotNet.Cli.Utils { public static class Env { - private static IEnumerable _searchPaths; - private static IEnumerable _executableExtensions; + private static IEnvironmentProvider _environment = new EnvironmentProvider(); public static IEnumerable ExecutableExtensions { get { - if (_executableExtensions == null) - { - - _executableExtensions = PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows - ? Environment.GetEnvironmentVariable("PATHEXT") - .Split(';') - .Select(e => e.ToLower().Trim('"')) - : new [] { string.Empty }; - } - - return _executableExtensions; - } - } - - private static IEnumerable SearchPaths - { - get - { - if (_searchPaths == null) - { - var searchPaths = new List { PlatformServices.Default.Application.ApplicationBasePath }; - - searchPaths.AddRange(Environment - .GetEnvironmentVariable("PATH") - .Split(Path.PathSeparator) - .Select(p => p.Trim('"'))); - - _searchPaths = searchPaths; - } - - return _searchPaths; + return _environment.ExecutableExtensions; } } public static string GetCommandPath(string commandName, params string[] extensions) { - if (!extensions.Any()) - { - extensions = Env.ExecutableExtensions.ToArray(); - } - - var commandPath = Env.SearchPaths.Join( - extensions, - p => true, s => true, - (p, s) => Path.Combine(p, commandName + s)) - .FirstOrDefault(File.Exists); - - return commandPath; + return _environment.GetCommandPath(commandName, extensions); } public static string GetCommandPathFromRootPath(string rootPath, string commandName, params string[] extensions) { - if (!extensions.Any()) - { - extensions = Env.ExecutableExtensions.ToArray(); - } + return _environment.GetCommandPathFromRootPath(rootPath, commandName, extensions); + } - var commandPath = extensions.Select(e => Path.Combine(rootPath, commandName + e)) - .FirstOrDefault(File.Exists); - - return commandPath; + public static string GetCommandPathFromRootPath(string rootPath, string commandName, IEnumerable extensions) + { + return _environment.GetCommandPathFromRootPath(rootPath, commandName, extensions); } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs b/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs new file mode 100644 index 000000000..9cade5892 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class EnvironmentProvider : IEnvironmentProvider + { + private IEnumerable _searchPaths; + private IEnumerable _executableExtensions; + + public IEnumerable ExecutableExtensions + { + get + { + if (_executableExtensions == null) + { + + _executableExtensions = PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows + ? Environment.GetEnvironmentVariable("PATHEXT") + .Split(';') + .Select(e => e.ToLower().Trim('"')) + : new [] { string.Empty }; + } + + return _executableExtensions; + } + } + + private IEnumerable SearchPaths + { + get + { + if (_searchPaths == null) + { + var searchPaths = new List { PlatformServices.Default.Application.ApplicationBasePath }; + + searchPaths.AddRange(Environment + .GetEnvironmentVariable("PATH") + .Split(Path.PathSeparator) + .Select(p => p.Trim('"'))); + + _searchPaths = searchPaths; + } + + return _searchPaths; + } + } + + public EnvironmentProvider(IEnumerable extensionsOverride = null) + { + if (extensionsOverride != null) + { + _executableExtensions = extensionsOverride; + } + } + + public string GetCommandPath(string commandName, params string[] extensions) + { + if (!extensions.Any()) + { + extensions = ExecutableExtensions.ToArray(); + } + + var commandPath = SearchPaths.Join( + extensions, + p => true, s => true, + (p, s) => Path.Combine(p, commandName + s)) + .FirstOrDefault(File.Exists); + + return commandPath; + } + + public string GetCommandPathFromRootPath(string rootPath, string commandName, params string[] extensions) + { + if (!extensions.Any()) + { + extensions = ExecutableExtensions.ToArray(); + } + + var commandPath = extensions.Select(e => Path.Combine(rootPath, commandName + e)) + .FirstOrDefault(File.Exists); + + return commandPath; + } + + public string GetCommandPathFromRootPath(string rootPath, string commandName, IEnumerable extensions) + { + var extensionsArr = extensions.EmptyIfNull().ToArray(); + + return GetCommandPathFromRootPath(rootPath, commandName, extensionsArr); + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/Extensions/CollectionsExtensions.cs b/src/Microsoft.DotNet.Cli.Utils/Extensions/CollectionsExtensions.cs new file mode 100644 index 000000000..4f5b3d3bd --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/Extensions/CollectionsExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.DotNet.Cli.Utils +{ + public static class CollectionsExtensions + { + public static IEnumerable EmptyIfNull(this IEnumerable enumerable) + { + return enumerable == null + ? Enumerable.Empty() + : enumerable; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs b/src/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs new file mode 100644 index 000000000..db04ebc0b --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.DotNet.Cli.Utils +{ + public interface IEnvironmentProvider + { + IEnumerable ExecutableExtensions { get; } + + string GetCommandPath(string commandName, params string[] extensions); + + string GetCommandPathFromRootPath(string rootPath, string commandName, params string[] extensions); + + string GetCommandPathFromRootPath(string rootPath, string commandName, IEnumerable extensions); + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/CommandResolverTestUtils.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/CommandResolverTestUtils.cs new file mode 100644 index 000000000..24242b06f --- /dev/null +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/CommandResolverTestUtils.cs @@ -0,0 +1,25 @@ +// 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; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public static class CommandResolverTestUtils + { + public static void CreateNonRunnableTestCommand(string directory, string filename, string extension=".dll") + { + Directory.CreateDirectory(directory); + + var filePath = Path.Combine(directory, filename + extension); + + File.WriteAllText(filePath, "test command that does nothing."); + } + + public static IEnvironmentProvider SetupEnvironmentProviderWhichFindsExtensions(params string[] extensions) + { + return new EnvironmentProvider(extensions); + } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenACompositeCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenACompositeCommandResolver.cs new file mode 100644 index 000000000..988d9e319 --- /dev/null +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenACompositeCommandResolver.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Linq; +using Xunit; +using Moq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System.Threading; +using FluentAssertions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenACompositeCommandResolver + { + [Fact] + public void It_iterates_through_all_added_resolvers_in_order_when_they_return_null() + { + var compositeCommandResolver = new CompositeCommandResolver(); + + var resolverCalls = new List(); + + var mockResolver1 = new Mock(); + mockResolver1.Setup(r => r + .Resolve(It.IsAny())) + .Returns(default(CommandSpec)) + .Callback(() => resolverCalls.Add(1)); + + var mockResolver2 = new Mock(); + mockResolver2.Setup(r => r + .Resolve(It.IsAny())) + .Returns(default(CommandSpec)) + .Callback(() => resolverCalls.Add(2)); + + compositeCommandResolver.AddCommandResolver(mockResolver1.Object); + compositeCommandResolver.AddCommandResolver(mockResolver2.Object); + + compositeCommandResolver.Resolve(default(CommandResolverArguments)); + + resolverCalls.Should() + .HaveCount(2) + .And + .ContainInOrder(new [] {1, 2}); + + } + + [Fact] + public void It_stops_iterating_through_added_resolvers_when_one_returns_nonnull() + { + var compositeCommandResolver = new CompositeCommandResolver(); + + var resolverCalls = new List(); + + var mockResolver1 = new Mock(); + mockResolver1.Setup(r => r + .Resolve(It.IsAny())) + .Returns(new CommandSpec(null, null, default(CommandResolutionStrategy))) + .Callback(() => resolverCalls.Add(1)); + + var mockResolver2 = new Mock(); + mockResolver2.Setup(r => r + .Resolve(It.IsAny())) + .Returns(default(CommandSpec)) + .Callback(() => resolverCalls.Add(2)); + + compositeCommandResolver.AddCommandResolver(mockResolver1.Object); + compositeCommandResolver.AddCommandResolver(mockResolver2.Object); + + compositeCommandResolver.Resolve(default(CommandResolverArguments)); + + resolverCalls.Should() + .HaveCount(1) + .And + .ContainInOrder(new [] {1}); + + } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs new file mode 100644 index 000000000..20adb9293 --- /dev/null +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs @@ -0,0 +1,206 @@ +// 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 System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Linq; +using Xunit; +using Moq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System.Threading; +using FluentAssertions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenADefaultCommandResolver + { + [Fact] + public void It_contains_resolvers_in_the_right_order() + { + var defaultCommandResolver = DefaultCommandResolver.Create(); + + var resolvers = defaultCommandResolver.OrderedCommandResolvers; + + resolvers.Should().HaveCount(5); + + resolvers.Select(r => r.GetType()) + .Should() + .ContainInOrder( + new []{ + typeof(RootedCommandResolver), + typeof(ProjectDependenciesCommandResolver), + typeof(ProjectToolsCommandResolver), + typeof(AppBaseCommandResolver), + typeof(PathCommandResolver) + }); + } + + + // [Fact] + // public void It_Resolves_Rooted_Commands_Correctly() + // { + // var path = Path.Combine(AppContext.BaseDirectory, "rooteddir"); + // Directory.CreateDirectory(path); + + // var testCommandPath = CreateTestCommandFile(path, ".dll", "rootedcommand"); + + // var defaultCommandResolver = new DefaultCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = testCommandPath, + // CommandArguments = new string[] {} + // }; + + // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().NotBeNull(); + + // commandSpec.Path.Should().Be(testCommandPath); + // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.RootedPath); + // } + + // [Fact] + // public void It_Resolves_AppBase_Commands_Correctly() + // { + // var testCommandPath = CreateTestCommandFile(AppContext.BaseDirectory, ".exe", "appbasecommand"); + // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); + + // var defaultCommandResolver = new DefaultCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = testCommandName, + // CommandArguments = new string[] {}, + // Environment = new EnvironmentProvider() + // }; + + // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().NotBeNull(); + + // commandSpec.Path.Should().Be(testCommandPath); + // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.BaseDirectory); + // } + + // [Fact] + // public void It_Resolves_PATH_Commands_Correctly() + // { + // var path = Path.Combine(AppContext.BaseDirectory, "pathdir"); + // var testCommandPath = CreateTestCommandFile(path, ".dll", "pathcommmand"); + // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); + + // Mock mockEnvironment = new Mock(); + // mockEnvironment.Setup(c => c + // .GetCommandPath(It.IsAny(), It.IsAny())) + // .Returns(testCommandPath); + + // var defaultCommandResolver = new DefaultCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = testCommandName, + // CommandArguments = new string[] {}, + // Environment = mockEnvironment.Object + // }; + + // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().NotBeNull(); + + // commandSpec.Path.Should().Be(testCommandPath); + // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.Path); + // } + + // [Fact] + // public void It_Resolves_Project_Tools_Commands_Correctly() + // { + // var testAppPath = Path.Combine(AppContext.BaseDirectory, + // "TestAssets/TestProjects/AppWithToolDependency"); + + // var defaultCommandResolver = new DefaultCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = "dotnet-hello", + // CommandArguments = new string[] {}, + // ProjectDirectory = testAppPath, + // Environment = new EnvironmentProvider() + // }; + + // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().NotBeNull(); + + // commandSpec.Path.Should().NotBeNull(); + // commandSpec.Args.Should().NotContain("--depsfile"); + // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.NugetPackage); + // } + + // [Fact] + // public void It_Resolves_Project_Dependencies_Commands_Correctly() + // { + // var testAppPath = Path.Combine(AppContext.BaseDirectory, + // "TestAssets/TestProjects/AppWithDirectDependency"); + + // var defaultCommandResolver = new DefaultCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = "dotnet-hello", + // CommandArguments = new string[] {}, + // ProjectDirectory = testAppPath, + // Environment = new EnvironmentProvider(), + // Framework = FrameworkConstants.CommonFrameworks.DnxCore50, + // Configuration = "Debug" + // }; + + + // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().NotBeNull(); + + // commandSpec.Path.Should().NotBeNull(); + // commandSpec.Args.Should().Contain("--depsfile"); + // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.NugetPackage); + // } + + // [Fact] + // public void It_does_not_resolve_ProjectLocal_commands() + // { + // var path = Path.Combine(AppContext.BaseDirectory, + // "testdir"); + + // var testCommandPath = CreateTestCommandFile(path, ".exe", "projectlocalcommand"); + // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); + + // var defaultCommandResolver = new DefaultCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = testCommandName, + // CommandArguments = new string[] {}, + // ProjectDirectory = path, + // Environment = new EnvironmentProvider() + // }; + + // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); + + // commandSpec.Should().Be(null); + // } + + // public string CreateTestCommandFile(string path, string extension, string name = "testcommand") + // { + // Directory.CreateDirectory(path); + + // var filename = name + extension; + // var filepath = Path.Combine(path, filename); + + // File.WriteAllText(filepath, "hello world"); + + // return filepath; + // } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAPathCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAPathCommandResolver.cs new file mode 100644 index 000000000..e69de29bb diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectDependencyCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectDependencyCommandResolver.cs new file mode 100644 index 000000000..e69de29bb diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectPathCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectPathCommandResolver.cs new file mode 100644 index 000000000..1117f1b43 --- /dev/null +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectPathCommandResolver.cs @@ -0,0 +1,239 @@ +// 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 System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Linq; +using Xunit; +using Moq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System.Threading; +using FluentAssertions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenAProjectPathCommandResolver + { + private static readonly string s_testProjectDirectory = Path.Combine(AppContext.BaseDirectory, "testprojectdirectory"); + + [Fact] + public void It_returns_null_when_CommandName_is_null() + { + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = null, + CommandArguments = new string[] {""}, + ProjectDirectory = "/some/directory" + }; + + var result = projectPathCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_ProjectDirectory_is_null() + { + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "command", + CommandArguments = new string[] {""}, + ProjectDirectory = null + }; + + var result = projectPathCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_CommandName_does_not_exist_in_ProjectDirectory() + { + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "nonexistent-command", + CommandArguments = null, + ProjectDirectory = s_testProjectDirectory + }; + + var result = projectPathCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_CommandName_exists_in_a_subdirectory_of_ProjectDirectory() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment); + + var testDir = Path.Combine(s_testProjectDirectory, "projectpathtestsubdir"); + CommandResolverTestUtils.CreateNonRunnableTestCommand(testDir, "projectpathtestsubdircommand", ".exe"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "projectpathtestsubdircommand", + CommandArguments = null, + ProjectDirectory = s_testProjectDirectory + }; + + var result = projectPathCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_a_CommandSpec_with_CommandName_as_FileName_when_CommandName_exists_in_ProjectDirectory() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testProjectDirectory, "projectpathtestcommand1", ".exe"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "projectpathtestcommand1", + CommandArguments = null, + ProjectDirectory = s_testProjectDirectory + }; + + var result = projectPathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileNameWithoutExtension(result.Path); + + commandFile.Should().Be("projectpathtestcommand1"); + } + + [Fact] + public void It_escapes_CommandArguments_when_returning_a_CommandSpec() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testProjectDirectory, "projectpathtestcommand1", ".exe"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "projectpathtestcommand1", + CommandArguments = new [] { "arg with space"}, + ProjectDirectory = s_testProjectDirectory + }; + + var result = projectPathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Args.Should().Be("\"arg with space\""); + } + + [Fact] + public void It_resolves_commands_with_extensions_defined_in_InferredExtensions() + { + var extensions = new string[] {".sh", ".cmd", ".foo", ".exe"}; + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(); + + foreach (var extension in extensions) + { + var extensionTestDir = Path.Combine(s_testProjectDirectory, "testext" + extension); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(extensionTestDir, "projectpathexttest", extension); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "projectpathexttest", + CommandArguments = null, + ProjectDirectory = extensionTestDir, + InferredExtensions = extensions + }; + + var result = projectPathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFileName = Path.GetFileName(result.Path); + commandFileName.Should().Be("projectpathexttest" + extension); + } + } + + [Fact] + public void It_returns_a_CommandSpec_with_Args_as_stringEmpty_when_returning_a_CommandSpec_and_CommandArguments_are_null() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testProjectDirectory, "projectpathtestcommand1", ".exe"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "projectpathtestcommand1", + CommandArguments = null, + ProjectDirectory = s_testProjectDirectory + }; + + var result = projectPathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Args.Should().Be(string.Empty); + } + + [Fact] + public void It_prefers_EXE_over_CMD_when_two_command_candidates_exist_and_using_WindowsExePreferredCommandSpecFactory() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + + var projectPathCommandResolver = new ProjectPathCommandResolver(environment, platformCommandSpecFactory); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testProjectDirectory, "projectpathtestcommand1", ".exe"); + CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testProjectDirectory, "projectpathtestcommand1", ".cmd"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "projectpathtestcommand1", + CommandArguments = null, + ProjectDirectory = s_testProjectDirectory + }; + + var result = projectPathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileName(result.Path); + commandFile.Should().Be("projectpathtestcommand1.exe"); + } + + private ProjectPathCommandResolver SetupPlatformProjectPathCommandResolver(IEnvironmentProvider environment = null) + { + environment = environment ?? new EnvironmentProvider(); + + var platformCommandSpecFactory = default(IPlatformCommandSpecFactory); + + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows) + { + platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + } + else + { + platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); + } + + var projectPathCommandResolver = new ProjectPathCommandResolver(environment, platformCommandSpecFactory); + + return projectPathCommandResolver; + } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs new file mode 100644 index 000000000..e69de29bb diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenARootedCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenARootedCommandResolver.cs new file mode 100644 index 000000000..bcaba5148 --- /dev/null +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenARootedCommandResolver.cs @@ -0,0 +1,111 @@ +// 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 System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Linq; +using Xunit; +using Moq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System.Threading; +using FluentAssertions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenARootedCommandResolver + { + [Fact] + public void It_returns_null_when_CommandName_is_null() + { + var rootedCommandResolver = new RootedCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = null, + CommandArguments = null + }; + + var result = rootedCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_CommandName_is_not_rooted() + { + var rootedCommandResolver = new RootedCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "some/relative/path", + CommandArguments = null + }; + + var result = rootedCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_a_CommandSpec_with_CommandName_as_Path_when_CommandName_is_rooted() + { + var rootedCommandResolver = new RootedCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "/some/rooted/path", + CommandArguments = null + }; + + var result = rootedCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Path.Should().Be(commandResolverArguments.CommandName); + } + + [Fact] + public void It_escapes_CommandArguments_when_returning_a_CommandSpec() + { + var rootedCommandResolver = new RootedCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "/some/rooted/path", + CommandArguments = new [] { "arg with space"} + }; + + var result = rootedCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Path.Should().Be(commandResolverArguments.CommandName); + + result.Args.Should().Be("\"arg with space\""); + } + + [Fact] + public void It_returns_a_CommandSpec_with_Args_as_stringEmpty_when_returning_a_CommandSpec_and_CommandArguments_are_null() + { + var rootedCommandResolver = new RootedCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "/some/rooted/path", + CommandArguments = null + }; + + var result = rootedCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Path.Should().Be(commandResolverArguments.CommandName); + + result.Args.Should().Be(string.Empty); + } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs new file mode 100644 index 000000000..e5b852456 --- /dev/null +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs @@ -0,0 +1,199 @@ +// 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 System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; +using Moq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System.Threading; +using FluentAssertions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenAScriptCommandResolver + { + [Fact] + public void It_contains_resolvers_in_the_right_order() + { + var scriptCommandResolver = ScriptCommandResolver.Create(); + + var resolvers = scriptCommandResolver.OrderedCommandResolvers; + + resolvers.Should().HaveCount(4); + + resolvers.Select(r => r.GetType()) + .Should() + .ContainInOrder( + new []{ + typeof(RootedCommandResolver), + typeof(ProjectPathCommandResolver), + typeof(AppBaseCommandResolver), + typeof(PathCommandResolver) + }); + } + + // [Fact] + // public void It_Resolves_Rooted_Commands_Correctly() + // { + // var path = Path.Combine(AppContext.BaseDirectory, "rooteddir"); + // Directory.CreateDirectory(path); + + // var testCommandPath = CreateTestCommandFile(path, ".dll", "scriptrootedcommand"); + + // var scriptCommandResolver = new ScriptCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = testCommandPath, + // CommandArguments = new string[] {} + // }; + + // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().NotBeNull(); + + // commandSpec.Path.Should().Be(testCommandPath); + // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.RootedPath); + // } + + // [Fact] + // public void It_Resolves_AppBase_Commands_Correctly() + // { + // var testCommandPath = CreateTestCommandFile(AppContext.BaseDirectory, ".exe", "scriptappbasecommand"); + // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); + + // var scriptCommandResolver = new ScriptCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = testCommandName, + // CommandArguments = new string[] {}, + // Environment = new EnvironmentProvider() + // }; + + // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().NotBeNull(); + + // commandSpec.Path.Should().Be(testCommandPath); + // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.BaseDirectory); + // } + + // [Fact] + // public void It_Resolves_PATH_Commands_Correctly() + // { + // var path = Path.Combine(AppContext.BaseDirectory, "pathdir"); + // var testCommandPath = CreateTestCommandFile(path, ".dll", "scriptpathcommmand"); + // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); + + // Mock mockEnvironment = new Mock(); + // mockEnvironment.Setup(c => c + // .GetCommandPath(It.IsAny(), It.IsAny())) + // .Returns(testCommandPath); + + // var scriptCommandResolver = new ScriptCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = testCommandName, + // CommandArguments = new string[] {}, + // Environment = mockEnvironment.Object + // }; + + // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().NotBeNull(); + + // commandSpec.Path.Should().Be(testCommandPath); + // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.Path); + // } + + // [Fact] + // public void It_does_NOT_Resolve_Project_Tools_Commands() + // { + // var testAppPath = Path.Combine(AppContext.BaseDirectory, + // "TestAssets/TestProjects/AppWithToolDependency"); + + // var scriptCommandResolver = new ScriptCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = "dotnet-hello", + // CommandArguments = new string[] {}, + // ProjectDirectory = testAppPath, + // Environment = new EnvironmentProvider() + // }; + + // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().BeNull(); + // } + + // [Fact] + // public void It_does_NOT_Resolve_Project_Dependencies_Commands() + // { + // var testAppPath = Path.Combine(AppContext.BaseDirectory, + // "TestAssets/TestProjects/AppWithDirectDependency"); + + // var scriptCommandResolver = new ScriptCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = "dotnet-hello", + // CommandArguments = new string[] {}, + // ProjectDirectory = testAppPath, + // Environment = new EnvironmentProvider(), + // Framework = FrameworkConstants.CommonFrameworks.DnxCore50, + // Configuration = "Debug" + // }; + + + // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); + // commandSpec.Should().BeNull(); + // } + + // [Fact] + // public void It_resolves_ProjectLocal_commands_correctly() + // { + // var path = Path.Combine(AppContext.BaseDirectory, + // "testdir"); + + // var testCommandPath = CreateTestCommandFile(path, ".exe", "scriptprojectlocalcommand"); + // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); + + // var scriptCommandResolver = new ScriptCommandResolver(); + + // var commandResolverArgs = new CommandResolverArguments + // { + // CommandName = testCommandName, + // CommandArguments = new string[] {}, + // ProjectDirectory = path, + // Environment = new EnvironmentProvider() + // }; + + // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); + + // commandSpec.Should().NotBeNull(); + // commandSpec.Path.Should().Be(testCommandPath); + // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.ProjectLocal); + + // } + + // public string CreateTestCommandFile(string path, string extension, string name = "testcommand") + // { + // Directory.CreateDirectory(path); + + // var filename = name + extension; + // var filepath = Path.Combine(path, filename); + + // File.WriteAllText(filepath, "hello world"); + + // return filepath; + // } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAnAppBaseCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAnAppBaseCommandResolver.cs new file mode 100644 index 000000000..15ef2fe2d --- /dev/null +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAnAppBaseCommandResolver.cs @@ -0,0 +1,184 @@ +// 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 System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Linq; +using Xunit; +using Moq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System.Threading; +using FluentAssertions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenAnAppBaseCommandResolver + { + [Fact] + public void It_returns_null_when_CommandName_is_null() + { + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = null, + CommandArguments = null + }; + + var result = appBaseCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_CommandName_does_not_exist_applocal() + { + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "nonexistent-command", + CommandArguments = null + }; + + var result = appBaseCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_a_CommandSpec_with_CommandName_as_FileName_when_CommandName_exists_applocal() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(AppContext.BaseDirectory, "appbasetestcommand1", ".exe"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "appbasetestcommand1", + CommandArguments = null + }; + + var result = appBaseCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileNameWithoutExtension(result.Path); + + commandFile.Should().Be("appbasetestcommand1"); + } + + [Fact] + public void It_returns_null_when_CommandName_exists_applocal_in_a_subdirectory() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment); + + var testDir = Path.Combine(AppContext.BaseDirectory, "appbasetestsubdir"); + CommandResolverTestUtils.CreateNonRunnableTestCommand(testDir, "appbasetestsubdircommand", ".exe"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "appbasetestsubdircommand", + CommandArguments = null + }; + + var result = appBaseCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_escapes_CommandArguments_when_returning_a_CommandSpec() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(AppContext.BaseDirectory, "appbasetestcommand1", ".exe"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "appbasetestcommand1", + CommandArguments = new [] { "arg with space"} + }; + + var result = appBaseCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Args.Should().Be("\"arg with space\""); + } + + [Fact] + public void It_returns_a_CommandSpec_with_Args_as_stringEmpty_when_returning_a_CommandSpec_and_CommandArguments_are_null() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(AppContext.BaseDirectory, "appbasetestcommand1", ".exe"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "appbasetestcommand1", + CommandArguments = null + }; + + var result = appBaseCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Args.Should().Be(string.Empty); + } + + [Fact] + public void It_prefers_EXE_over_CMD_when_two_command_candidates_exist_and_using_WindowsExePreferredCommandSpecFactory() + { + var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); + var platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + + var appBaseCommandResolver = new AppBaseCommandResolver(environment, platformCommandSpecFactory); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(AppContext.BaseDirectory, "appbasetestcommand1", ".exe"); + CommandResolverTestUtils.CreateNonRunnableTestCommand(AppContext.BaseDirectory, "appbasetestcommand1", ".cmd"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "appbasetestcommand1", + CommandArguments = null + }; + + var result = appBaseCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileName(result.Path); + commandFile.Should().Be("appbasetestcommand1.exe"); + } + + private AppBaseCommandResolver SetupPlatformAppBaseCommandResolver(IEnvironmentProvider environment = null) + { + environment = environment ?? new EnvironmentProvider(); + + var platformCommandSpecFactory = default(IPlatformCommandSpecFactory); + + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows) + { + platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + } + else + { + platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); + } + + var appBaseCommandResolver = new AppBaseCommandResolver(environment, platformCommandSpecFactory); + + return appBaseCommandResolver; + } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/project.json b/test/Microsoft.DotNet.Cli.Utils.Tests/project.json index 4aae182ed..97536b3c7 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/project.json +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/project.json @@ -13,6 +13,7 @@ "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "moq.netcore": "4.4.0-beta8", "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-dev-79755-47" }, @@ -27,7 +28,11 @@ }, "content": [ - "../../TestAssets/TestProjects/OutputStandardOutputAndError/*" + "../../TestAssets/TestProjects/OutputStandardOutputAndError/*", + "../../TestAssets/TestProjects/TestAppWithArgs/*", + "../../TestAssets/TestProjects/AppWithDirectAndToolDependency/**/*", + "../../TestAssets/TestProjects/AppWithDirectDependency/**/*", + "../../TestAssets/TestProjects/AppWithToolDependency/**/*" ], "testRunner": "xunit" From b813e2b849f02d64fc3c69cda7b7cec1af432194 Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Thu, 3 Mar 2016 15:31:04 -0800 Subject: [PATCH 05/13] Fixes #1649 make things work again fix Tests Passing cleanup fix fix --- .../AppWithToolDependency/project.json | 2 +- src/Microsoft.DotNet.Cli.Utils/Command.cs | 5 + .../CommandResolverArguments.cs | 2 + .../DefaultCommandResolver.cs | 1 - .../ProjectDependenciesCommandResolver.cs | 8 +- .../ProjectToolsPackageCommandResolver.cs | 141 --------------- .../CommandResolver.cs | 123 +------------ .../FixedPathCommandFactory.cs | 41 ----- .../ProjectDependenciesCommandFactory.cs | 92 ++++++++++ src/dotnet/commands/dotnet-test/Program.cs | 22 ++- .../GivenADefaultCommandResolver.cs | 164 +----------------- .../GivenAScriptCommandResolver.cs | 155 ----------------- .../Assertions/CommandResultAssertions.cs | 7 + test/dotnet.Tests/PackagedCommandTests.cs | 35 +++- 14 files changed, 163 insertions(+), 635 deletions(-) delete mode 100644 src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsPackageCommandResolver.cs delete mode 100644 src/Microsoft.DotNet.Cli.Utils/FixedPathCommandFactory.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/ProjectDependenciesCommandFactory.cs diff --git a/TestAssets/TestProjects/AppWithToolDependency/project.json b/TestAssets/TestProjects/AppWithToolDependency/project.json index f8982d688..9537feb8f 100644 --- a/TestAssets/TestProjects/AppWithToolDependency/project.json +++ b/TestAssets/TestProjects/AppWithToolDependency/project.json @@ -15,6 +15,6 @@ }, "tools": { - "dotnet-hello": { "version": "1.0.0", "target": "package" } + "dotnet-hello": { "version": "2.0.0", "target": "package" } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/Command.cs b/src/Microsoft.DotNet.Cli.Utils/Command.cs index 3779deeb0..6bc62c57c 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Command.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Command.cs @@ -85,6 +85,11 @@ namespace Microsoft.DotNet.Cli.Utils return command; } + + public static Command Create(CommandSpec commandSpec) + { + return new Command(commandSpec); + } public static Command CreateForScript( string commandName, diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs index 2e2c5ee93..e888efc90 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs @@ -15,6 +15,8 @@ namespace Microsoft.DotNet.Cli.Utils public NuGetFramework Framework { get; set; } + public string OutputPath { get; set; } + public string ProjectDirectory { get; set; } public string Configuration { get; set; } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs index 1fdcb83ff..74239cbc4 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs @@ -37,7 +37,6 @@ namespace Microsoft.DotNet.Cli.Utils IPlatformCommandSpecFactory platformCommandSpecFactory) : base() { AddCommandResolver(new RootedCommandResolver()); - AddCommandResolver(new ProjectDependenciesCommandResolver(environment, packagedCommandSpecFactory)); AddCommandResolver(new ProjectToolsCommandResolver(packagedCommandSpecFactory)); AddCommandResolver(new AppBaseCommandResolver(environment, platformCommandSpecFactory)); AddCommandResolver(new PathCommandResolver(environment, platformCommandSpecFactory)); diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs index 0c96fc2b2..ac51862b7 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs @@ -52,7 +52,8 @@ namespace Microsoft.DotNet.Cli.Utils commandResolverArguments.Framework, commandResolverArguments.Configuration, commandResolverArguments.CommandName, - commandResolverArguments.CommandArguments); + commandResolverArguments.CommandArguments, + commandResolverArguments.OutputPath); } private CommandSpec ResolveFromProjectDependencies( @@ -60,7 +61,8 @@ namespace Microsoft.DotNet.Cli.Utils NuGetFramework framework, string configuration, string commandName, - IEnumerable commandArguments) + IEnumerable commandArguments, + string outputPath) { var allowedExtensions = GetAllowedCommandExtensionsFromEnvironment(_environment); @@ -73,7 +75,7 @@ namespace Microsoft.DotNet.Cli.Utils return null; } - var depsFilePath = projectContext.GetOutputPaths(configuration).RuntimeFiles.Deps; + var depsFilePath = projectContext.GetOutputPaths(configuration, outputPath: outputPath).RuntimeFiles.Deps; var dependencyLibraries = GetAllDependencyLibraries(projectContext); diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsPackageCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsPackageCommandResolver.cs deleted file mode 100644 index fca3b52d2..000000000 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsPackageCommandResolver.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using Microsoft.DotNet.ProjectModel; -using Microsoft.DotNet.ProjectModel.Graph; -using Microsoft.Extensions.PlatformAbstractions; -using NuGet.Frameworks; -using NuGet.Packaging; - -namespace Microsoft.DotNet.Cli.Utils -{ - public class ProjectToolsCommandResolver : ICommandResolver - { - private static readonly NuGetFramework s_toolPackageFramework = FrameworkConstants.CommonFrameworks.DnxCore50; - private static readonly CommandResolutionStrategy s_commandResolutionStrategy = - CommandResolutionStrategy.ProjectToolsPackage; - - private List _allowedCommandExtensions; - private IPackagedCommandSpecFactory _packagedCommandSpecFactory; - - public ProjectToolsCommandResolver(IPackagedCommandSpecFactory packagedCommandSpecFactory) - { - _packagedCommandSpecFactory = packagedCommandSpecFactory; - - _allowedCommandExtensions = new List() - { - FileNameSuffixes.DotNet.DynamicLib - }; - } - - public CommandSpec Resolve(CommandResolverArguments commandResolverArguments) - { - return ResolveFromProjectTools( - commandResolverArguments.CommandName, - commandResolverArguments.CommandArguments, - commandResolverArguments.ProjectDirectory); - } - - private CommandSpec ResolveFromProjectTools( - string commandName, - IEnumerable args, - string projectDirectory) - { - var projectContext = GetProjectContextFromDirectory(projectDirectory, s_toolPackageFramework); - - if (projectContext == null) - { - return null; - } - - var toolsLibraries = projectContext.ProjectFile.Tools.EmptyIfNull(); - - return ResolveCommandSpecFromAllToolLibraries( - toolsLibraries, - commandName, - args, - projectContext); - } - - private CommandSpec ResolveCommandSpecFromAllToolLibraries( - IEnumerable toolsLibraries, - string commandName, - IEnumerable args, - ProjectContext projectContext) - { - foreach (var toolLibrary in toolsLibraries) - { - var commandSpec = ResolveCommandSpecFromToolLibrary(toolLibrary, commandName, args, projectContext); - - if (commandSpec != null) - { - return commandSpec; - } - } - - return null; - } - - private CommandSpec ResolveCommandSpecFromToolLibrary( - LibraryRange toolLibrary, - string commandName, - IEnumerable args, - ProjectContext projectContext) - { - //todo: change this for new resolution strategy - var lockFilePath = Path.Combine( - projectContext.ProjectDirectory, - "artifacts", "Tools", toolLibrary.Name, - "project.lock.json"); - - if (!File.Exists(lockFilePath)) - { - return null; - } - - var lockFile = LockFileReader.Read(lockFilePath); - - var lockFilePackageLibrary = lockFile.PackageLibraries.FirstOrDefault(l => l.Name == toolLibrary.Name); - - var nugetPackagesRoot = projectContext.PackagesDirectory; - - return _packagedCommandSpecFactory.CreateCommandSpecFromLibrary( - lockFilePackageLibrary, - commandName, - args, - _allowedCommandExtensions, - projectContext.PackagesDirectory, - s_commandResolutionStrategy, - null); - } - - private ProjectContext GetProjectContextFromDirectory(string directory, NuGetFramework framework) - { - if (directory == null || framework == null) - { - return null; - } - - var projectRootPath = directory; - - if (!File.Exists(Path.Combine(projectRootPath, Project.FileName))) - { - return null; - } - - var projectContext = ProjectContext.Create( - projectRootPath, - framework, - PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers()); - - if (projectContext.RuntimeIdentifier == null) - { - return null; - } - - return projectContext; - } - } -} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs index 4eba9460f..b8261dea6 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs @@ -13,128 +13,8 @@ namespace Microsoft.DotNet.Cli.Utils { internal static class CommandResolver { -<<<<<<< HEAD - public static CommandSpec TryResolveCommandSpec( - string commandName, - IEnumerable args, - NuGetFramework framework = null, - string configuration = Constants.DefaultConfiguration, - string outputPath = null) - { - return ResolveFromRootedCommand(commandName, args) ?? - ResolveFromProjectDependencies(commandName, args, framework, configuration, outputPath) ?? - ResolveFromProjectTools(commandName, args) ?? - ResolveFromAppBase(commandName, args) ?? - ResolveFromPath(commandName, args); - } - - public static CommandSpec TryResolveScriptCommandSpec(string commandName, IEnumerable args, Project project, string[] inferredExtensionList) - { - return ResolveFromRootedCommand(commandName, args) ?? - ResolveFromProjectPath(commandName, args, project, inferredExtensionList) ?? - ResolveFromAppBase(commandName, args) ?? - ResolveFromPath(commandName, args); - } - - - private static CommandSpec ResolveFromPath(string commandName, IEnumerable args) - { - var commandPath = Env.GetCommandPath(commandName); - return commandPath == null - ? null - : CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.Path); - } - - private static CommandSpec ResolveFromAppBase(string commandName, IEnumerable args) - { - var commandPath = Env.GetCommandPathFromRootPath(PlatformServices.Default.Application.ApplicationBasePath, commandName); - return commandPath == null - ? null - : CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.BaseDirectory); - } - - private static CommandSpec ResolveFromProjectPath(string commandName, IEnumerable args, Project project, string[] inferredExtensionList) - { - var commandPath = Env.GetCommandPathFromRootPath(project.ProjectDirectory, commandName, inferredExtensionList); - return commandPath == null - ? null - : CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.ProjectLocal); - } - - private static CommandSpec ResolveFromRootedCommand(string commandName, IEnumerable args) - { - if (Path.IsPathRooted(commandName)) - { - var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); - return new CommandSpec(commandName, escapedArgs, CommandResolutionStrategy.Path); - } - - return null; - } - - public static CommandSpec ResolveFromProjectDependencies( - string commandName, - IEnumerable args, - NuGetFramework framework, - string configuration, - string outputPath) - { - if (framework == null) return null; - - var projectContext = GetProjectContext(framework); - - if (projectContext == null) return null; - - var commandPackage = GetCommandPackage(projectContext, commandName); - - if (commandPackage == null) return null; - - var depsPath = projectContext.GetOutputPaths(configuration, outputPath: outputPath).RuntimeFiles.Deps; - - return ConfigureCommandFromPackage(commandName, args, commandPackage, projectContext, depsPath); - } - - private static ProjectContext GetProjectContext(NuGetFramework framework) - { - var projectRootPath = Directory.GetCurrentDirectory(); - - if (!File.Exists(Path.Combine(projectRootPath, Project.FileName))) - { - return null; - } - - var projectContext = ProjectContext.Create(projectRootPath, framework, PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers()); - return projectContext; - } - - private static PackageDescription GetCommandPackage(ProjectContext projectContext, string commandName) - { - return projectContext.LibraryManager.GetLibraries() - .Where(l => l.GetType() == typeof(PackageDescription)) - .Select(l => l as PackageDescription) - .FirstOrDefault(p => p.Library.Files - .Select(Path.GetFileName) - .Where(f => Path.GetFileNameWithoutExtension(f) == commandName) - .Select(Path.GetExtension) - .Any(e => Env.ExecutableExtensions.Contains(e) || - e == FileNameSuffixes.DotNet.DynamicLib)); - } - - public static CommandSpec ResolveFromProjectTools(string commandName, IEnumerable args) - { - var context = GetProjectContext(FrameworkConstants.CommonFrameworks.NetStandardApp15); - - if (context == null) - { - return null; - } - - var commandLibrary = context.ProjectFile.Tools - .FirstOrDefault(l => l.Name == commandName); -======= private static DefaultCommandResolver _defaultCommandResolver; private static ScriptCommandResolver _scriptCommandResolver; ->>>>>>> 9c4329a... Refactor CommandResolver into individual CommandResolver Implementation public static CommandSpec TryResolveCommandSpec( string commandName, @@ -149,7 +29,8 @@ namespace Microsoft.DotNet.Cli.Utils CommandArguments = args, Framework = framework, ProjectDirectory = Directory.GetCurrentDirectory(), - Configuration = configuration + Configuration = configuration, + OutputPath = outputPath }; if (_defaultCommandResolver == null) diff --git a/src/Microsoft.DotNet.Cli.Utils/FixedPathCommandFactory.cs b/src/Microsoft.DotNet.Cli.Utils/FixedPathCommandFactory.cs deleted file mode 100644 index f0eab5391..000000000 --- a/src/Microsoft.DotNet.Cli.Utils/FixedPathCommandFactory.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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 NuGet.Frameworks; - -namespace Microsoft.DotNet.Cli.Utils -{ - public class FixedPathCommandFactory : ICommandFactory - { - private readonly NuGetFramework _nugetFramework; - private readonly string _configuration; - private readonly string _outputPath; - - public FixedPathCommandFactory(NuGetFramework nugetFramework, string configuration, string outputPath) - { - _nugetFramework = nugetFramework; - _configuration = configuration; - _outputPath = outputPath; - } - - public ICommand Create( - string commandName, - IEnumerable args, - NuGetFramework framework = null, - string configuration = Constants.DefaultConfiguration) - { - if (string.IsNullOrEmpty(configuration)) - { - configuration = _configuration; - } - - if (framework == null) - { - framework = _nugetFramework; - } - - return Command.Create(commandName, args, framework, configuration, _outputPath); - } - } -} diff --git a/src/Microsoft.DotNet.Cli.Utils/ProjectDependenciesCommandFactory.cs b/src/Microsoft.DotNet.Cli.Utils/ProjectDependenciesCommandFactory.cs new file mode 100644 index 000000000..3b6d943f9 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/ProjectDependenciesCommandFactory.cs @@ -0,0 +1,92 @@ +// 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 NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class ProjectDependenciesCommandFactory : ICommandFactory + { + private readonly NuGetFramework _nugetFramework; + private readonly string _configuration; + private readonly string _outputPath; + private readonly string _projectDirectory; + + public ProjectDependenciesCommandFactory( + NuGetFramework nugetFramework, + string configuration, + string outputPath, + string projectDirectory) + { + _nugetFramework = nugetFramework; + _configuration = configuration; + _outputPath = outputPath; + _projectDirectory = projectDirectory; + } + + public ICommand Create( + string commandName, + IEnumerable args, + NuGetFramework framework = null, + string configuration = Constants.DefaultConfiguration) + { + if (string.IsNullOrEmpty(configuration)) + { + configuration = _configuration; + } + + if (framework == null) + { + framework = _nugetFramework; + } + + var commandSpec = FindProjectDependencyCommands( + commandName, + args, + configuration, + framework, + _outputPath, + _projectDirectory); + + return Command.Create(commandSpec); + } + + private CommandSpec FindProjectDependencyCommands( + string commandName, + IEnumerable commandArgs, + string configuration, + NuGetFramework framework, + string outputPath, + string projectDirectory) + { + var commandResolverArguments = new CommandResolverArguments + { + CommandName = commandName, + CommandArguments = commandArgs, + Framework = framework, + Configuration = configuration, + OutputPath = outputPath, + ProjectDirectory = projectDirectory + }; + + var commandResolver = GetProjectDependenciesCommandResolver(); + + var commandSpec = commandResolver.Resolve(commandResolverArguments); + if (commandSpec == null) + { + throw new CommandUnknownException(commandName); + } + + return commandSpec; + } + + private ICommandResolver GetProjectDependenciesCommandResolver() + { + var environment = new EnvironmentProvider(); + var packagedCommandSpecFactory = new PackagedCommandSpecFactory(); + + return new ProjectDependenciesCommandResolver(environment, packagedCommandSpecFactory); + } + } +} diff --git a/src/dotnet/commands/dotnet-test/Program.cs b/src/dotnet/commands/dotnet-test/Program.cs index 7d9eed583..24d12c11b 100644 --- a/src/dotnet/commands/dotnet-test/Program.cs +++ b/src/dotnet/commands/dotnet-test/Program.cs @@ -102,12 +102,19 @@ namespace Microsoft.DotNet.Tools.Test var commandArgs = new List { GetAssemblyUnderTest(projectContext, configuration, outputPath) }; commandArgs.AddRange(app.RemainingArguments); - return Command.Create( + var commandFactory = + new ProjectDependenciesCommandFactory( + projectContext.TargetFramework, + configuration, + outputPath, + projectContext.ProjectDirectory); + + + return commandFactory.Create( $"dotnet-{GetCommandName(testRunner)}", commandArgs, projectContext.TargetFramework, - configuration: configuration, - outputPath: outputPath) + configuration) .ForwardStdErr() .ForwardStdOut() .Execute() @@ -159,8 +166,13 @@ namespace Microsoft.DotNet.Tools.Test var messages = new TestMessagesCollection(); using (var dotnetTest = new DotnetTest(messages, assemblyUnderTest)) { - var commandFactory = - new FixedPathCommandFactory(projectContext.TargetFramework, configuration, outputPath); + var commandFactory = + new ProjectDependenciesCommandFactory( + projectContext.TargetFramework, + configuration, + outputPath, + projectContext.ProjectDirectory); + var testRunnerFactory = new TestRunnerFactory(GetCommandName(testRunner), commandFactory); dotnetTest diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs index 20adb9293..85d58309f 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs @@ -28,179 +28,17 @@ namespace Microsoft.DotNet.Cli.Utils.Tests var resolvers = defaultCommandResolver.OrderedCommandResolvers; - resolvers.Should().HaveCount(5); + resolvers.Should().HaveCount(4); resolvers.Select(r => r.GetType()) .Should() .ContainInOrder( new []{ typeof(RootedCommandResolver), - typeof(ProjectDependenciesCommandResolver), typeof(ProjectToolsCommandResolver), typeof(AppBaseCommandResolver), typeof(PathCommandResolver) }); } - - - // [Fact] - // public void It_Resolves_Rooted_Commands_Correctly() - // { - // var path = Path.Combine(AppContext.BaseDirectory, "rooteddir"); - // Directory.CreateDirectory(path); - - // var testCommandPath = CreateTestCommandFile(path, ".dll", "rootedcommand"); - - // var defaultCommandResolver = new DefaultCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = testCommandPath, - // CommandArguments = new string[] {} - // }; - - // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().NotBeNull(); - - // commandSpec.Path.Should().Be(testCommandPath); - // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.RootedPath); - // } - - // [Fact] - // public void It_Resolves_AppBase_Commands_Correctly() - // { - // var testCommandPath = CreateTestCommandFile(AppContext.BaseDirectory, ".exe", "appbasecommand"); - // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); - - // var defaultCommandResolver = new DefaultCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = testCommandName, - // CommandArguments = new string[] {}, - // Environment = new EnvironmentProvider() - // }; - - // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().NotBeNull(); - - // commandSpec.Path.Should().Be(testCommandPath); - // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.BaseDirectory); - // } - - // [Fact] - // public void It_Resolves_PATH_Commands_Correctly() - // { - // var path = Path.Combine(AppContext.BaseDirectory, "pathdir"); - // var testCommandPath = CreateTestCommandFile(path, ".dll", "pathcommmand"); - // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); - - // Mock mockEnvironment = new Mock(); - // mockEnvironment.Setup(c => c - // .GetCommandPath(It.IsAny(), It.IsAny())) - // .Returns(testCommandPath); - - // var defaultCommandResolver = new DefaultCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = testCommandName, - // CommandArguments = new string[] {}, - // Environment = mockEnvironment.Object - // }; - - // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().NotBeNull(); - - // commandSpec.Path.Should().Be(testCommandPath); - // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.Path); - // } - - // [Fact] - // public void It_Resolves_Project_Tools_Commands_Correctly() - // { - // var testAppPath = Path.Combine(AppContext.BaseDirectory, - // "TestAssets/TestProjects/AppWithToolDependency"); - - // var defaultCommandResolver = new DefaultCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = "dotnet-hello", - // CommandArguments = new string[] {}, - // ProjectDirectory = testAppPath, - // Environment = new EnvironmentProvider() - // }; - - // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().NotBeNull(); - - // commandSpec.Path.Should().NotBeNull(); - // commandSpec.Args.Should().NotContain("--depsfile"); - // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.NugetPackage); - // } - - // [Fact] - // public void It_Resolves_Project_Dependencies_Commands_Correctly() - // { - // var testAppPath = Path.Combine(AppContext.BaseDirectory, - // "TestAssets/TestProjects/AppWithDirectDependency"); - - // var defaultCommandResolver = new DefaultCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = "dotnet-hello", - // CommandArguments = new string[] {}, - // ProjectDirectory = testAppPath, - // Environment = new EnvironmentProvider(), - // Framework = FrameworkConstants.CommonFrameworks.DnxCore50, - // Configuration = "Debug" - // }; - - - // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().NotBeNull(); - - // commandSpec.Path.Should().NotBeNull(); - // commandSpec.Args.Should().Contain("--depsfile"); - // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.NugetPackage); - // } - - // [Fact] - // public void It_does_not_resolve_ProjectLocal_commands() - // { - // var path = Path.Combine(AppContext.BaseDirectory, - // "testdir"); - - // var testCommandPath = CreateTestCommandFile(path, ".exe", "projectlocalcommand"); - // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); - - // var defaultCommandResolver = new DefaultCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = testCommandName, - // CommandArguments = new string[] {}, - // ProjectDirectory = path, - // Environment = new EnvironmentProvider() - // }; - - // var commandSpec = defaultCommandResolver.Resolve(commandResolverArgs); - - // commandSpec.Should().Be(null); - // } - - // public string CreateTestCommandFile(string path, string extension, string name = "testcommand") - // { - // Directory.CreateDirectory(path); - - // var filename = name + extension; - // var filepath = Path.Combine(path, filename); - - // File.WriteAllText(filepath, "hello world"); - - // return filepath; - // } } } diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs index e5b852456..1363a07c5 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs @@ -40,160 +40,5 @@ namespace Microsoft.DotNet.Cli.Utils.Tests typeof(PathCommandResolver) }); } - - // [Fact] - // public void It_Resolves_Rooted_Commands_Correctly() - // { - // var path = Path.Combine(AppContext.BaseDirectory, "rooteddir"); - // Directory.CreateDirectory(path); - - // var testCommandPath = CreateTestCommandFile(path, ".dll", "scriptrootedcommand"); - - // var scriptCommandResolver = new ScriptCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = testCommandPath, - // CommandArguments = new string[] {} - // }; - - // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().NotBeNull(); - - // commandSpec.Path.Should().Be(testCommandPath); - // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.RootedPath); - // } - - // [Fact] - // public void It_Resolves_AppBase_Commands_Correctly() - // { - // var testCommandPath = CreateTestCommandFile(AppContext.BaseDirectory, ".exe", "scriptappbasecommand"); - // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); - - // var scriptCommandResolver = new ScriptCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = testCommandName, - // CommandArguments = new string[] {}, - // Environment = new EnvironmentProvider() - // }; - - // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().NotBeNull(); - - // commandSpec.Path.Should().Be(testCommandPath); - // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.BaseDirectory); - // } - - // [Fact] - // public void It_Resolves_PATH_Commands_Correctly() - // { - // var path = Path.Combine(AppContext.BaseDirectory, "pathdir"); - // var testCommandPath = CreateTestCommandFile(path, ".dll", "scriptpathcommmand"); - // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); - - // Mock mockEnvironment = new Mock(); - // mockEnvironment.Setup(c => c - // .GetCommandPath(It.IsAny(), It.IsAny())) - // .Returns(testCommandPath); - - // var scriptCommandResolver = new ScriptCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = testCommandName, - // CommandArguments = new string[] {}, - // Environment = mockEnvironment.Object - // }; - - // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().NotBeNull(); - - // commandSpec.Path.Should().Be(testCommandPath); - // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.Path); - // } - - // [Fact] - // public void It_does_NOT_Resolve_Project_Tools_Commands() - // { - // var testAppPath = Path.Combine(AppContext.BaseDirectory, - // "TestAssets/TestProjects/AppWithToolDependency"); - - // var scriptCommandResolver = new ScriptCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = "dotnet-hello", - // CommandArguments = new string[] {}, - // ProjectDirectory = testAppPath, - // Environment = new EnvironmentProvider() - // }; - - // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().BeNull(); - // } - - // [Fact] - // public void It_does_NOT_Resolve_Project_Dependencies_Commands() - // { - // var testAppPath = Path.Combine(AppContext.BaseDirectory, - // "TestAssets/TestProjects/AppWithDirectDependency"); - - // var scriptCommandResolver = new ScriptCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = "dotnet-hello", - // CommandArguments = new string[] {}, - // ProjectDirectory = testAppPath, - // Environment = new EnvironmentProvider(), - // Framework = FrameworkConstants.CommonFrameworks.DnxCore50, - // Configuration = "Debug" - // }; - - - // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); - // commandSpec.Should().BeNull(); - // } - - // [Fact] - // public void It_resolves_ProjectLocal_commands_correctly() - // { - // var path = Path.Combine(AppContext.BaseDirectory, - // "testdir"); - - // var testCommandPath = CreateTestCommandFile(path, ".exe", "scriptprojectlocalcommand"); - // var testCommandName = Path.GetFileNameWithoutExtension(testCommandPath); - - // var scriptCommandResolver = new ScriptCommandResolver(); - - // var commandResolverArgs = new CommandResolverArguments - // { - // CommandName = testCommandName, - // CommandArguments = new string[] {}, - // ProjectDirectory = path, - // Environment = new EnvironmentProvider() - // }; - - // var commandSpec = scriptCommandResolver.Resolve(commandResolverArgs); - - // commandSpec.Should().NotBeNull(); - // commandSpec.Path.Should().Be(testCommandPath); - // commandSpec.ResolutionStrategy.Should().Be(CommandResolutionStrategy.ProjectLocal); - - // } - - // public string CreateTestCommandFile(string path, string extension, string name = "testcommand") - // { - // Directory.CreateDirectory(path); - - // var filename = name + extension; - // var filepath = Path.Combine(path, filename); - - // File.WriteAllText(filepath, "hello world"); - - // return filepath; - // } } } diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs index 38812e448..00516044b 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs @@ -32,6 +32,13 @@ namespace Microsoft.DotNet.Tools.Test.Utilities return new AndConstraint(this); } + public AndConstraint NotPass() + { + Execute.Assertion.ForCondition(_commandResult.ExitCode != 0) + .FailWith(AppendDiagnosticsTo($"Expected command to fail but it did not.")); + return new AndConstraint(this); + } + public AndConstraint Fail() { Execute.Assertion.ForCondition(_commandResult.ExitCode != 0) diff --git a/test/dotnet.Tests/PackagedCommandTests.cs b/test/dotnet.Tests/PackagedCommandTests.cs index c02b7b2b1..941d3f258 100644 --- a/test/dotnet.Tests/PackagedCommandTests.cs +++ b/test/dotnet.Tests/PackagedCommandTests.cs @@ -6,6 +6,7 @@ using System.IO; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Tools.Test.Utilities; using Xunit; +using FluentAssertions; namespace Microsoft.DotNet.Tests { @@ -20,11 +21,10 @@ namespace Microsoft.DotNet.Tests [Theory] [InlineData("AppWithDirectAndToolDependency")] - [InlineData("AppWithDirectDependency")] [InlineData("AppWithToolDependency")] - public void TestPackagedCommandDependency(string appName) + public void TestProjectToolIsAvailableThroughDriver(string appName) { - string appDirectory = Path.Combine(_testProjectsRoot, appName); + var appDirectory = Path.Combine(_testProjectsRoot, appName); new BuildCommand(Path.Combine(appDirectory, "project.json")) .Execute() @@ -38,7 +38,7 @@ namespace Microsoft.DotNet.Tests { CommandResult result = new HelloCommand().ExecuteWithCapturedOutput(); - result.Should().HaveStdOut("Hello" + Environment.NewLine); + result.Should().HaveStdOut("Hello World!" + Environment.NewLine); result.Should().NotHaveStdErr(); result.Should().Pass(); } @@ -48,6 +48,33 @@ namespace Microsoft.DotNet.Tests } } + [Fact] + public void TestProjectDependencyIsNotAvailableThroughDriver() + { + var appName = "AppWithDirectDependency"; + var appDirectory = Path.Combine(_testProjectsRoot, appName); + + new BuildCommand(Path.Combine(appDirectory, "project.json")) + .Execute() + .Should() + .Pass(); + + var currentDirectory = Directory.GetCurrentDirectory(); + Directory.SetCurrentDirectory(appDirectory); + + try + { + CommandResult result = new HelloCommand().ExecuteWithCapturedOutput(); + + result.StdOut.Should().Contain("No executable found matching command"); + result.Should().NotPass(); + } + finally + { + Directory.SetCurrentDirectory(currentDirectory); + } + } + class HelloCommand : TestCommand { public HelloCommand() From 9f58651b6cdcc2918f80236836844479d8149686 Mon Sep 17 00:00:00 2001 From: Bryan Thornbury Date: Mon, 7 Mar 2016 11:50:52 -0800 Subject: [PATCH 06/13] PR Feedback and nearly full test coverage for resolvers fix resolve rebase issues --- .../CollectionsExtensions.cs | 18 -- .../AbstractPathBasedCommandResolver.cs | 2 +- .../CommandResolverArguments.cs | 2 - ...ver.cs => DefaultCommandResolverPolicy.cs} | 22 +- .../ProjectDependenciesCommandResolver.cs | 2 +- .../ProjectPathCommandResolver.cs | 2 +- .../ProjectToolsCommandResolver.cs | 10 +- .../RootedCommandResolver.cs | 2 +- ...lver.cs => ScriptCommandResolverPolicy.cs} | 20 +- .../CommandResolver.cs | 23 +- src/Microsoft.DotNet.Cli.Utils/CoreHost.cs | 1 + .../EnvironmentProvider.cs | 12 +- .../Extensions/CollectionsExtensions.cs | 2 +- .../CommandResolverTestUtils.cs | 4 +- .../GivenADefaultCommandResolver.cs | 2 +- .../GivenAPathCommandResolver.cs | 218 +++++++++++++++++ .../GivenAProjectDependencyCommandResolver.cs | 221 ++++++++++++++++++ .../GivenAProjectPathCommandResolver.cs | 55 +++-- .../GivenAProjectToolsCommandResolver.cs | 152 ++++++++++++ .../GivenAScriptCommandResolver.cs | 2 +- .../GivenAnAppBaseCommandResolver.cs | 51 ++-- 21 files changed, 726 insertions(+), 97 deletions(-) delete mode 100644 src/Microsoft.DotNet.Cli.Utils/CollectionsExtensions.cs rename src/Microsoft.DotNet.Cli.Utils/CommandResolution/{DefaultCommandResolver.cs => DefaultCommandResolverPolicy.cs} (56%) rename src/Microsoft.DotNet.Cli.Utils/CommandResolution/{ScriptCommandResolver.cs => ScriptCommandResolverPolicy.cs} (53%) diff --git a/src/Microsoft.DotNet.Cli.Utils/CollectionsExtensions.cs b/src/Microsoft.DotNet.Cli.Utils/CollectionsExtensions.cs deleted file mode 100644 index 575ee62ba..000000000 --- a/src/Microsoft.DotNet.Cli.Utils/CollectionsExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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 System.Linq; - -namespace Microsoft.DotNet.Cli.Utils -{ - public static class CollectionsExtensions - { - public static IEnumerable OrEmptyIfNull(this IEnumerable enumerable) - { - return enumerable == null - ? Enumerable.Empty() - : enumerable; - } - } -} diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AbstractPathBasedCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AbstractPathBasedCommandResolver.cs index 883aa16a5..73167305d 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AbstractPathBasedCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/AbstractPathBasedCommandResolver.cs @@ -48,7 +48,7 @@ namespace Microsoft.DotNet.Cli.Utils return _commandSpecFactory.CreateCommandSpec( commandResolverArguments.CommandName, - commandResolverArguments.CommandArguments.EmptyIfNull(), + commandResolverArguments.CommandArguments.OrEmptyIfNull(), commandPath, GetCommandResolutionStrategy(), _environment); diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs index e888efc90..bdd9d83b5 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/CommandResolverArguments.cs @@ -22,7 +22,5 @@ namespace Microsoft.DotNet.Cli.Utils public string Configuration { get; set; } public IEnumerable InferredExtensions { get; set; } - - public IEnvironmentProvider Environment { get; set; } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolverPolicy.cs similarity index 56% rename from src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs rename to src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolverPolicy.cs index 74239cbc4..af4244158 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/DefaultCommandResolverPolicy.cs @@ -11,9 +11,9 @@ using NuGet.Packaging; namespace Microsoft.DotNet.Cli.Utils { - public class DefaultCommandResolver : CompositeCommandResolver + public class DefaultCommandResolverPolicy { - public static DefaultCommandResolver Create() + public static CompositeCommandResolver Create() { var environment = new EnvironmentProvider(); var packagedCommandSpecFactory = new PackagedCommandSpecFactory(); @@ -28,18 +28,22 @@ namespace Microsoft.DotNet.Cli.Utils platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); } - return new DefaultCommandResolver(environment, packagedCommandSpecFactory, platformCommandSpecFactory); + return CreateDefaultCommandResolver(environment, packagedCommandSpecFactory, platformCommandSpecFactory); } - public DefaultCommandResolver( + public static CompositeCommandResolver CreateDefaultCommandResolver( IEnvironmentProvider environment, IPackagedCommandSpecFactory packagedCommandSpecFactory, - IPlatformCommandSpecFactory platformCommandSpecFactory) : base() + IPlatformCommandSpecFactory platformCommandSpecFactory) { - AddCommandResolver(new RootedCommandResolver()); - AddCommandResolver(new ProjectToolsCommandResolver(packagedCommandSpecFactory)); - AddCommandResolver(new AppBaseCommandResolver(environment, platformCommandSpecFactory)); - AddCommandResolver(new PathCommandResolver(environment, platformCommandSpecFactory)); + var compositeCommandResolver = new CompositeCommandResolver(); + + compositeCommandResolver.AddCommandResolver(new RootedCommandResolver()); + compositeCommandResolver.AddCommandResolver(new ProjectToolsCommandResolver(packagedCommandSpecFactory)); + compositeCommandResolver.AddCommandResolver(new AppBaseCommandResolver(environment, platformCommandSpecFactory)); + compositeCommandResolver.AddCommandResolver(new PathCommandResolver(environment, platformCommandSpecFactory)); + + return compositeCommandResolver; } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs index ac51862b7..f5436186c 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs @@ -52,7 +52,7 @@ namespace Microsoft.DotNet.Cli.Utils commandResolverArguments.Framework, commandResolverArguments.Configuration, commandResolverArguments.CommandName, - commandResolverArguments.CommandArguments, + commandResolverArguments.CommandArguments.OrEmptyIfNull(), commandResolverArguments.OutputPath); } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectPathCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectPathCommandResolver.cs index 98809a5c5..d857ba890 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectPathCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectPathCommandResolver.cs @@ -26,7 +26,7 @@ namespace Microsoft.DotNet.Cli.Utils return _environment.GetCommandPathFromRootPath( commandResolverArguments.ProjectDirectory, commandResolverArguments.CommandName, - commandResolverArguments.InferredExtensions.EmptyIfNull()); + commandResolverArguments.InferredExtensions.OrEmptyIfNull()); } internal override CommandResolutionStrategy GetCommandResolutionStrategy() diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs index 20649af71..906d660e9 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs @@ -32,9 +32,15 @@ namespace Microsoft.DotNet.Cli.Utils public CommandSpec Resolve(CommandResolverArguments commandResolverArguments) { + if (commandResolverArguments.CommandName == null + || commandResolverArguments.ProjectDirectory == null) + { + return null; + } + return ResolveFromProjectTools( commandResolverArguments.CommandName, - commandResolverArguments.CommandArguments, + commandResolverArguments.CommandArguments.OrEmptyIfNull(), commandResolverArguments.ProjectDirectory); } @@ -50,7 +56,7 @@ namespace Microsoft.DotNet.Cli.Utils return null; } - var toolsLibraries = projectContext.ProjectFile.Tools.EmptyIfNull(); + var toolsLibraries = projectContext.ProjectFile.Tools.OrEmptyIfNull(); return ResolveCommandSpecFromAllToolLibraries( toolsLibraries, diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/RootedCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/RootedCommandResolver.cs index a363e18ce..f5a6d08a1 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/RootedCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/RootedCommandResolver.cs @@ -23,7 +23,7 @@ namespace Microsoft.DotNet.Cli.Utils if (Path.IsPathRooted(commandResolverArguments.CommandName)) { var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart( - commandResolverArguments.CommandArguments.EmptyIfNull()); + commandResolverArguments.CommandArguments.OrEmptyIfNull()); return new CommandSpec(commandResolverArguments.CommandName, escapedArgs, CommandResolutionStrategy.RootedPath); } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolverPolicy.cs similarity index 53% rename from src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolver.cs rename to src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolverPolicy.cs index a29c3ecfd..cb18e0f8e 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ScriptCommandResolverPolicy.cs @@ -11,9 +11,9 @@ using NuGet.Packaging; namespace Microsoft.DotNet.Cli.Utils { - public class ScriptCommandResolver : CompositeCommandResolver + public class ScriptCommandResolverPolicy { - public static ScriptCommandResolver Create() + public static CompositeCommandResolver Create() { var environment = new EnvironmentProvider(); @@ -27,17 +27,21 @@ namespace Microsoft.DotNet.Cli.Utils platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); } - return new ScriptCommandResolver(environment, platformCommandSpecFactory); + return CreateScriptCommandResolver(environment, platformCommandSpecFactory); } - public ScriptCommandResolver( + public static CompositeCommandResolver CreateScriptCommandResolver( IEnvironmentProvider environment, IPlatformCommandSpecFactory platformCommandSpecFactory) { - AddCommandResolver(new RootedCommandResolver()); - AddCommandResolver(new ProjectPathCommandResolver(environment, platformCommandSpecFactory)); - AddCommandResolver(new AppBaseCommandResolver(environment, platformCommandSpecFactory)); - AddCommandResolver(new PathCommandResolver(environment, platformCommandSpecFactory)); + var compositeCommandResolver = new CompositeCommandResolver(); + + compositeCommandResolver.AddCommandResolver(new RootedCommandResolver()); + compositeCommandResolver.AddCommandResolver(new ProjectPathCommandResolver(environment, platformCommandSpecFactory)); + compositeCommandResolver.AddCommandResolver(new AppBaseCommandResolver(environment, platformCommandSpecFactory)); + compositeCommandResolver.AddCommandResolver(new PathCommandResolver(environment, platformCommandSpecFactory)); + + return compositeCommandResolver; } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs index b8261dea6..6c797b4a1 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs @@ -13,9 +13,6 @@ namespace Microsoft.DotNet.Cli.Utils { internal static class CommandResolver { - private static DefaultCommandResolver _defaultCommandResolver; - private static ScriptCommandResolver _scriptCommandResolver; - public static CommandSpec TryResolveCommandSpec( string commandName, IEnumerable args, @@ -32,13 +29,10 @@ namespace Microsoft.DotNet.Cli.Utils Configuration = configuration, OutputPath = outputPath }; - - if (_defaultCommandResolver == null) - { - _defaultCommandResolver = DefaultCommandResolver.Create(); - } - - return _defaultCommandResolver.Resolve(commandResolverArgs); + + var defaultCommandResolver = DefaultCommandResolverPolicy.Create(); + + return defaultCommandResolver.Resolve(commandResolverArgs); } public static CommandSpec TryResolveScriptCommandSpec( @@ -55,12 +49,9 @@ namespace Microsoft.DotNet.Cli.Utils InferredExtensions = inferredExtensionList }; - if (_scriptCommandResolver == null) - { - _scriptCommandResolver = ScriptCommandResolver.Create(); - } - - return _scriptCommandResolver.Resolve(commandResolverArgs); + var scriptCommandResolver = ScriptCommandResolverPolicy.Create(); + + return scriptCommandResolver.Resolve(commandResolverArgs); } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/CoreHost.cs b/src/Microsoft.DotNet.Cli.Utils/CoreHost.cs index 7775800ec..2dae756b0 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CoreHost.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CoreHost.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using Microsoft.Extensions.PlatformAbstractions; diff --git a/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs b/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs index 9cade5892..1060443ff 100644 --- a/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs +++ b/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs @@ -50,12 +50,12 @@ namespace Microsoft.DotNet.Cli.Utils } } - public EnvironmentProvider(IEnumerable extensionsOverride = null) + public EnvironmentProvider( + IEnumerable extensionsOverride = null, + IEnumerable searchPathsOverride = null) { - if (extensionsOverride != null) - { - _executableExtensions = extensionsOverride; - } + _executableExtensions = extensionsOverride; + _searchPaths = searchPathsOverride; } public string GetCommandPath(string commandName, params string[] extensions) @@ -89,7 +89,7 @@ namespace Microsoft.DotNet.Cli.Utils public string GetCommandPathFromRootPath(string rootPath, string commandName, IEnumerable extensions) { - var extensionsArr = extensions.EmptyIfNull().ToArray(); + var extensionsArr = extensions.OrEmptyIfNull().ToArray(); return GetCommandPathFromRootPath(rootPath, commandName, extensionsArr); } diff --git a/src/Microsoft.DotNet.Cli.Utils/Extensions/CollectionsExtensions.cs b/src/Microsoft.DotNet.Cli.Utils/Extensions/CollectionsExtensions.cs index 4f5b3d3bd..6cea049c3 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Extensions/CollectionsExtensions.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Extensions/CollectionsExtensions.cs @@ -6,7 +6,7 @@ namespace Microsoft.DotNet.Cli.Utils { public static class CollectionsExtensions { - public static IEnumerable EmptyIfNull(this IEnumerable enumerable) + public static IEnumerable OrEmptyIfNull(this IEnumerable enumerable) { return enumerable == null ? Enumerable.Empty() diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/CommandResolverTestUtils.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/CommandResolverTestUtils.cs index 24242b06f..c83237012 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/CommandResolverTestUtils.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/CommandResolverTestUtils.cs @@ -8,13 +8,15 @@ namespace Microsoft.DotNet.Cli.Utils.Tests { public static class CommandResolverTestUtils { - public static void CreateNonRunnableTestCommand(string directory, string filename, string extension=".dll") + public static string CreateNonRunnableTestCommand(string directory, string filename, string extension=".dll") { Directory.CreateDirectory(directory); var filePath = Path.Combine(directory, filename + extension); File.WriteAllText(filePath, "test command that does nothing."); + + return filePath; } public static IEnvironmentProvider SetupEnvironmentProviderWhichFindsExtensions(params string[] extensions) diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs index 85d58309f..5c1a5a5ec 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenADefaultCommandResolver.cs @@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests [Fact] public void It_contains_resolvers_in_the_right_order() { - var defaultCommandResolver = DefaultCommandResolver.Create(); + var defaultCommandResolver = DefaultCommandResolverPolicy.Create(); var resolvers = defaultCommandResolver.OrderedCommandResolvers; diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAPathCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAPathCommandResolver.cs index e69de29bb..4004c5b00 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAPathCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAPathCommandResolver.cs @@ -0,0 +1,218 @@ +// 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 System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Linq; +using Xunit; +using Moq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System.Threading; +using FluentAssertions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenAPathCommandResolver + { + private static readonly string s_testDirectory = Path.Combine(AppContext.BaseDirectory, "pathTestDirectory"); + + [Fact] + public void It_returns_null_when_CommandName_is_null() + { + var pathCommandResolver = SetupPlatformPathCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = null, + CommandArguments = null + }; + + var result = pathCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_CommandName_does_not_exist_in_PATH() + { + var emptyPathEnvironmentMock = new Mock(); + emptyPathEnvironmentMock.Setup(e => e + .GetCommandPath(It.IsAny(), It.IsAny())) + .Returns((string)null); + + var pathCommandResolver = SetupPlatformPathCommandResolver(emptyPathEnvironmentMock.Object); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "nonexistent-command", + CommandArguments = null + }; + + var result = pathCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_a_CommandSpec_with_CommandName_as_FileName_when_CommandName_exists_in_PATH() + { + var testCommandPath = CommandResolverTestUtils.CreateNonRunnableTestCommand( + s_testDirectory, + "pathtestcommand1", + ".exe"); + + var staticPathEnvironmentMock = new Mock(); + staticPathEnvironmentMock.Setup(e => e + .GetCommandPath(It.IsAny(), It.IsAny())) + .Returns(testCommandPath); + + var pathCommandResolver = SetupPlatformPathCommandResolver(staticPathEnvironmentMock.Object, forceGeneric: true); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = Path.GetFileNameWithoutExtension(testCommandPath), + CommandArguments = null + }; + + var result = pathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileNameWithoutExtension(result.Path); + + commandFile.Should().Be(Path.GetFileNameWithoutExtension(testCommandPath)); + } + + [Fact] + public void It_escapes_CommandArguments_when_returning_a_CommandSpec() + { + var testCommandPath = CommandResolverTestUtils.CreateNonRunnableTestCommand( + s_testDirectory, + "pathtestcommand1", + ".exe"); + + var staticPathEnvironmentMock = new Mock(); + staticPathEnvironmentMock.Setup(e => e + .GetCommandPath(It.IsAny(), It.IsAny())) + .Returns(testCommandPath); + + var pathCommandResolver = SetupPlatformPathCommandResolver(staticPathEnvironmentMock.Object, forceGeneric: true); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = Path.GetFileNameWithoutExtension(testCommandPath), + CommandArguments = new [] {"arg with space"} + }; + + var result = pathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Args.Should().Be("\"arg with space\""); + } + + [Fact] + public void It_returns_a_CommandSpec_with_Args_as_stringEmpty_when_returning_a_CommandSpec_and_CommandArguments_are_null() + { + var testCommandPath = CommandResolverTestUtils.CreateNonRunnableTestCommand( + s_testDirectory, + "pathtestcommand1", + ".exe"); + + var staticPathEnvironmentMock = new Mock(); + staticPathEnvironmentMock.Setup(e => e + .GetCommandPath(It.IsAny(), It.IsAny())) + .Returns(testCommandPath); + + var pathCommandResolver = SetupPlatformPathCommandResolver(staticPathEnvironmentMock.Object, forceGeneric: true); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = Path.GetFileNameWithoutExtension(testCommandPath), + CommandArguments = null + }; + + var result = pathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Args.Should().Be(string.Empty); + } + + [Fact] + public void It_prefers_EXE_over_CMD_when_two_command_candidates_exist_and_using_WindowsExePreferredCommandSpecFactory() + { + var environment = new EnvironmentProvider(new [] {".exe", ".cmd"}, new[] { s_testDirectory }); + var platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + + var pathCommandResolver = new PathCommandResolver(environment, platformCommandSpecFactory); + + CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testDirectory, "extensionPreferenceCommand", ".exe"); + CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testDirectory, "extensionPreferenceCommand", ".cmd"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "extensionPreferenceCommand", + CommandArguments = null + }; + + var result = pathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileName(result.Path); + commandFile.Should().Be("extensionPreferenceCommand.exe"); + } + + [Fact] + public void It_wraps_command_with_CMD_EXE_when_command_has_CMD_Extension_and_using_WindowsExePreferredCommandSpecFactory() + { + var environment = new EnvironmentProvider(new [] {".cmd"}, new[] { s_testDirectory }); + var platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + + var pathCommandResolver = new PathCommandResolver(environment, platformCommandSpecFactory); + + var testCommandPath = + CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testDirectory, "cmdWrapCommand", ".cmd"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "cmdWrapCommand", + CommandArguments = null + }; + + var result = pathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileName(result.Path); + commandFile.Should().Be("cmd.exe"); + + result.Args.Should().Contain(testCommandPath); + } + + private PathCommandResolver SetupPlatformPathCommandResolver( + IEnvironmentProvider environment = null, + bool forceGeneric = false) + { + environment = environment ?? new EnvironmentProvider(); + + IPlatformCommandSpecFactory platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); + + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows + && !forceGeneric) + { + platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + } + + var pathCommandResolver = new PathCommandResolver(environment, platformCommandSpecFactory); + + return pathCommandResolver; + } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectDependencyCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectDependencyCommandResolver.cs index e69de29bb..3fc93ae72 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectDependencyCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectDependencyCommandResolver.cs @@ -0,0 +1,221 @@ +// 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 System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Linq; +using Xunit; +using Moq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System.Threading; +using FluentAssertions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenAProjectDependenciesCommandResolver + { + + private static readonly string s_liveProjectDirectory = + Path.Combine(AppContext.BaseDirectory, "TestAssets/TestProjects/AppWithDirectDependency"); + + [Fact] + public void It_returns_null_when_CommandName_is_null() + { + var projectDependenciesCommandResolver = SetupProjectDependenciesCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = null, + CommandArguments = new string[] {""}, + ProjectDirectory = "/some/directory", + Configuration = "Debug", + Framework = FrameworkConstants.CommonFrameworks.NetStandardApp15 + }; + + var result = projectDependenciesCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_ProjectDirectory_is_null() + { + var projectDependenciesCommandResolver = SetupProjectDependenciesCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "command", + CommandArguments = new string[] {""}, + ProjectDirectory = null, + Configuration = "Debug", + Framework = FrameworkConstants.CommonFrameworks.NetStandardApp15 + }; + + var result = projectDependenciesCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_Framework_is_null() + { + var projectDependenciesCommandResolver = SetupProjectDependenciesCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "command", + CommandArguments = new string[] {""}, + ProjectDirectory = s_liveProjectDirectory, + Configuration = "Debug", + Framework = null + }; + + var result = projectDependenciesCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_Configuration_is_null() + { + var projectDependenciesCommandResolver = SetupProjectDependenciesCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "command", + CommandArguments = new string[] {""}, + ProjectDirectory = s_liveProjectDirectory, + Configuration = null, + Framework = FrameworkConstants.CommonFrameworks.NetStandardApp15 + }; + + var result = projectDependenciesCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_CommandName_does_not_exist_in_ProjectDependencies() + { + var projectDependenciesCommandResolver = SetupProjectDependenciesCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "nonexistent-command", + CommandArguments = null, + ProjectDirectory = s_liveProjectDirectory, + Configuration = "Debug", + Framework = FrameworkConstants.CommonFrameworks.NetStandardApp15 + }; + + var result = projectDependenciesCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_a_CommandSpec_with_CoreHost_as_FileName_and_CommandName_in_Args_when_CommandName_exists_in_ProjectDependencies() + { + var projectDependenciesCommandResolver = SetupProjectDependenciesCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "dotnet-hello", + CommandArguments = null, + ProjectDirectory = s_liveProjectDirectory, + Configuration = "Debug", + Framework = FrameworkConstants.CommonFrameworks.NetStandardApp15 + }; + + var result = projectDependenciesCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileNameWithoutExtension(result.Path); + + commandFile.Should().Be("corehost"); + + result.Args.Should().Contain(commandResolverArguments.CommandName); + } + + [Fact] + public void It_escapes_CommandArguments_when_returning_a_CommandSpec() + { + var projectDependenciesCommandResolver = SetupProjectDependenciesCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "dotnet-hello", + CommandArguments = new [] { "arg with space"}, + ProjectDirectory = s_liveProjectDirectory, + Configuration = "Debug", + Framework = FrameworkConstants.CommonFrameworks.NetStandardApp15 + }; + + var result = projectDependenciesCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Args.Should().Contain("\"arg with space\""); + } + + [Fact] + public void It_passes_depsfile_arg_to_corehost_when_returning_a_commandspec() + { + var projectDependenciesCommandResolver = SetupProjectDependenciesCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "dotnet-hello", + CommandArguments = null, + ProjectDirectory = s_liveProjectDirectory, + Configuration = "Debug", + Framework = FrameworkConstants.CommonFrameworks.NetStandardApp15 + }; + + var result = projectDependenciesCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Args.Should().Contain("--depsfile"); + } + + [Fact] + public void It_returns_a_CommandSpec_with_CommandName_in_Args_when_returning_a_CommandSpec_and_CommandArguments_are_null() + { + var projectDependenciesCommandResolver = SetupProjectDependenciesCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "dotnet-hello", + CommandArguments = null, + ProjectDirectory = s_liveProjectDirectory, + Configuration = "Debug", + Framework = FrameworkConstants.CommonFrameworks.NetStandardApp15 + }; + + var result = projectDependenciesCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + result.Args.Should().Contain("dotnet-hello"); + } + + private ProjectDependenciesCommandResolver SetupProjectDependenciesCommandResolver( + IEnvironmentProvider environment = null, + IPackagedCommandSpecFactory packagedCommandSpecFactory = null) + { + environment = environment ?? new EnvironmentProvider(); + packagedCommandSpecFactory = packagedCommandSpecFactory ?? new PackagedCommandSpecFactory(); + + var projectDependenciesCommandResolver = new ProjectDependenciesCommandResolver(environment, packagedCommandSpecFactory); + + return projectDependenciesCommandResolver; + } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectPathCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectPathCommandResolver.cs index 1117f1b43..9c0a0ce6a 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectPathCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectPathCommandResolver.cs @@ -26,7 +26,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests [Fact] public void It_returns_null_when_CommandName_is_null() { - var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(forceGeneric: true); var commandResolverArguments = new CommandResolverArguments() { @@ -43,7 +43,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests [Fact] public void It_returns_null_when_ProjectDirectory_is_null() { - var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(forceGeneric: true); var commandResolverArguments = new CommandResolverArguments() { @@ -60,7 +60,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests [Fact] public void It_returns_null_when_CommandName_does_not_exist_in_ProjectDirectory() { - var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(forceGeneric: true); var commandResolverArguments = new CommandResolverArguments() { @@ -78,7 +78,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests public void It_returns_null_when_CommandName_exists_in_a_subdirectory_of_ProjectDirectory() { var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); - var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment, forceGeneric: true); var testDir = Path.Combine(s_testProjectDirectory, "projectpathtestsubdir"); CommandResolverTestUtils.CreateNonRunnableTestCommand(testDir, "projectpathtestsubdircommand", ".exe"); @@ -99,7 +99,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests public void It_returns_a_CommandSpec_with_CommandName_as_FileName_when_CommandName_exists_in_ProjectDirectory() { var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); - var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment, forceGeneric: true); CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testProjectDirectory, "projectpathtestcommand1", ".exe"); @@ -123,7 +123,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests public void It_escapes_CommandArguments_when_returning_a_CommandSpec() { var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); - var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment, forceGeneric: true); CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testProjectDirectory, "projectpathtestcommand1", ".exe"); @@ -144,7 +144,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests public void It_resolves_commands_with_extensions_defined_in_InferredExtensions() { var extensions = new string[] {".sh", ".cmd", ".foo", ".exe"}; - var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(forceGeneric: true); foreach (var extension in extensions) { @@ -173,7 +173,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests public void It_returns_a_CommandSpec_with_Args_as_stringEmpty_when_returning_a_CommandSpec_and_CommandArguments_are_null() { var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); - var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment); + var projectPathCommandResolver = SetupPlatformProjectPathCommandResolver(environment, forceGeneric: true); CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testProjectDirectory, "projectpathtestcommand1", ".exe"); @@ -216,20 +216,45 @@ namespace Microsoft.DotNet.Cli.Utils.Tests commandFile.Should().Be("projectpathtestcommand1.exe"); } - private ProjectPathCommandResolver SetupPlatformProjectPathCommandResolver(IEnvironmentProvider environment = null) + public void It_wraps_command_with_CMD_EXE_when_command_has_CMD_Extension_and_using_WindowsExePreferredCommandSpecFactory() + { + var environment = new EnvironmentProvider(new [] {".cmd"}); + var platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + + var pathCommandResolver = new PathCommandResolver(environment, platformCommandSpecFactory); + + var testCommandPath = + CommandResolverTestUtils.CreateNonRunnableTestCommand(s_testProjectDirectory, "cmdWrapCommand", ".cmd"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "cmdWrapCommand", + CommandArguments = null + }; + + var result = pathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileName(result.Path); + commandFile.Should().Be("cmd.exe"); + + result.Args.Should().Contain(testCommandPath); + } + + private ProjectPathCommandResolver SetupPlatformProjectPathCommandResolver( + IEnvironmentProvider environment = null, + bool forceGeneric = false) { environment = environment ?? new EnvironmentProvider(); - var platformCommandSpecFactory = default(IPlatformCommandSpecFactory); + IPlatformCommandSpecFactory platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); - if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows) + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows + && !forceGeneric) { platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); } - else - { - platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); - } var projectPathCommandResolver = new ProjectPathCommandResolver(environment, platformCommandSpecFactory); diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs index e69de29bb..c414b1345 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs @@ -0,0 +1,152 @@ +// 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 System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Linq; +using Xunit; +using Moq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.PlatformAbstractions; +using System.Threading; +using FluentAssertions; +using NuGet.Frameworks; + +namespace Microsoft.DotNet.Cli.Utils.Tests +{ + public class GivenAProjectToolsCommandResolver + { + + private static readonly string s_liveProjectDirectory = + Path.Combine(AppContext.BaseDirectory, "TestAssets/TestProjects/AppWithToolDependency"); + + [Fact] + public void It_returns_null_when_CommandName_is_null() + { + var projectToolsCommandResolver = SetupProjectToolsCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = null, + CommandArguments = new string[] {""}, + ProjectDirectory = "/some/directory" + }; + + var result = projectToolsCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_ProjectDirectory_is_null() + { + var projectToolsCommandResolver = SetupProjectToolsCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "command", + CommandArguments = new string[] {""}, + ProjectDirectory = null + }; + + var result = projectToolsCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_null_when_CommandName_does_not_exist_in_ProjectTools() + { + var projectToolsCommandResolver = SetupProjectToolsCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "nonexistent-command", + CommandArguments = null, + ProjectDirectory = s_liveProjectDirectory + }; + + var result = projectToolsCommandResolver.Resolve(commandResolverArguments); + + result.Should().BeNull(); + } + + [Fact] + public void It_returns_a_CommandSpec_with_CoreHost_as_FileName_and_CommandName_in_Args_when_CommandName_exists_in_ProjectTools() + { + var projectToolsCommandResolver = SetupProjectToolsCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "dotnet-hello", + CommandArguments = null, + ProjectDirectory = s_liveProjectDirectory + }; + + var result = projectToolsCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileNameWithoutExtension(result.Path); + + commandFile.Should().Be("corehost"); + + result.Args.Should().Contain(commandResolverArguments.CommandName); + } + + [Fact] + public void It_escapes_CommandArguments_when_returning_a_CommandSpec() + { + var projectToolsCommandResolver = SetupProjectToolsCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "dotnet-hello", + CommandArguments = new [] { "arg with space"}, + ProjectDirectory = s_liveProjectDirectory + }; + + var result = projectToolsCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + result.Args.Should().Contain("\"arg with space\""); + } + + [Fact] + public void It_returns_a_CommandSpec_with_Args_as_CommandPath_when_returning_a_CommandSpec_and_CommandArguments_are_null() + { + var projectToolsCommandResolver = SetupProjectToolsCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "dotnet-hello", + CommandArguments = null, + ProjectDirectory = s_liveProjectDirectory + }; + + var result = projectToolsCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandPath = result.Args.Trim('"'); + commandPath.Should().Contain("dotnet-hello"); + + File.Exists(commandPath).Should().BeTrue(); + } + + private ProjectToolsCommandResolver SetupProjectToolsCommandResolver( + IPackagedCommandSpecFactory packagedCommandSpecFactory = null) + { + packagedCommandSpecFactory = packagedCommandSpecFactory ?? new PackagedCommandSpecFactory(); + + var projectToolsCommandResolver = new ProjectToolsCommandResolver(packagedCommandSpecFactory); + + return projectToolsCommandResolver; + } + } +} diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs index 1363a07c5..3dd23c6e5 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAScriptCommandResolver.cs @@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests [Fact] public void It_contains_resolvers_in_the_right_order() { - var scriptCommandResolver = ScriptCommandResolver.Create(); + var scriptCommandResolver = ScriptCommandResolverPolicy.Create(); var resolvers = scriptCommandResolver.OrderedCommandResolvers; diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAnAppBaseCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAnAppBaseCommandResolver.cs index 15ef2fe2d..2f1bd56db 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAnAppBaseCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAnAppBaseCommandResolver.cs @@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests [Fact] public void It_returns_null_when_CommandName_is_null() { - var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(forceGeneric: true); var commandResolverArguments = new CommandResolverArguments() { @@ -40,7 +40,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests [Fact] public void It_returns_null_when_CommandName_does_not_exist_applocal() { - var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(forceGeneric: true); var commandResolverArguments = new CommandResolverArguments() { @@ -57,7 +57,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests public void It_returns_a_CommandSpec_with_CommandName_as_FileName_when_CommandName_exists_applocal() { var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); - var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment, forceGeneric: true); CommandResolverTestUtils.CreateNonRunnableTestCommand(AppContext.BaseDirectory, "appbasetestcommand1", ".exe"); @@ -80,7 +80,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests public void It_returns_null_when_CommandName_exists_applocal_in_a_subdirectory() { var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); - var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment, forceGeneric: true); var testDir = Path.Combine(AppContext.BaseDirectory, "appbasetestsubdir"); CommandResolverTestUtils.CreateNonRunnableTestCommand(testDir, "appbasetestsubdircommand", ".exe"); @@ -100,7 +100,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests public void It_escapes_CommandArguments_when_returning_a_CommandSpec() { var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); - var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment, forceGeneric: true); CommandResolverTestUtils.CreateNonRunnableTestCommand(AppContext.BaseDirectory, "appbasetestcommand1", ".exe"); @@ -120,7 +120,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests public void It_returns_a_CommandSpec_with_Args_as_stringEmpty_when_returning_a_CommandSpec_and_CommandArguments_are_null() { var environment = CommandResolverTestUtils.SetupEnvironmentProviderWhichFindsExtensions(".exe"); - var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment); + var appBaseCommandResolver = SetupPlatformAppBaseCommandResolver(environment, forceGeneric: true); CommandResolverTestUtils.CreateNonRunnableTestCommand(AppContext.BaseDirectory, "appbasetestcommand1", ".exe"); @@ -161,20 +161,45 @@ namespace Microsoft.DotNet.Cli.Utils.Tests commandFile.Should().Be("appbasetestcommand1.exe"); } - private AppBaseCommandResolver SetupPlatformAppBaseCommandResolver(IEnvironmentProvider environment = null) + public void It_wraps_command_with_CMD_EXE_when_command_has_CMD_Extension_and_using_WindowsExePreferredCommandSpecFactory() + { + var environment = new EnvironmentProvider(new [] {".cmd"}); + var platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); + + var pathCommandResolver = new PathCommandResolver(environment, platformCommandSpecFactory); + + var testCommandPath = + CommandResolverTestUtils.CreateNonRunnableTestCommand(AppContext.BaseDirectory, "cmdWrapCommand", ".cmd"); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "cmdWrapCommand", + CommandArguments = null + }; + + var result = pathCommandResolver.Resolve(commandResolverArguments); + + result.Should().NotBeNull(); + + var commandFile = Path.GetFileName(result.Path); + commandFile.Should().Be("cmd.exe"); + + result.Args.Should().Contain(testCommandPath); + } + + private AppBaseCommandResolver SetupPlatformAppBaseCommandResolver( + IEnvironmentProvider environment = null, + bool forceGeneric = false) { environment = environment ?? new EnvironmentProvider(); - var platformCommandSpecFactory = default(IPlatformCommandSpecFactory); + IPlatformCommandSpecFactory platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); - if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows) + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows + && !forceGeneric) { platformCommandSpecFactory = new WindowsExePreferredCommandSpecFactory(); } - else - { - platformCommandSpecFactory = new GenericPlatformCommandSpecFactory(); - } var appBaseCommandResolver = new AppBaseCommandResolver(environment, platformCommandSpecFactory); From 6705585a67aa343bf6a3f5a3988044190c32d029 Mon Sep 17 00:00:00 2001 From: Livar Cunha Date: Mon, 7 Mar 2016 14:55:39 -0800 Subject: [PATCH 07/13] Updating test projects to the lastest version of dotnet-test-xunit runner. --- test/EndToEnd/project.json | 2 +- test/Microsoft.DotNet.ProjectModel.Tests/project.json | 2 +- test/Microsoft.DotNet.Tools.Tests.Utilities/project.json | 2 +- test/Microsoft.Extensions.DependencyModel.Tests/project.json | 3 +-- test/dotnet-build.Tests/project.json | 4 ++-- test/dotnet-compile.Tests/project.json | 2 +- test/dotnet-compile.UnitTests/project.json | 2 +- test/dotnet-projectmodel-server.Tests/project.json | 2 +- test/dotnet-publish.Tests/project.json | 2 +- test/dotnet-test.UnitTests/project.json | 2 +- test/dotnet.Tests/project.json | 2 +- 11 files changed, 12 insertions(+), 13 deletions(-) diff --git a/test/EndToEnd/project.json b/test/EndToEnd/project.json index 93c7084d9..4bcbd9290 100644 --- a/test/EndToEnd/project.json +++ b/test/EndToEnd/project.json @@ -14,7 +14,7 @@ "xunit": "2.1.0", "xunit.netcore.extensions": "1.0.0-prerelease-*", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, "frameworks": { diff --git a/test/Microsoft.DotNet.ProjectModel.Tests/project.json b/test/Microsoft.DotNet.ProjectModel.Tests/project.json index 0a33d56a1..edaaef649 100644 --- a/test/Microsoft.DotNet.ProjectModel.Tests/project.json +++ b/test/Microsoft.DotNet.ProjectModel.Tests/project.json @@ -6,7 +6,7 @@ "Microsoft.DotNet.ProjectModel": { "target": "project" }, "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, "frameworks": { "netstandardapp1.5": { diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json b/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json index 535f5b902..cf42e619c 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json @@ -10,7 +10,7 @@ "System.IO.Compression": "4.1.0-rc2-23901", "FluentAssertions": "4.0.0", "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47", + "dotnet-test-xunit": "1.0.0-dev-91790-12", "Microsoft.DotNet.TestFramework": "1.0.0-*", "Microsoft.DotNet.Cli.Utils": "1.0.0-*", diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/project.json b/test/Microsoft.Extensions.DependencyModel.Tests/project.json index cb583becc..18a43cf55 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/project.json +++ b/test/Microsoft.Extensions.DependencyModel.Tests/project.json @@ -12,7 +12,7 @@ "FluentAssertions": "4.0.0", "moq.netcore": "4.4.0-beta8", "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, "frameworks": { @@ -24,6 +24,5 @@ } }, - "testRunner": "xunit" } \ No newline at end of file diff --git a/test/dotnet-build.Tests/project.json b/test/dotnet-build.Tests/project.json index 00afd26c1..c158034ef 100644 --- a/test/dotnet-build.Tests/project.json +++ b/test/dotnet-build.Tests/project.json @@ -1,6 +1,6 @@ { "version": "1.0.0-*", - + "dependencies": { "NETStandard.Library": "1.0.0-rc2-23901", "System.IO.Compression": "4.1.0-rc2-23901", @@ -11,7 +11,7 @@ }, "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, "frameworks": { diff --git a/test/dotnet-compile.Tests/project.json b/test/dotnet-compile.Tests/project.json index 83f842a88..dd02d8697 100644 --- a/test/dotnet-compile.Tests/project.json +++ b/test/dotnet-compile.Tests/project.json @@ -11,7 +11,7 @@ }, "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, "frameworks": { diff --git a/test/dotnet-compile.UnitTests/project.json b/test/dotnet-compile.UnitTests/project.json index 0208dc29c..9813a2d85 100644 --- a/test/dotnet-compile.UnitTests/project.json +++ b/test/dotnet-compile.UnitTests/project.json @@ -13,7 +13,7 @@ "Microsoft.DotNet.ProjectModel": { "target": "project" }, "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47", + "dotnet-test-xunit": "1.0.0-dev-91790-12", "moq.netcore": "4.4.0-beta8", "FluentAssertions": "4.2.2" }, diff --git a/test/dotnet-projectmodel-server.Tests/project.json b/test/dotnet-projectmodel-server.Tests/project.json index bbdf75c63..42d8fd6f6 100644 --- a/test/dotnet-projectmodel-server.Tests/project.json +++ b/test/dotnet-projectmodel-server.Tests/project.json @@ -4,7 +4,7 @@ "Microsoft.DotNet.ProjectModel": { "target": "project" }, "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47", + "dotnet-test-xunit": "1.0.0-dev-91790-12", "System.Net.NameResolution": "4.0.0-rc2-23901" }, diff --git a/test/dotnet-publish.Tests/project.json b/test/dotnet-publish.Tests/project.json index ab16338b8..0064f9349 100644 --- a/test/dotnet-publish.Tests/project.json +++ b/test/dotnet-publish.Tests/project.json @@ -13,7 +13,7 @@ "xunit": "2.1.0", "xunit.netcore.extensions": "1.0.0-prerelease-*", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, "frameworks": { diff --git a/test/dotnet-test.UnitTests/project.json b/test/dotnet-test.UnitTests/project.json index 1aa86bcd6..eb23b1792 100644 --- a/test/dotnet-test.UnitTests/project.json +++ b/test/dotnet-test.UnitTests/project.json @@ -8,7 +8,7 @@ "dotnet": { "target": "project" }, "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47", + "dotnet-test-xunit": "1.0.0-dev-91790-12", "moq.netcore": "4.4.0-beta8", "FluentAssertions": "4.2.2" }, diff --git a/test/dotnet.Tests/project.json b/test/dotnet.Tests/project.json index f6266f4ae..46da798d1 100644 --- a/test/dotnet.Tests/project.json +++ b/test/dotnet.Tests/project.json @@ -12,7 +12,7 @@ }, "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, "frameworks": { From 53fd6075845a11a2760bfeed1a4f4c4426fd34fe Mon Sep 17 00:00:00 2001 From: Sridhar Periyasamy Date: Mon, 7 Mar 2016 20:23:41 -0500 Subject: [PATCH 08/13] Fix EndToEnd tests for RHEL. --- test/EndToEnd/EndToEndTest.cs | 63 ++++++++++++----------------------- 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/test/EndToEnd/EndToEndTest.cs b/test/EndToEnd/EndToEndTest.cs index f6f5663be..5fb8edccc 100644 --- a/test/EndToEnd/EndToEndTest.cs +++ b/test/EndToEnd/EndToEndTest.cs @@ -83,15 +83,8 @@ namespace Microsoft.DotNet.Tests.EndToEnd [Fact] public void TestDotnetBuildNativeRyuJit() { - if(IsCentOSorRHEL()) + if(!IsNativeCompilationSupported()) { - Console.WriteLine("Skipping native compilation tests on CentOS/RHEL - https://github.com/dotnet/cli/issues/453"); - return; - } - - if (IsWinX86()) - { - Console.WriteLine("Skipping native compilation tests on Windows x86 - https://github.com/dotnet/cli/issues/1550"); return; } @@ -105,15 +98,8 @@ namespace Microsoft.DotNet.Tests.EndToEnd [Fact] public void TestDotnetBuildNativeCpp() { - if(IsCentOSorRHEL()) - { - Console.WriteLine("Skipping native compilation tests on CentOS/RHEL - https://github.com/dotnet/cli/issues/453"); - return; - } - - if (IsWinX86()) - { - Console.WriteLine("Skipping native compilation tests on Windows x86 - https://github.com/dotnet/cli/issues/1550"); + if(!IsNativeCompilationSupported()) + { return; } @@ -127,15 +113,8 @@ namespace Microsoft.DotNet.Tests.EndToEnd [Fact] public void TestDotnetCompileNativeCppIncremental() { - if (IsCentOSorRHEL()) - { - Console.WriteLine("Skipping native compilation tests on CentOS/RHEL - https://github.com/dotnet/cli/issues/453"); - return; - } - - if (IsWinX86()) - { - Console.WriteLine("Skipping native compilation tests on Windows x86 - https://github.com/dotnet/cli/issues/1550"); + if(!IsNativeCompilationSupported()) + { return; } @@ -236,26 +215,26 @@ namespace Microsoft.DotNet.Tests.EndToEnd Directory.SetCurrentDirectory(currentDirectory); } - private bool IsCentOSorRHEL() + private bool IsNativeCompilationSupported() { - if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + bool isSupported = true; + var platform = PlatformServices.Default.Runtime.OperatingSystem.ToLower(); + switch (platform) { - const string OSIDFILE = "/etc/os-release"; - - if(File.Exists(OSIDFILE)) - { - string osidcontent = File.ReadAllText(OSIDFILE).ToLower(); - return osidcontent.Contains("centos") || osidcontent.Contains("rhel"); - } + case "centos": + case "rhel": + Console.WriteLine("Skipping native compilation tests on CentOS/RHEL - https://github.com/dotnet/cli/issues/453"); + isSupported = false; + break; + case "windows": + Console.WriteLine("Skipping native compilation tests on Windows x86 - https://github.com/dotnet/cli/issues/1550"); + isSupported = RuntimeInformation.ProcessArchitecture != Architecture.X86; + break; + default: + break; } - return false; - } - - private bool IsWinX86() - { - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && - RuntimeInformation.ProcessArchitecture == Architecture.X86; + return isSupported; } private static DateTime GetLastWriteTimeUtcOfDirectoryFiles(string outputDirectory) From 444e4f9fd71b7bebbc647eae7c0e9cd352645f30 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Mon, 7 Mar 2016 09:34:18 -0800 Subject: [PATCH 09/13] update version of nuget --- .../NuGet.Config | 0 .../Packages/SharedContentA.1.0.0.nupkg | Bin .../project.json | 2 +- src/dotnet/commands/dotnet-restore/Program.cs | 6 ++++-- src/dotnet/project.json | 20 +++++++++--------- test/Installer/testmsi.ps1 | 8 +++---- 6 files changed, 18 insertions(+), 18 deletions(-) rename TestAssets/TestProjects/{TestAppWithContentPackage => }/NuGet.Config (100%) rename TestAssets/TestProjects/{TestAppWithContentPackage => }/Packages/SharedContentA.1.0.0.nupkg (100%) diff --git a/TestAssets/TestProjects/TestAppWithContentPackage/NuGet.Config b/TestAssets/TestProjects/NuGet.Config similarity index 100% rename from TestAssets/TestProjects/TestAppWithContentPackage/NuGet.Config rename to TestAssets/TestProjects/NuGet.Config diff --git a/TestAssets/TestProjects/TestAppWithContentPackage/Packages/SharedContentA.1.0.0.nupkg b/TestAssets/TestProjects/Packages/SharedContentA.1.0.0.nupkg similarity index 100% rename from TestAssets/TestProjects/TestAppWithContentPackage/Packages/SharedContentA.1.0.0.nupkg rename to TestAssets/TestProjects/Packages/SharedContentA.1.0.0.nupkg diff --git a/src/Microsoft.DotNet.ProjectModel/project.json b/src/Microsoft.DotNet.ProjectModel/project.json index 29752a4e7..82b9ecbe2 100644 --- a/src/Microsoft.DotNet.ProjectModel/project.json +++ b/src/Microsoft.DotNet.ProjectModel/project.json @@ -6,7 +6,7 @@ "description": "Types to model a .NET Project", "dependencies": { "System.Reflection.Metadata": "1.2.0-rc2-23901", - "NuGet.Packaging": "3.4.0-beta-632", + "NuGet.Packaging": "3.4.0-rtm-0724", "Microsoft.Extensions.FileSystemGlobbing": "1.0.0-rc2-15996", "Microsoft.Extensions.JsonParser.Sources": { "type": "build", diff --git a/src/dotnet/commands/dotnet-restore/Program.cs b/src/dotnet/commands/dotnet-restore/Program.cs index 8ca6057a1..a42330263 100644 --- a/src/dotnet/commands/dotnet-restore/Program.cs +++ b/src/dotnet/commands/dotnet-restore/Program.cs @@ -180,7 +180,7 @@ namespace Microsoft.DotNet.Tools.Restore Console.WriteLine($"Restoring Tool '{tooldep.Name}' for '{projectPath}' in '{tempPath}'"); File.WriteAllText(projectPath, GenerateProjectJsonContents(new[] {"netstandardapp1.5"}, tooldep)); - return NuGet3.Restore(new [] { $"{projectPath}", "--runtime", $"{DefaultRid}"}.Concat(args), quiet) == 0; + return NuGet3.Restore(new[] { $"{projectPath}" }.Concat(args), quiet) == 0; } private static string GenerateProjectJsonContents(IEnumerable frameworks, LibraryRange tooldep) @@ -194,9 +194,11 @@ namespace Microsoft.DotNet.Tools.Restore foreach (var framework in frameworks) { var importsStatement = "\"imports\": [ \"dnxcore50\", \"portable-net452+win81\" ]"; - sb.AppendLine($" \"{framework}\": {{ {importsStatement} }}"); } + sb.AppendLine(" },"); + sb.AppendLine(" \"runtimes\": { "); + sb.AppendLine($" \"{DefaultRid}\": {{}}"); sb.AppendLine(" }"); sb.AppendLine("}"); var pjContents = sb.ToString(); diff --git a/src/dotnet/project.json b/src/dotnet/project.json index b98fab4ff..dcc0b0182 100644 --- a/src/dotnet/project.json +++ b/src/dotnet/project.json @@ -3,16 +3,16 @@ "compilationOptions": { "emitEntryPoint": true }, - "compileExclude": [ - "commands/dotnet-new/CSharp_Console/**", - "commands/dotnet-new/FSharp_Console/**" + "compileExclude": [ + "commands/dotnet-new/CSharp_Console/**", + "commands/dotnet-new/FSharp_Console/**" ], - "resource": [ - "commands/dotnet-new/CSharp_Console/NuGet.Config", - "commands/dotnet-new/CSharp_Console/Program.cs", - "commands/dotnet-new/CSharp_Console/project.json.template", - "commands/dotnet-new/FSharp_Console/NuGet.config", - "commands/dotnet-new/FSharp_Console/Program.fs", + "resource": [ + "commands/dotnet-new/CSharp_Console/NuGet.Config", + "commands/dotnet-new/CSharp_Console/Program.cs", + "commands/dotnet-new/CSharp_Console/project.json.template", + "commands/dotnet-new/FSharp_Console/NuGet.config", + "commands/dotnet-new/FSharp_Console/Program.fs", "commands/dotnet-new/FSharp_Console/project.json.template" ], "dependencies": { @@ -24,7 +24,7 @@ "Microsoft.CodeAnalysis.CSharp": "1.3.0-beta1-20160225-02", "Microsoft.DiaSymReader.Native": "1.3.3", - "NuGet.CommandLine.XPlat": "3.4.0-beta-632", + "NuGet.CommandLine.XPlat": "3.4.0-rtm-0724", "System.CommandLine": "0.1.0-e160119-1", "Microsoft.DotNet.ProjectModel": "1.0.0-*", diff --git a/test/Installer/testmsi.ps1 b/test/Installer/testmsi.ps1 index 2a11669be..3f4f0d3cc 100644 --- a/test/Installer/testmsi.ps1 +++ b/test/Installer/testmsi.ps1 @@ -14,7 +14,7 @@ function CopyInstaller([string]$destination) # Copy both the .msi and the .exe to the testBin directory so # the tests running in the docker container have access to them. Copy-Item $inputMsi -Destination:$destination - + $BundlePath = [System.IO.Path]::ChangeExtension($inputMsi, "exe") Copy-Item $BundlePath -Destination:$destination } @@ -22,7 +22,7 @@ function CopyInstaller([string]$destination) function CopyTestXUnitRunner([string]$destination) { $XUnitRunnerDir = Join-Path $env:NUGET_PACKAGES xunit.runner.console\2.1.0\tools - + Copy-Item $XUnitRunnerDir\xunit.console.exe -Destination:$destination Copy-Item $XUnitRunnerDir\xunit.runner.utility.desktop.dll -Destination:$destination } @@ -42,7 +42,6 @@ pushd "$Stage2Dir\bin" try { .\dotnet restore ` - --runtime win-anycpu ` $testDir ` -f https://www.myget.org/F/dotnet-buildtools/api/v3/index.json | Out-Host @@ -53,7 +52,6 @@ try { .\dotnet publish ` --framework net46 ` - --runtime win-anycpu ` --output $testBin ` $testDir | Out-Host @@ -68,7 +66,7 @@ try { CopyTestXUnitRunner $testBin Write-Host "Running installer tests in Windows Container" - + # --net="none" works around a networking issue on the containers on the CI machines. # Since our installer tests don't require the network, it is fine to shut it off. $MsiFileName = [System.IO.Path]::GetFileName($inputMsi) From 7cc90d9ad1162db4a5bfcccd667f0f42a1132075 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Tue, 1 Mar 2016 17:42:44 -0800 Subject: [PATCH 10/13] Update dotnet-build to produce portable layout dotnet-build will produce a deps file for portable builds, and will now create "runnable" outputs for RID-less targets the outputs won't actually be runnable today because we need corehost changes and to generate a deps.json file for corehost to use. --- .../BuildTestPortableProject/Lib.cs | 3 + .../BuildTestPortableProject/project.json | 16 +++++ .../BuildTestStandaloneProject/Lib.cs | 3 + .../BuildTestStandaloneProject/project.json | 20 ++++++ scripts/dev-dotnet.ps1 | 9 ++- scripts/dotnet-cli-build/CompileTargets.cs | 7 ++ src/Microsoft.DotNet.Cli.Utils/project.json | 5 +- .../Executable.cs | 12 ++-- .../OutputPathsCalculator.cs | 10 ++- src/Microsoft.DotNet.ProjectModel/Project.cs | 4 +- .../ProjectContext.cs | 26 +++---- .../ProjectReader.cs | 23 ++++--- .../commands/dotnet-build/CompileContext.cs | 29 ++++---- .../dotnet-compile/CompilerCommandApp.cs | 67 ++++++++++++------- .../dotnet-compile/ManagedCompiler.cs | 5 +- test/ArgumentForwardingTests/project.json | 2 +- .../Assertions/CommandResultAssertions.cs | 23 ++++++- .../Commands/BuildCommand.cs | 66 ++++++++++++------ .../BuildInvalidArgumentsTests.cs | 55 +++++++++++++++ test/dotnet-build.Tests/BuildPortableTests.cs | 44 ++++++++++++ .../BuildProjectToProjectTests.cs | 2 +- .../Microsoft.DotNet.Tools.Publish.Tests.cs | 2 +- 22 files changed, 334 insertions(+), 99 deletions(-) create mode 100644 TestAssets/TestProjects/BuildTestPortableProject/Lib.cs create mode 100644 TestAssets/TestProjects/BuildTestPortableProject/project.json create mode 100644 TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs create mode 100644 TestAssets/TestProjects/BuildTestStandaloneProject/project.json create mode 100644 test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs create mode 100644 test/dotnet-build.Tests/BuildPortableTests.cs diff --git a/TestAssets/TestProjects/BuildTestPortableProject/Lib.cs b/TestAssets/TestProjects/BuildTestPortableProject/Lib.cs new file mode 100644 index 000000000..2ac94ca9d --- /dev/null +++ b/TestAssets/TestProjects/BuildTestPortableProject/Lib.cs @@ -0,0 +1,3 @@ +public static class Thingy +{ +} diff --git a/TestAssets/TestProjects/BuildTestPortableProject/project.json b/TestAssets/TestProjects/BuildTestPortableProject/project.json new file mode 100644 index 000000000..43c20c90b --- /dev/null +++ b/TestAssets/TestProjects/BuildTestPortableProject/project.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + }, + "frameworks": { + "net45": {}, + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.0.0-rc2-23901" + } + } + } +} diff --git a/TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs b/TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs new file mode 100644 index 000000000..2ac94ca9d --- /dev/null +++ b/TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs @@ -0,0 +1,3 @@ +public static class Thingy +{ +} diff --git a/TestAssets/TestProjects/BuildTestStandaloneProject/project.json b/TestAssets/TestProjects/BuildTestStandaloneProject/project.json new file mode 100644 index 000000000..d8b5d9d86 --- /dev/null +++ b/TestAssets/TestProjects/BuildTestStandaloneProject/project.json @@ -0,0 +1,20 @@ +{ + "dependencies": { }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "NETStandard.Library": "1.0.0-rc2-23901" + } + } + }, + "runtimes": { + "win7-x64": {}, + "osx.10.10-x64": {}, + "ubuntu.14.04-x64": {}, + "centos.7-x64": {} + } +} diff --git a/scripts/dev-dotnet.ps1 b/scripts/dev-dotnet.ps1 index 8b5f1290f..4728a8b7c 100644 --- a/scripts/dev-dotnet.ps1 +++ b/scripts/dev-dotnet.ps1 @@ -6,11 +6,16 @@ $oldPath = $env:PATH try { # Put the stage2 output on the front of the path - $stage2 = "$PSScriptRoot\..\artifacts\win7-x64\stage2\bin" + if(!(Get-Command dotnet -ErrorAction SilentlyContinue)) { + throw "You need to have a version of 'dotnet' on your path so we can determine the RID" + } + + $rid = dotnet --version | where { $_ -match "^ Runtime Id:\s*(.*)$" } | foreach { $matches[1] } + $stage2 = "$PSScriptRoot\..\artifacts\$rid\stage2\bin" if (Test-Path $stage2) { $env:PATH="$stage2;$env:PATH" } else { - Write-Host "You don't have a dev build in the 'artifacts\win7-x64\stage2' folder!" + Write-Host "You don't have a dev build in the 'artifacts\$rid\stage2' folder!" } dotnet @args diff --git a/scripts/dotnet-cli-build/CompileTargets.cs b/scripts/dotnet-cli-build/CompileTargets.cs index b84fa6452..bbf03cf4f 100644 --- a/scripts/dotnet-cli-build/CompileTargets.cs +++ b/scripts/dotnet-cli-build/CompileTargets.cs @@ -45,6 +45,13 @@ namespace Microsoft.DotNet.Cli.Build "Microsoft.Extensions.Testing.Abstractions" }; + // Updates the stage 2 with recent changes. + [Target(nameof(PrepareTargets.Init), nameof(CompileStage2))] + public static BuildTargetResult UpdateBuild(BuildTargetContext c) + { + return c.Success(); + } + [Target(nameof(PrepareTargets.Init), nameof(CompileCoreHost), nameof(CompileStage1), nameof(CompileStage2))] public static BuildTargetResult Compile(BuildTargetContext c) { diff --git a/src/Microsoft.DotNet.Cli.Utils/project.json b/src/Microsoft.DotNet.Cli.Utils/project.json index 877df020f..625b48bf4 100644 --- a/src/Microsoft.DotNet.Cli.Utils/project.json +++ b/src/Microsoft.DotNet.Cli.Utils/project.json @@ -19,8 +19,9 @@ "netstandard1.3": { "imports": "dnxcore50", "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" + "NETStandard.Library": "1.0.0-rc2-23901", + "System.Diagnostics.Process": "4.1.0-rc2-23901" } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Compiler.Common/Executable.cs b/src/Microsoft.DotNet.Compiler.Common/Executable.cs index 31f92825f..3aedeedc8 100644 --- a/src/Microsoft.DotNet.Compiler.Common/Executable.cs +++ b/src/Microsoft.DotNet.Compiler.Common/Executable.cs @@ -39,11 +39,6 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common public void MakeCompilationOutputRunnable() { - if (string.IsNullOrEmpty(_context.RuntimeIdentifier)) - { - throw new InvalidOperationException($"Can not make output runnable for framework {_context.TargetFramework}, because it doesn't have runtime target"); - } - CopyContentFiles(); ExportRuntimeAssets(); } @@ -72,8 +67,11 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common { WriteDepsFileAndCopyProjectDependencies(_exporter); - // TODO: Pick a host based on the RID - CoreHost.CopyTo(_runtimeOutputPath, _context.ProjectFile.Name + Constants.ExeSuffix); + if (!string.IsNullOrEmpty(_context.RuntimeIdentifier)) + { + // TODO: Pick a host based on the RID + CoreHost.CopyTo(_runtimeOutputPath, _context.ProjectFile.Name + Constants.ExeSuffix); + } } private void CopyContentFiles() diff --git a/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs b/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs index aa9c9fb87..fdaa7c5e3 100644 --- a/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs +++ b/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs @@ -48,12 +48,18 @@ namespace Microsoft.DotNet.ProjectModel { if (!string.IsNullOrEmpty(runtimeIdentifier)) { - runtimeOutputPath= PathUtility.EnsureTrailingSlash(Path.Combine(compilationOutputPath, runtimeIdentifier)); + runtimeOutputPath = PathUtility.EnsureTrailingSlash(Path.Combine(compilationOutputPath, runtimeIdentifier)); + } + else + { + // "Runtime" assets (i.e. the deps file) will be dropped to the compilation output path, because + // we are building a RID-less target. + runtimeOutputPath = compilationOutputPath; } } else { - runtimeOutputPath= PathUtility.EnsureTrailingSlash(Path.GetFullPath(outputPath)); + runtimeOutputPath = PathUtility.EnsureTrailingSlash(Path.GetFullPath(outputPath)); } var intermediateOutputPath = PathUtility.EnsureTrailingSlash(Path.Combine( diff --git a/src/Microsoft.DotNet.ProjectModel/Project.cs b/src/Microsoft.DotNet.ProjectModel/Project.cs index c4a8c4a41..5e187f55b 100644 --- a/src/Microsoft.DotNet.ProjectModel/Project.cs +++ b/src/Microsoft.DotNet.ProjectModel/Project.cs @@ -35,7 +35,7 @@ namespace Microsoft.DotNet.ProjectModel return Path.GetDirectoryName(ProjectFilePath); } } - + public AnalyzerOptions AnalyzerOptions { get; set; } public string Name { get; set; } @@ -124,12 +124,12 @@ namespace Microsoft.DotNet.ProjectModel public bool HasRuntimeOutput(string configuration) { - var compilationOptions = GetCompilerOptions(targetFramework: null, configurationName: configuration); // TODO: Make this opt in via another mechanism return compilationOptions.EmitEntryPoint.GetValueOrDefault() || IsTestProject; } + private CommonCompilerOptions GetCompilerOptions() { return _defaultCompilerOptions; diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs b/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs index 9e237ff11..99f869f36 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs @@ -83,7 +83,7 @@ namespace Microsoft.DotNet.ProjectModel .WithRuntimeIdentifiers(runtimeIdentifiers) .Build(); } - + public static ProjectContextBuilder CreateBuilder(string projectPath, NuGetFramework framework) { if (projectPath.EndsWith(Project.FileName)) @@ -120,15 +120,12 @@ namespace Microsoft.DotNet.ProjectModel /// /// Creates a project context for each target located in the project at /// - public static IEnumerable CreateContextForEachTarget(string projectPath) + public static IEnumerable CreateContextForEachTarget(string projectPath, ProjectReaderSettings settings = null) { - if (!projectPath.EndsWith(Project.FileName)) - { - projectPath = Path.Combine(projectPath, Project.FileName); - } var project = ProjectReader.GetProject(projectPath); return new ProjectContextBuilder() + .WithReaderSettings(settings) .WithProject(project) .BuildAllTargets(); } @@ -143,15 +140,20 @@ namespace Microsoft.DotNet.ProjectModel public ProjectContext CreateRuntimeContext(IEnumerable runtimeIdentifiers) { - var context = Create(ProjectFile.ProjectFilePath, TargetFramework, runtimeIdentifiers); - if (context.RuntimeIdentifier == null) + // Check if there are any runtime targets (i.e. are we portable) + var standalone = LockFile.Targets + .Where(t => t.TargetFramework.Equals(TargetFramework)) + .Any(t => !string.IsNullOrEmpty(t.RuntimeIdentifier)); + + var context = Create(ProjectFile.ProjectFilePath, TargetFramework, standalone ? runtimeIdentifiers : Enumerable.Empty()); + if (standalone && context.RuntimeIdentifier == null) { + // We are standalone, but don't support this runtime var rids = string.Join(", ", runtimeIdentifiers); - throw new InvalidOperationException($"Can not find runtime target for framework '{TargetFramework}' and RID's '{rids}'. " + + throw new InvalidOperationException($"Can not find runtime target for framework '{TargetFramework}' compatible with one of the target runtimes: '{rids}'. " + "Possible causes:" + Environment.NewLine + - "1. Project is not restored or restore failed - run `dotnet restore`" + Environment.NewLine + - "2. Project is not targeting `runable` framework (`netstandardapp*` or `net*`)" - ); + "1. The project has not been restored or restore failed - run `dotnet restore`" + Environment.NewLine + + $"2. The project does not list one of '{rids}' in the 'runtimes' section."); } return context; } diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs b/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs index 2a58b3bd5..39f28ba12 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs @@ -61,18 +61,20 @@ namespace Microsoft.DotNet.ProjectModel return true; } - public static Project GetProject(string projectFile, ProjectReaderSettings settings = null) - { - return GetProject(projectFile, new List(), settings); - } + public static Project GetProject(string projectPath, ProjectReaderSettings settings = null) => GetProject(projectPath, new List(), settings); - public static Project GetProject(string projectFile, ICollection diagnostics, ProjectReaderSettings settings = null) + public static Project GetProject(string projectPath, ICollection diagnostics, ProjectReaderSettings settings = null) { - var name = Path.GetFileName(Path.GetDirectoryName(projectFile)); - - using (var stream = new FileStream(projectFile, FileMode.Open, FileAccess.Read, FileShare.Read)) + if (!projectPath.EndsWith(Project.FileName)) { - return new ProjectReader().ReadProject(stream, name, projectFile, diagnostics, settings); + projectPath = Path.Combine(projectPath, Project.FileName); + } + + var name = Path.GetFileName(Path.GetDirectoryName(projectPath)); + + using (var stream = new FileStream(projectPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + return new ProjectReader().ReadProject(stream, name, projectPath, diagnostics, settings); } } @@ -546,7 +548,8 @@ namespace Microsoft.DotNet.ProjectModel analyzerOptions.LanguageId = languageId; break; - default:; + default: + ; throw FileFormatException.Create( $"Unrecognized analyzerOption key: {key}", project.ProjectFilePath); diff --git a/src/dotnet/commands/dotnet-build/CompileContext.cs b/src/dotnet/commands/dotnet-build/CompileContext.cs index 7e0e43957..de2b43475 100644 --- a/src/dotnet/commands/dotnet-build/CompileContext.cs +++ b/src/dotnet/commands/dotnet-build/CompileContext.cs @@ -48,7 +48,7 @@ namespace Microsoft.DotNet.Tools.Build { CreateOutputDirectories(); - return CompileDendencies(incremental) && CompileRootProject(incremental); + return CompileDependencies(incremental) && CompileRootProject(incremental); } private bool CompileRootProject(bool incremental) @@ -66,7 +66,7 @@ namespace Microsoft.DotNet.Tools.Build return success; } - private bool CompileDendencies(bool incremental) + private bool CompileDependencies(bool incremental) { if (_args.ShouldSkipDependencies) { @@ -395,15 +395,7 @@ namespace Microsoft.DotNet.Tools.Build if (succeeded) { - if (_rootProject.ProjectFile.HasRuntimeOutput(_args.ConfigValue)) - { - MakeRunnable(); - } - else if (!string.IsNullOrEmpty(_args.OutputValue)) - { - var outputPaths = _rootProject.GetOutputPaths(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue); - CopyCompilationOutput(outputPaths); - } + MakeRunnable(); } return succeeded; @@ -428,9 +420,22 @@ namespace Microsoft.DotNet.Tools.Build private void MakeRunnable() { var runtimeContext = _rootProject.CreateRuntimeContext(_args.GetRuntimes()); + if(_args.PortableMode) + { + // HACK: Force the use of the portable target + runtimeContext = _rootProject; + } + var outputPaths = runtimeContext.GetOutputPaths(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue); var libraryExporter = runtimeContext.CreateExporter(_args.ConfigValue, _args.BuildBasePathValue); - CopyCompilationOutput(outputPaths); + + // If we're building for a specific RID, we need to copy the RID-less compilation output into + // the RID-specific output dir + if (!string.IsNullOrEmpty(runtimeContext.RuntimeIdentifier)) + { + CopyCompilationOutput(outputPaths); + } + var executable = new Executable(runtimeContext, outputPaths, libraryExporter); executable.MakeCompilationOutputRunnable(); diff --git a/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs b/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs index 2c7fa2a0f..9a66f1777 100644 --- a/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs +++ b/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using Microsoft.Dnx.Runtime.Common.CommandLine; @@ -28,6 +29,7 @@ namespace Microsoft.DotNet.Tools.Compiler private CommandOption _runtimeOption; private CommandOption _versionSuffixOption; private CommandOption _configurationOption; + private CommandOption _portableOption; private CommandArgument _projectArgument; private CommandOption _nativeOption; private CommandOption _archOption; @@ -41,8 +43,8 @@ namespace Microsoft.DotNet.Tools.Compiler // resolved values for the options and arguments public string ProjectPathValue { get; set; } public string BuildBasePathValue { get; set; } - public string OutputValue { get; set; } public string RuntimeValue { get; set; } + public string OutputValue { get; set; } public string VersionSuffixValue { get; set; } public string ConfigValue { get; set; } public bool IsNativeValue { get; set; } @@ -53,6 +55,7 @@ namespace Microsoft.DotNet.Tools.Compiler public bool IsCppModeValue { get; set; } public string AppDepSdkPathValue { get; set; } public string CppCompilerFlagsValue { get; set; } + public bool PortableMode { get; set; } // workaround: CommandLineApplication is internal therefore I cannot make _app protected so baseclasses can add their own params private readonly Dictionary baseClassOptions; @@ -77,12 +80,15 @@ namespace Microsoft.DotNet.Tools.Compiler _outputOption = _app.Option("-o|--output ", "Directory in which to place outputs", CommandOptionType.SingleValue); _buildBasePath = _app.Option("-b|--build-base-path ", "Directory in which to place temporary outputs", CommandOptionType.SingleValue); - _frameworkOption = _app.Option("-f|--framework ", "Compile a specific framework", CommandOptionType.MultipleValue); + _frameworkOption = _app.Option("-f|--framework ", "Compile a specific framework", CommandOptionType.SingleValue); + _runtimeOption = _app.Option("-r|--runtime ", "Produce runtime-specific assets for the specified runtime", CommandOptionType.SingleValue); _configurationOption = _app.Option("-c|--configuration ", "Configuration under which to build", CommandOptionType.SingleValue); - _runtimeOption = _app.Option("-r|--runtime ", "Target runtime to publish for", CommandOptionType.SingleValue); _versionSuffixOption = _app.Option("--version-suffix ", "Defines what `*` should be replaced with in version field in project.json", CommandOptionType.SingleValue); _projectArgument = _app.Argument("", "The project to compile, defaults to the current directory. Can be a path to a project.json or a project directory"); + // HACK: Allow us to treat a project as though it was portable by ignoring the runtime-specific targets. This is temporary until RID inference is removed from NuGet + _portableOption = _app.Option("--portable", "TEMPORARY: Enforces portable build/publish mode", CommandOptionType.NoValue); + // Native Args _nativeOption = _app.Option("-n|--native", "Compiles source to native machine code.", CommandOptionType.NoValue); _archOption = _app.Option("-a|--arch ", "The architecture for which to compile. x64 only currently supported.", CommandOptionType.SingleValue); @@ -98,6 +104,12 @@ namespace Microsoft.DotNet.Tools.Compiler { _app.OnExecute(() => { + if (_outputOption.HasValue() && !_frameworkOption.HasValue()) + { + Reporter.Error.WriteLine("When the '--output' option is provided, the '--framework' option must also be provided."); + return 1; + } + // Locate the project and get the name and full path ProjectPathValue = _projectArgument.Value; if (string.IsNullOrEmpty(ProjectPathValue)) @@ -110,6 +122,7 @@ namespace Microsoft.DotNet.Tools.Compiler ConfigValue = _configurationOption.Value() ?? Constants.DefaultConfiguration; RuntimeValue = _runtimeOption.Value(); VersionSuffixValue = _versionSuffixOption.Value(); + PortableMode = _portableOption.HasValue(); IsNativeValue = _nativeOption.HasValue(); ArchValue = _archOption.Value(); @@ -120,8 +133,6 @@ namespace Microsoft.DotNet.Tools.Compiler IsCppModeValue = _cppModeOption.HasValue(); CppCompilerFlagsValue = _cppCompilerFlagsOption.Value(); - IEnumerable contexts; - // Set defaults based on the environment var settings = ProjectReaderSettings.ReadFromEnvironment(); @@ -130,29 +141,35 @@ namespace Microsoft.DotNet.Tools.Compiler settings.VersionSuffix = VersionSuffixValue; } - if (_frameworkOption.HasValue()) + // Load the project file and construct all the targets + var targets = ProjectContext.CreateContextForEachFramework(ProjectPathValue, settings).ToList(); + + if (targets.Count == 0) { - contexts = _frameworkOption.Values - .Select(f => - { - return ProjectContext.CreateBuilder(ProjectPathValue, NuGetFramework.Parse(f)) - .WithReaderSettings(settings) - .Build(); - }); - } - else - { - if (!string.IsNullOrEmpty(OutputValue)) - { - throw new InvalidOperationException($"'{_frameworkOption.LongName}' is required when '{_outputOption.LongName}' is specified"); - } - else - { - contexts = ProjectContext.CreateContextForEachFramework(ProjectPathValue, settings); - } + // Project is missing 'frameworks' section + Reporter.Error.WriteLine("Project does not have any frameworks listed in the 'frameworks' section."); + return 1; } - var success = execute(contexts.ToList(), this); + // Filter the targets down based on the inputs + if (_frameworkOption.HasValue()) + { + var fx = NuGetFramework.Parse(_frameworkOption.Value()); + targets = targets.Where(t => fx.Equals(t.TargetFramework)).ToList(); + + if (targets.Count == 0) + { + // We filtered everything out + Reporter.Error.WriteLine($"Project does not support framework: {fx.DotNetFrameworkName}."); + return 1; + } + + Debug.Assert(targets.Count == 1); + } + + Debug.Assert(targets.All(t => string.IsNullOrEmpty(t.RuntimeIdentifier))); + + var success = execute(targets, this); return success ? 0 : 1; }); diff --git a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs index aeb692979..d2b149b31 100644 --- a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs +++ b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs @@ -9,7 +9,6 @@ using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Compilation; -using Microsoft.DotNet.ProjectModel.Utilities; using Microsoft.Extensions.DependencyModel; namespace Microsoft.DotNet.Tools.Compiler @@ -161,6 +160,10 @@ namespace Microsoft.DotNet.Tools.Compiler contextVariables.Add( "compile:RuntimeOutputDir", runtimeOutputPath.RuntimeOutputPath.TrimEnd('\\', '/')); + + contextVariables.Add( + "compile:RuntimeIdentifier", + runtimeContext.RuntimeIdentifier); } _scriptRunner.RunScripts(context, ScriptNames.PreCompile, contextVariables); diff --git a/test/ArgumentForwardingTests/project.json b/test/ArgumentForwardingTests/project.json index 7175750b5..8b99a663f 100644 --- a/test/ArgumentForwardingTests/project.json +++ b/test/ArgumentForwardingTests/project.json @@ -27,5 +27,5 @@ "testRunner": "xunit", - "scripts": { "precompile": "dotnet build ../ArgumentsReflector/project.json --framework netstandardapp1.5 --output %compile:RuntimeOutputDir%" } + "scripts": { "precompile": "dotnet build ../ArgumentsReflector/project.json --framework netstandardapp1.5 --runtime %compile:RuntimeIdentifier% --output %compile:RuntimeOutputDir%" } } diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs index 00516044b..3fab175b5 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/CommandResultAssertions.cs @@ -60,7 +60,14 @@ namespace Microsoft.DotNet.Tools.Test.Utilities return new AndConstraint(this); } - public AndConstraint StdOutMatchPattern(string pattern, RegexOptions options = RegexOptions.None) + public AndConstraint HaveStdOutContaining(string pattern) + { + Execute.Assertion.ForCondition(_commandResult.StdOut.Contains(pattern)) + .FailWith(AppendDiagnosticsTo($"The command output did not contain expected result: {pattern}{Environment.NewLine}")); + return new AndConstraint(this); + } + + public AndConstraint HaveStdOutMatching(string pattern, RegexOptions options = RegexOptions.None) { Execute.Assertion.ForCondition(Regex.Match(_commandResult.StdOut, pattern, options).Success) .FailWith(AppendDiagnosticsTo($"Matching the command output failed. Pattern: {pattern}{Environment.NewLine}")); @@ -74,6 +81,20 @@ namespace Microsoft.DotNet.Tools.Test.Utilities return new AndConstraint(this); } + public AndConstraint HaveStdErrContaining(string pattern) + { + Execute.Assertion.ForCondition(_commandResult.StdErr.Contains(pattern)) + .FailWith(AppendDiagnosticsTo($"The command error output did not contain expected result: {pattern}{Environment.NewLine}")); + return new AndConstraint(this); + } + + public AndConstraint HaveStdErrMatching(string pattern, RegexOptions options = RegexOptions.None) + { + Execute.Assertion.ForCondition(Regex.Match(_commandResult.StdErr, pattern, options).Success) + .FailWith(AppendDiagnosticsTo($"Matching the command error output failed. Pattern: {pattern}{Environment.NewLine}")); + return new AndConstraint(this); + } + public AndConstraint NotHaveStdOut() { Execute.Assertion.ForCondition(string.IsNullOrEmpty(_commandResult.StdOut)) diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs index 93102cf12..da059a1fe 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs @@ -11,23 +11,25 @@ namespace Microsoft.DotNet.Tools.Test.Utilities public sealed class BuildCommand : TestCommand { private Project _project; - private string _projectPath; - private string _outputDirectory; - private string _buidBasePathDirectory; - private string _configuration; - private string _framework; - private string _versionSuffix; - private bool _noHost; - private bool _native; - private string _architecture; - private string _ilcArgs; - private string _ilcPath; - private string _appDepSDKPath; - private bool _nativeCppMode; - private string _cppCompilerFlags; - private bool _buildProfile; - private bool _noIncremental; - private bool _noDependencies; + private readonly string _projectPath; + private readonly string _outputDirectory; + private readonly string _buidBasePathDirectory; + private readonly string _configuration; + private readonly string _framework; + private readonly string _versionSuffix; + private readonly bool _noHost; + private readonly bool _native; + private readonly string _architecture; + private readonly string _ilcArgs; + private readonly string _ilcPath; + private readonly string _appDepSDKPath; + private readonly bool _nativeCppMode; + private readonly string _cppCompilerFlags; + private readonly bool _buildProfile; + private readonly bool _noIncremental; + private readonly bool _noDependencies; + private readonly string _runtime; + private readonly bool _forcePortable; private string OutputOption { @@ -39,6 +41,16 @@ namespace Microsoft.DotNet.Tools.Test.Utilities } } + private string ForcePortableOption + { + get + { + return _forcePortable ? + "--portable" : + string.Empty; + } + } + private string BuildBasePathOption { get @@ -67,7 +79,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities $"--framework {_framework}"; } } - + private string VersionSuffixOption { get @@ -98,6 +110,16 @@ namespace Microsoft.DotNet.Tools.Test.Utilities } } + private string RuntimeOption + { + get + { + return _runtime == string.Empty ? + "" : + $"--runtime {_runtime}"; + } + } + private string ArchitectureOption { get @@ -194,6 +216,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities string buidBasePath="", string configuration="", string framework="", + string runtime="", string versionSuffix="", bool noHost=false, bool native=false, @@ -205,7 +228,8 @@ namespace Microsoft.DotNet.Tools.Test.Utilities string cppCompilerFlags="", bool buildProfile=true, bool noIncremental=false, - bool noDependencies=false + bool noDependencies=false, + bool forcePortable=false ) : base("dotnet") { @@ -217,6 +241,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities _configuration = configuration; _versionSuffix = versionSuffix; _framework = framework; + _runtime = runtime; _noHost = noHost; _native = native; _architecture = architecture; @@ -228,6 +253,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities _buildProfile = buildProfile; _noIncremental = noIncremental; _noDependencies = noDependencies; + _forcePortable = forcePortable; } public override CommandResult Execute(string args = "") @@ -251,7 +277,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities private string BuildArgs() { - return $"{BuildProfile} {NoDependencies} {NoIncremental} \"{_projectPath}\" {OutputOption} {BuildBasePathOption} {ConfigurationOption} {FrameworkOption} {VersionSuffixOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}"; + return $"{BuildProfile} {ForcePortableOption} {NoDependencies} {NoIncremental} \"{_projectPath}\" {OutputOption} {BuildBasePathOption} {ConfigurationOption} {FrameworkOption} {RuntimeOption} {VersionSuffixOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}"; } } } diff --git a/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs b/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs new file mode 100644 index 000000000..15a38b24b --- /dev/null +++ b/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs @@ -0,0 +1,55 @@ +using System.IO; +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; + +namespace Microsoft.DotNet.Tools.Builder.Tests +{ + public class BuildInvalidArgumentsTests : TestBase + { + [Fact] + public void ErrorOccursWhenBuildingPortableProjectToSpecificOutputPathWithoutSpecifyingFramework() + { + var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject") + .WithLockFiles(); + + var result = new BuildCommand( + projectPath: testInstance.TestRoot, + output: Path.Combine(testInstance.TestRoot, "out")) + .ExecuteWithCapturedOutput(); + + result.Should().Fail(); + result.Should().HaveStdErrContaining("When the '--output' option is provided, the '--framework' option must also be provided."); + } + + [Fact] + public void ErrorOccursWhenBuildingPortableProjectAndSpecifyingFrameworkThatProjectDoesNotSupport() + { + var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject") + .WithLockFiles(); + + var result = new BuildCommand( + projectPath: testInstance.TestRoot, + output: Path.Combine(testInstance.TestRoot, "out"), + framework: "sl40") + .ExecuteWithCapturedOutput(); + + result.Should().Fail(); + result.Should().HaveStdErrContaining("Project does not support framework: Silverlight,Version=v4.0."); + } + + [Fact] + public void ErrorOccursWhenBuildingStandaloneProjectToSpecificOutputPathWithoutSpecifyingFramework() + { + var testInstance = TestAssetsManager.CreateTestInstance("BuildTestStandaloneProject") + .WithLockFiles(); + + var result = new BuildCommand( + projectPath: testInstance.TestRoot, + output: Path.Combine(testInstance.TestRoot, "out")) + .ExecuteWithCapturedOutput(); + + result.Should().Fail(); + result.Should().HaveStdErrContaining("When the '--output' option is provided, the '--framework' option must also be provided."); + } + } +} diff --git a/test/dotnet-build.Tests/BuildPortableTests.cs b/test/dotnet-build.Tests/BuildPortableTests.cs new file mode 100644 index 000000000..6ecfad1f1 --- /dev/null +++ b/test/dotnet-build.Tests/BuildPortableTests.cs @@ -0,0 +1,44 @@ +using System.IO; +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; + +namespace Microsoft.DotNet.Tools.Builder.Tests +{ + public class BuildPortableTests : TestBase + { + [Fact] + public void BuildingAPortableProjectProducesDepsFile() + { + var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject") + .WithLockFiles(); + + var result = new BuildCommand( + projectPath: testInstance.TestRoot, + forcePortable: true) + .ExecuteWithCapturedOutput(); + + result.Should().Pass(); + + var outputBase = new DirectoryInfo(Path.Combine(testInstance.TestRoot, "bin", "Debug")); + + var netstandardappOutput = outputBase.Sub("netstandardapp1.5"); + var fxSubdirs = new[] { + netstandardappOutput, + outputBase.Sub("net45") + }; + + foreach(var fxSubdir in fxSubdirs) + { + fxSubdir.Should() + .Exist().And + .HaveFiles(new[] + { + "BuildTestPortableProject.dll", + "BuildTestPortableProject.pdb" + }); + } + + netstandardappOutput.Should().HaveFile("BuildTestPortableProject.deps"); + } + } +} diff --git a/test/dotnet-build.Tests/BuildProjectToProjectTests.cs b/test/dotnet-build.Tests/BuildProjectToProjectTests.cs index c3b55a740..8735a84a6 100644 --- a/test/dotnet-build.Tests/BuildProjectToProjectTests.cs +++ b/test/dotnet-build.Tests/BuildProjectToProjectTests.cs @@ -75,7 +75,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests // second build with no dependencies and no incremental; only the root rebuilds var result2 = BuildProject(noDependencies: true, noIncremental: true); - result2.Should().StdOutMatchPattern("Compiling.*L0.*"); + result2.Should().HaveStdOutMatching("Compiling.*L0.*"); AssertResultDoesNotContainStrings(result2, dependencies); diff --git a/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs b/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs index c1dcfeda3..22562e818 100644 --- a/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs +++ b/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs @@ -188,7 +188,7 @@ namespace Microsoft.DotNet.Tools.Publish.Tests var publishCommand = new PublishCommand(testProject); var result = publishCommand.ExecuteWithCapturedOutput(); - result.Should().StdOutMatchPattern("\nprepublish_output( \\?[^%]+\\?){5}.+\npostpublish_output( \\?[^%]+\\?){5}", RegexOptions.Singleline); + result.Should().HaveStdOutMatching("\nprepublish_output( \\?[^%]+\\?){5}.+\npostpublish_output( \\?[^%]+\\?){5}", RegexOptions.Singleline); result.Should().Pass(); } } From dd3c0f50db98416b9912d94d7446a4d5310a1003 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Mon, 7 Mar 2016 10:51:40 -0800 Subject: [PATCH 11/13] Add reference assemblies and runtime targets support to dependency context builder. --- .../Compilation/LibraryExport.cs | 16 ++- .../Compilation/LibraryExportBuilder.cs | 47 ++++++- .../Compilation/LibraryExporter.cs | 24 ++++ .../Compilation/LibraryResourceAssembly.cs | 18 +++ .../Compilation/LibraryRuntimeTarget.cs | 32 +++++ .../DependencyContextBuilder.cs | 42 ++++-- .../Graph/LockFileReader.cs | 15 ++ .../Graph/LockFileRuntimeTarget.cs | 21 +++ .../Graph/LockFileTargetLibrary.cs | 2 + .../PackageDescription.cs | 4 +- .../CompilationLibrary.cs | 11 +- .../DependencyContextCsvReader.cs | 11 +- .../DependencyContextJsonReader.cs | 28 +++- .../DependencyContextStrings.cs | 4 + .../DependencyContextWriter.cs | 95 +++++++------ .../Library.cs | 5 +- .../ResourceAssembly.cs | 19 +++ .../RuntimeLibrary.cs | 14 +- .../RuntimeTarget.cs | 7 +- .../LibraryExporterPackageTests.cs | 130 ++++++++++++------ .../CompositeResolverTests.cs | 19 ++- .../DependencyContextBuilderTests.cs | 42 ++++-- .../DependencyContextJsonReaderTest.cs | 5 + .../DependencyContextJsonWriterTests.cs | 75 ++++++++++ 24 files changed, 556 insertions(+), 130 deletions(-) create mode 100644 src/Microsoft.DotNet.ProjectModel/Compilation/LibraryResourceAssembly.cs create mode 100644 src/Microsoft.DotNet.ProjectModel/Compilation/LibraryRuntimeTarget.cs create mode 100644 src/Microsoft.DotNet.ProjectModel/Graph/LockFileRuntimeTarget.cs create mode 100644 src/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExport.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExport.cs index 0380fc56a..8d883495f 100644 --- a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExport.cs +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExport.cs @@ -49,6 +49,16 @@ namespace Microsoft.DotNet.ProjectModel.Compilation /// public IEnumerable AnalyzerReferences { get; } + /// + /// Get a list of runtime targets provided by this export. + /// + public IEnumerable RuntimeTargets { get; } + + /// + /// Get a list of resource assemblies provided by this export. + /// + public IEnumerable ResourceAssemblies { get; } + public LibraryExport(LibraryDescription library, IEnumerable compileAssemblies, IEnumerable sourceReferences, @@ -56,7 +66,9 @@ namespace Microsoft.DotNet.ProjectModel.Compilation IEnumerable runtimeAssets, IEnumerable nativeLibraries, IEnumerable embeddedResources, - IEnumerable analyzers) + IEnumerable analyzers, + IEnumerable runtimeTargets, + IEnumerable resourceAssemblies) { Library = library; CompilationAssemblies = compileAssemblies; @@ -66,6 +78,8 @@ namespace Microsoft.DotNet.ProjectModel.Compilation NativeLibraries = nativeLibraries; EmbeddedResources = embeddedResources; AnalyzerReferences = analyzers; + RuntimeTargets = runtimeTargets; + ResourceAssemblies = resourceAssemblies; } private string DebuggerDisplay => Library.Identity.ToString(); diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExportBuilder.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExportBuilder.cs index 42d98c897..3bf323893 100644 --- a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExportBuilder.cs +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExportBuilder.cs @@ -26,6 +26,10 @@ namespace Microsoft.DotNet.ProjectModel.Compilation private IList _analyzerReferences; + private IList _runtimeTargets; + + private IList _resourceAssemblies; + public LibraryDescription Library { get; set; } public IEnumerable RuntimeAssemblies => _runtimeAssemblies; @@ -44,6 +48,10 @@ namespace Microsoft.DotNet.ProjectModel.Compilation public IEnumerable AnalyzerReferences => _analyzerReferences; + public IEnumerable RuntimeTargets => _runtimeTargets; + + public IEnumerable ResourceAssemblies => _resourceAssemblies; + public static LibraryExportBuilder Create(LibraryDescription library = null) { return new LibraryExportBuilder().WithLibrary(library); @@ -63,7 +71,9 @@ namespace Microsoft.DotNet.ProjectModel.Compilation RuntimeAssets ?? EmptyArray.Value, NativeLibraries ?? EmptyArray.Value, EmbeddedResources ?? EmptyArray.Value, - AnalyzerReferences ?? EmptyArray.Value); + AnalyzerReferences ?? EmptyArray.Value, + RuntimeTargets ?? EmptyArray.Value, + ResourceAssemblies ?? EmptyArray.Value); } public LibraryExportBuilder WithLibrary(LibraryDescription libraryDescription) @@ -89,7 +99,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation Replace(ref _compilationAssemblies, assets); return this; } - + public LibraryExportBuilder WithSourceReferences(IEnumerable assets) { Replace(ref _sourceReferences, assets); @@ -114,6 +124,18 @@ namespace Microsoft.DotNet.ProjectModel.Compilation return this; } + public LibraryExportBuilder WithRuntimeTargets(IEnumerable targets) + { + Replace(ref _runtimeTargets, targets); + return this; + } + + public LibraryExportBuilder WithResourceAssemblies(IEnumerable assemblies) + { + Replace(ref _resourceAssemblies, assemblies); + return this; + } + public LibraryExportBuilder AddRuntimeAssembly(LibraryAsset asset) { Add(ref _runtimeAssemblies, asset); @@ -156,9 +178,28 @@ namespace Microsoft.DotNet.ProjectModel.Compilation return this; } + public LibraryExportBuilder AddRuntimeTarget(LibraryRuntimeTarget target) + { + Add(ref _runtimeTargets, target); + return this; + } + + public LibraryExportBuilder AddResourceAssembly(LibraryResourceAssembly assembly) + { + Add(ref _resourceAssemblies, assembly); + return this; + } + private void Replace(ref IList list, IEnumerable enumerable) { - list = new List(enumerable); + if (enumerable == null) + { + list = null; + } + else + { + list = new List(enumerable); + } } private void Add(ref IList list, T item) diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs index 2679750f4..ba21b79e9 100644 --- a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs @@ -122,6 +122,8 @@ namespace Microsoft.DotNet.ProjectModel.Compilation .WithNativeLibraries(libraryExport.NativeLibraries) .WithEmbedddedResources(libraryExport.EmbeddedResources) .WithAnalyzerReference(analyzerReferences) + .WithResourceAssemblies(libraryExport.ResourceAssemblies) + .WithRuntimeTargets(libraryExport.RuntimeTargets) .Build(); } } @@ -199,6 +201,28 @@ namespace Microsoft.DotNet.ProjectModel.Compilation } } } + if (package.RuntimeTargets.Any()) + { + foreach (var targetGroup in package.RuntimeTargets.GroupBy(t => t.Runtime)) + { + var runtime = new List(); + var native = new List(); + + foreach (var lockFileRuntimeTarget in targetGroup) + { + if (string.Equals(lockFileRuntimeTarget.AssetType, "native", StringComparison.OrdinalIgnoreCase)) + { + native.Add(LibraryAsset.CreateFromRelativePath(package.Path, lockFileRuntimeTarget.Path)); + } + else if (string.Equals(lockFileRuntimeTarget.AssetType, "runtime", StringComparison.OrdinalIgnoreCase)) + { + runtime.Add(LibraryAsset.CreateFromRelativePath(package.Path, lockFileRuntimeTarget.Path)); + } + } + + builder.AddRuntimeTarget(new LibraryRuntimeTarget(targetGroup.Key, runtime, native)); + } + } return builder.Build(); } diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryResourceAssembly.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryResourceAssembly.cs new file mode 100644 index 000000000..1520352c3 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryResourceAssembly.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DotNet.ProjectModel.Compilation +{ + public class LibraryResourceAssembly + { + public LibraryResourceAssembly(LibraryAsset asset, string locale) + { + Asset = asset; + Locale = locale; + } + + public LibraryAsset Asset { get; } + + public string Locale { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryRuntimeTarget.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryRuntimeTarget.cs new file mode 100644 index 000000000..9bf303411 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryRuntimeTarget.cs @@ -0,0 +1,32 @@ +// 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.Linq; +using System.Collections.Generic; + +namespace Microsoft.DotNet.ProjectModel.Compilation +{ + public class LibraryRuntimeTarget + { + public LibraryRuntimeTarget(string runtime, + IEnumerable runtimeAssemblies, + IEnumerable nativeLibraries) + { + Runtime = runtime; + RuntimeAssemblies = runtimeAssemblies.ToArray(); + NativeLibraries = nativeLibraries.ToArray(); + } + + public string Runtime { get; } + + /// + /// Gets a list of fully-qualified paths to MSIL binaries required to run + /// + public IReadOnlyList RuntimeAssemblies { get; } + + /// + /// Gets a list of fully-qualified paths to native binaries required to run + /// + public IReadOnlyList NativeLibraries { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs b/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs index da9ad8444..a44b0c410 100644 --- a/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs +++ b/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs @@ -49,8 +49,8 @@ namespace Microsoft.Extensions.DependencyModel runtime, false, GetCompilationOptions(compilerOptions), - GetLibraries(compilationExports, dependencyLookup, runtime: false).Cast().ToArray(), - GetLibraries(runtimeExports, dependencyLookup, runtime: true).Cast().ToArray(), + GetLibraries(compilationExports, dependencyLookup, runtime: false).Cast(), + GetLibraries(runtimeExports, dependencyLookup, runtime: true).Cast(), new KeyValuePair[0]); } @@ -107,14 +107,14 @@ namespace Microsoft.Extensions.DependencyModel } } - string[] assemblies; + IEnumerable assemblies; if (type == LibraryType.ReferenceAssembly) { assemblies = ResolveReferenceAssembliesPath(libraryAssets); } else { - assemblies = libraryAssets.Select(libraryAsset => libraryAsset.RelativePath).ToArray(); + assemblies = libraryAssets.Select(libraryAsset => libraryAsset.RelativePath); } if (runtime) @@ -124,9 +124,10 @@ namespace Microsoft.Extensions.DependencyModel export.Library.Identity.Name, export.Library.Identity.Version.ToString(), export.Library.Hash, - assemblies.Select(RuntimeAssembly.Create).ToArray(), - new RuntimeTarget[0], - libraryDependencies.ToArray(), + assemblies.Select(RuntimeAssembly.Create), + export.ResourceAssemblies.Select(CreateResourceAssembly), + export.RuntimeTargets.Select(CreateRuntimeTarget), + libraryDependencies, serviceable ); } @@ -138,15 +139,31 @@ namespace Microsoft.Extensions.DependencyModel export.Library.Identity.Version.ToString(), export.Library.Hash, assemblies, - libraryDependencies.ToArray(), + libraryDependencies, serviceable ); } } - private string[] ResolveReferenceAssembliesPath(IEnumerable libraryAssets) + private ResourceAssembly CreateResourceAssembly(LibraryResourceAssembly resourceAssembly) + { + return new ResourceAssembly( + path: resourceAssembly.Asset.RelativePath, + locale: resourceAssembly.Locale + ); + } + + private RuntimeTarget CreateRuntimeTarget(LibraryRuntimeTarget runtimeTarget) + { + return new RuntimeTarget( + runtime: runtimeTarget.Runtime, + assemblies: runtimeTarget.RuntimeAssemblies.Select(a => RuntimeAssembly.Create(a.RelativePath)), + nativeLibraries: runtimeTarget.NativeLibraries.Select(l => l.RelativePath) + ); + } + + private IEnumerable ResolveReferenceAssembliesPath(IEnumerable libraryAssets) { - var resolvedPaths = new List(); var referenceAssembliesPath = PathUtility.EnsureTrailingSlash(_referenceAssembliesPath); foreach (var libraryAsset in libraryAssets) @@ -155,14 +172,13 @@ namespace Microsoft.Extensions.DependencyModel // if not, save only assembly name and try to find it somehow later if (libraryAsset.ResolvedPath.StartsWith(referenceAssembliesPath)) { - resolvedPaths.Add(libraryAsset.ResolvedPath.Substring(referenceAssembliesPath.Length)); + yield return libraryAsset.ResolvedPath.Substring(referenceAssembliesPath.Length); } else { - resolvedPaths.Add(Path.GetFileName(libraryAsset.ResolvedPath)); + yield return Path.GetFileName(libraryAsset.ResolvedPath); } } - return resolvedPaths.ToArray(); } } } diff --git a/src/Microsoft.DotNet.ProjectModel/Graph/LockFileReader.cs b/src/Microsoft.DotNet.ProjectModel/Graph/LockFileReader.cs index 3985e20fd..dd5223aef 100644 --- a/src/Microsoft.DotNet.ProjectModel/Graph/LockFileReader.cs +++ b/src/Microsoft.DotNet.ProjectModel/Graph/LockFileReader.cs @@ -170,6 +170,21 @@ namespace Microsoft.DotNet.ProjectModel.Graph return library; } + private static LockFileRuntimeTarget ReadRuntimeTarget(string property, JsonValue json) + { + var jsonObject = json as JsonObject; + if (jsonObject == null) + { + throw FileFormatException.Create("The value type is not an object.", json); + } + + return new LockFileRuntimeTarget( + path: property, + runtime: jsonObject.ValueAsString("runtime"), + assetType: jsonObject.ValueAsString("assetType") + ); + } + private static LockFileContentFile ReadContentFile(string property, JsonValue json) { var contentFile = new LockFileContentFile() diff --git a/src/Microsoft.DotNet.ProjectModel/Graph/LockFileRuntimeTarget.cs b/src/Microsoft.DotNet.ProjectModel/Graph/LockFileRuntimeTarget.cs new file mode 100644 index 000000000..2573c1680 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/Graph/LockFileRuntimeTarget.cs @@ -0,0 +1,21 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DotNet.ProjectModel.Graph +{ + public class LockFileRuntimeTarget + { + public LockFileRuntimeTarget(string path, string runtime, string assetType) + { + Path = path; + Runtime = runtime; + AssetType = assetType; + } + + public string Path { get; } + + public string Runtime { get; } + + public string AssetType { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/Graph/LockFileTargetLibrary.cs b/src/Microsoft.DotNet.ProjectModel/Graph/LockFileTargetLibrary.cs index eb593e3a4..f35cdb031 100644 --- a/src/Microsoft.DotNet.ProjectModel/Graph/LockFileTargetLibrary.cs +++ b/src/Microsoft.DotNet.ProjectModel/Graph/LockFileTargetLibrary.cs @@ -32,5 +32,7 @@ namespace Microsoft.DotNet.ProjectModel.Graph public IList NativeLibraries { get; set; } = new List(); public IList ContentFiles { get; set; } = new List(); + + public IList RuntimeTargets { get; set; } = new List(); } } diff --git a/src/Microsoft.DotNet.ProjectModel/PackageDescription.cs b/src/Microsoft.DotNet.ProjectModel/PackageDescription.cs index fc75e998e..ab050b72b 100644 --- a/src/Microsoft.DotNet.ProjectModel/PackageDescription.cs +++ b/src/Microsoft.DotNet.ProjectModel/PackageDescription.cs @@ -32,7 +32,7 @@ namespace Microsoft.DotNet.ProjectModel } private LockFileTargetLibrary Target { get; } - + public LockFilePackageLibrary Library { get; } public IEnumerable RuntimeAssemblies => FilterPlaceholders(Target.RuntimeAssemblies); @@ -45,6 +45,8 @@ namespace Microsoft.DotNet.ProjectModel public IEnumerable ContentFiles => Target.ContentFiles; + public IEnumerable RuntimeTargets => Target.RuntimeTargets; + private IEnumerable FilterPlaceholders(IList items) { return items.Where(a => !PackageDependencyProvider.IsPlaceholderFile(a)); diff --git a/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs b/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs index 1d5490f0f..2a85c2f8e 100644 --- a/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs +++ b/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs @@ -3,16 +3,23 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.DependencyModel.Resolution; namespace Microsoft.Extensions.DependencyModel { public class CompilationLibrary : Library { - public CompilationLibrary(string type, string name, string version, string hash, string[] assemblies, Dependency[] dependencies, bool serviceable) + public CompilationLibrary(string type, + string name, + string version, + string hash, + IEnumerable assemblies, + IEnumerable dependencies, + bool serviceable) : base(type, name, version, hash, dependencies, serviceable) { - Assemblies = assemblies; + Assemblies = assemblies.ToArray(); } public IReadOnlyList Assemblies { get; } diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextCsvReader.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextCsvReader.cs index 8008fe065..69c4b2bf2 100644 --- a/src/Microsoft.Extensions.DependencyModel/DependencyContextCsvReader.cs +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextCsvReader.cs @@ -46,9 +46,10 @@ namespace Microsoft.Extensions.DependencyModel name: identity.Item2, version: identity.Item3, hash: identity.Item4, - assemblies: packageGroup.Select(l => RuntimeAssembly.Create(l.AssetPath)).ToArray(), - subTargets: new RuntimeTarget[0], - dependencies: new Dependency[] { }, + assemblies: packageGroup.Select(l => RuntimeAssembly.Create(l.AssetPath)), + resourceAssemblies: Enumerable.Empty(), + subTargets: Enumerable.Empty(), + dependencies: Enumerable.Empty(), serviceable: false )); } @@ -58,9 +59,9 @@ namespace Microsoft.Extensions.DependencyModel runtime: string.Empty, isPortable: false, compilationOptions: CompilationOptions.Default, - compileLibraries: new CompilationLibrary[] {}, + compileLibraries: Enumerable.Empty(), runtimeLibraries: runtimeLibraries.ToArray(), - runtimeGraph: new KeyValuePair[0]); + runtimeGraph: Enumerable.Empty>()); } private Tuple PackageIdentity(DepsFileLine line) diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs index e16498313..3dc15822f 100644 --- a/src/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs @@ -173,7 +173,18 @@ namespace Microsoft.Extensions.DependencyModel .Select(RuntimeAssembly.Create) .ToArray(); - return new RuntimeLibrary(stub.Type, name, version, stub.Hash, assemblies, runtimeTargets.ToArray(), dependencies, stub.Serviceable); + var resourceAssemblies = ReadResourceAssemblies((JObject)libraryObject[DependencyContextStrings.ResourceAssembliesPropertyName]); + + return new RuntimeLibrary( + type: stub.Type, + name: name, + version: version, + hash: stub.Hash, + assemblies: assemblies, + resourceAssemblies: resourceAssemblies, + subTargets: runtimeTargets.ToArray(), + dependencies: dependencies, + serviceable: stub.Serviceable); } else { @@ -182,6 +193,21 @@ namespace Microsoft.Extensions.DependencyModel } } + private IEnumerable ReadResourceAssemblies(JObject resourcesObject) + { + if (resourcesObject == null) + { + yield break; + } + foreach (var resourceProperty in resourcesObject) + { + yield return new ResourceAssembly( + locale: resourceProperty.Value[DependencyContextStrings.LocalePropertyName]?.Value(), + path: resourceProperty.Key + ); + } + } + private static IEnumerable ReadRuntimeTargetEntries(JObject runtimeTargetObject) { if (runtimeTargetObject == null) diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs index e42a850b5..7f098877e 100644 --- a/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs @@ -66,5 +66,9 @@ namespace Microsoft.Extensions.DependencyModel internal const string RuntimeAssetType = "runtime"; internal const string NativeAssetType = "native"; + + internal const string ResourceAssembliesPropertyName = "resources"; + + internal const string LocalePropertyName = "locale"; } } \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs index a63f622d4..c5e0ca8ee 100644 --- a/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs @@ -140,49 +140,70 @@ namespace Microsoft.Extensions.DependencyModel return targetObject; } + private void AddCompilationAssemblies(JObject libraryObject, IEnumerable compilationAssemblies) + { + libraryObject.Add(new JProperty(DependencyContextStrings.CompileTimeAssembliesKey, + WriteAssemblies(compilationAssemblies)) + ); + } + + private void AddRuntimeAssemblies(JObject libraryObject, IEnumerable runtimeAssemblies) + { + libraryObject.Add(new JProperty(DependencyContextStrings.RuntimeAssembliesKey, + WriteAssemblies(runtimeAssemblies.Select(a => a.Path))) + ); + } + + private void AddDependencies(JObject libraryObject, IEnumerable dependencies) + { + libraryObject.Add( + new JProperty(DependencyContextStrings.DependenciesPropertyName, + new JObject( + dependencies.Select(dependency => new JProperty(dependency.Name, dependency.Version)))) + ); + } + + private void AddResourceAssemblies(JObject libraryObject, IEnumerable resourceAssemblies) + { + libraryObject.Add(DependencyContextStrings.ResourceAssembliesPropertyName, + new JObject(resourceAssemblies.Select(a => + new JProperty(a.Path, new JObject(new JProperty(DependencyContextStrings.LocalePropertyName, a.Locale)))) + ) + ); + } + private JObject WriteTargetLibrary(Library library) { - string propertyName; - string[] assemblies; - var runtimeLibrary = library as RuntimeLibrary; if (runtimeLibrary != null) { - propertyName = DependencyContextStrings.RuntimeAssembliesKey; - assemblies = runtimeLibrary.Assemblies.Select(assembly => assembly.Path).ToArray(); + var libraryObject = new JObject(); + AddDependencies(libraryObject, runtimeLibrary.Dependencies); + AddRuntimeAssemblies(libraryObject, runtimeLibrary.Assemblies); + AddResourceAssemblies(libraryObject, runtimeLibrary.ResourceAssemblies); + return libraryObject; } - else + + var compilationLibrary = library as CompilationLibrary; + if (compilationLibrary != null) { - var compilationLibrary = library as CompilationLibrary; - if (compilationLibrary != null) - { - propertyName = DependencyContextStrings.CompileTimeAssembliesKey; - assemblies = compilationLibrary.Assemblies.ToArray(); - } - else - { - throw new NotSupportedException(); - } + var libraryObject = new JObject(); + AddDependencies(libraryObject, compilationLibrary.Dependencies); + AddCompilationAssemblies(libraryObject, compilationLibrary.Assemblies); + return libraryObject; } - - - return new JObject( - new JProperty(DependencyContextStrings.DependenciesPropertyName, WriteDependencies(library.Dependencies)), - new JProperty(propertyName, - WriteAssemblies(assemblies)) - ); + throw new NotSupportedException(); } private JObject WritePortableTargetLibrary(RuntimeLibrary runtimeLibrary, CompilationLibrary compilationLibrary) { - var libraryObject = new JObject(); - var dependencies = new HashSet(); + var libraryObject = new JObject(); + + var dependencies = new HashSet(); if (runtimeLibrary != null) { - libraryObject.Add(new JProperty(DependencyContextStrings.RuntimeAssembliesKey, - WriteAssemblies(runtimeLibrary.Assemblies.Select(a => a.Path))) - ); + if (runtimeLibrary.RuntimeTargets.Any()) { libraryObject.Add(new JProperty( @@ -190,21 +211,20 @@ namespace Microsoft.Extensions.DependencyModel new JObject(runtimeLibrary.RuntimeTargets.SelectMany(WriteRuntimeTarget))) ); } + AddResourceAssemblies(libraryObject, runtimeLibrary.ResourceAssemblies); + AddRuntimeAssemblies(libraryObject, runtimeLibrary.Assemblies); dependencies.UnionWith(runtimeLibrary.Dependencies); } if (compilationLibrary != null) { - libraryObject.Add(new JProperty(DependencyContextStrings.CompileTimeAssembliesKey, - WriteAssemblies(compilationLibrary.Assemblies)) - ); + AddCompilationAssemblies(libraryObject, compilationLibrary.Assemblies); + dependencies.UnionWith(compilationLibrary.Dependencies); } - libraryObject.Add( - new JProperty(DependencyContextStrings.DependenciesPropertyName, WriteDependencies(dependencies))); - + AddDependencies(libraryObject, dependencies); return libraryObject; } @@ -241,13 +261,6 @@ namespace Microsoft.Extensions.DependencyModel return new JObject(assemblies.Select(assembly => new JProperty(assembly, new JObject()))); } - private JObject WriteDependencies(IEnumerable dependencies) - { - return new JObject( - dependencies.Select(dependency => new JProperty(dependency.Name, dependency.Version)) - ); - } - private JObject WriteLibraries(DependencyContext context) { var allLibraries = diff --git a/src/Microsoft.Extensions.DependencyModel/Library.cs b/src/Microsoft.Extensions.DependencyModel/Library.cs index 74c5cc2ef..a3fefef42 100644 --- a/src/Microsoft.Extensions.DependencyModel/Library.cs +++ b/src/Microsoft.Extensions.DependencyModel/Library.cs @@ -1,19 +1,20 @@ // 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.Linq; using System.Collections.Generic; namespace Microsoft.Extensions.DependencyModel { public class Library { - public Library(string type, string name, string version, string hash, Dependency[] dependencies, bool serviceable) + public Library(string type, string name, string version, string hash, IEnumerable dependencies, bool serviceable) { Type = type; Name = name; Version = version; Hash = hash; - Dependencies = dependencies; + Dependencies = dependencies.ToArray(); Serviceable = serviceable; } diff --git a/src/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs b/src/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs new file mode 100644 index 000000000..416ed20b1 --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Extensions.DependencyModel +{ + public class ResourceAssembly + { + public ResourceAssembly(string path, string locale) + { + Locale = locale; + Path = path; + } + + public string Locale { get; set; } + + public string Path { get; set; } + + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs b/src/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs index 4519e8590..a3ee2332f 100644 --- a/src/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs +++ b/src/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs @@ -13,18 +13,22 @@ namespace Microsoft.Extensions.DependencyModel string name, string version, string hash, - RuntimeAssembly[] assemblies, - RuntimeTarget[] subTargets, - Dependency[] dependencies, + IEnumerable assemblies, + IEnumerable resourceAssemblies, + IEnumerable subTargets, + IEnumerable dependencies, bool serviceable) : base(type, name, version, hash, dependencies, serviceable) { - Assemblies = assemblies; - RuntimeTargets = subTargets; + Assemblies = assemblies.ToArray(); + ResourceAssemblies = resourceAssemblies.ToArray(); + RuntimeTargets = subTargets.ToArray(); } public IReadOnlyList Assemblies { get; } + public IReadOnlyList ResourceAssemblies { get; } + public IReadOnlyList RuntimeTargets { get; } } } \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/RuntimeTarget.cs b/src/Microsoft.Extensions.DependencyModel/RuntimeTarget.cs index 15b5d9d7f..81771662b 100644 --- a/src/Microsoft.Extensions.DependencyModel/RuntimeTarget.cs +++ b/src/Microsoft.Extensions.DependencyModel/RuntimeTarget.cs @@ -1,14 +1,15 @@ using System.Collections.Generic; +using System.Linq; namespace Microsoft.Extensions.DependencyModel { public class RuntimeTarget { - public RuntimeTarget(string runtime, IReadOnlyList assemblies, IReadOnlyList nativeLibraries) + public RuntimeTarget(string runtime, IEnumerable assemblies, IEnumerable nativeLibraries) { Runtime = runtime; - Assemblies = assemblies; - NativeLibraries = nativeLibraries; + Assemblies = assemblies.ToArray(); + NativeLibraries = nativeLibraries.ToArray(); } public string Runtime { get; } diff --git a/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs b/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs index 2b845545d..b056deedc 100644 --- a/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs +++ b/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs @@ -1,3 +1,6 @@ +// 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 System.IO; @@ -15,36 +18,6 @@ namespace Microsoft.DotNet.ProjectModel.Tests { private const string PackagePath = "PackagePath"; - private LibraryExport ExportSingle(LibraryDescription description = null) - { - var rootProject = new Project() - { - Name = "RootProject", - CompilerName = "csc" - }; - - var rootProjectDescription = new ProjectDescription( - new LibraryRange(), - rootProject, - new LibraryRange[] { }, - new TargetFrameworkInformation(), - true); - - if (description == null) - { - description = rootProjectDescription; - } - else - { - description.Parents.Add(rootProjectDescription); - } - - var libraryManager = new LibraryManager(new[] { description }, new DiagnosticMessage[] { }, ""); - var allExports = new LibraryExporter(rootProjectDescription, libraryManager, "config", "runtime", "basepath", "solutionroot").GetAllExports(); - var export = allExports.Single(); - return export; - } - private PackageDescription CreateDescription(LockFileTargetLibrary target = null, LockFilePackageLibrary package = null) { return new PackageDescription(PackagePath, @@ -54,7 +27,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests } [Fact] - private void ExportsPackageNativeLibraries() + public void ExportsPackageNativeLibraries() { var description = CreateDescription( new LockFileTargetLibrary() @@ -76,7 +49,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests } [Fact] - private void ExportsPackageCompilationAssebmlies() + public void ExportsPackageCompilationAssebmlies() { var description = CreateDescription( new LockFileTargetLibrary() @@ -98,7 +71,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests } [Fact] - private void ExportsPackageRuntimeAssebmlies() + public void ExportsPackageRuntimeAssebmlies() { var description = CreateDescription( new LockFileTargetLibrary() @@ -120,7 +93,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests } [Fact] - private void ExportsSources() + public void ExportsSources() { var description = CreateDescription( package: new LockFilePackageLibrary() @@ -142,7 +115,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests } [Fact] - private void ExportsCopyToOutputContentFiles() + public void ExportsCopyToOutputContentFiles() { var description = CreateDescription( new LockFileTargetLibrary() @@ -170,7 +143,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests [Fact] - private void ExportsResourceContentFiles() + public void ExportsResourceContentFiles() { var description = CreateDescription( new LockFileTargetLibrary() @@ -196,7 +169,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests } [Fact] - private void ExportsCompileContentFiles() + public void ExportsCompileContentFiles() { var description = CreateDescription( new LockFileTargetLibrary() @@ -224,7 +197,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests [Fact] - private void SelectsContentFilesOfProjectCodeLanguage() + public void SelectsContentFilesOfProjectCodeLanguage() { var description = CreateDescription( new LockFileTargetLibrary() @@ -264,7 +237,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests } [Fact] - private void SelectsContentFilesWithNoLanguageIfProjectLanguageNotMathed() + public void SelectsContentFilesWithNoLanguageIfProjectLanguageNotMathed() { var description = CreateDescription( new LockFileTargetLibrary() @@ -295,5 +268,84 @@ namespace Microsoft.DotNet.ProjectModel.Tests libraryAsset.RelativePath.Should().Be(Path.Combine("content", "file.any")); libraryAsset.ResolvedPath.Should().Be(Path.Combine(PackagePath, "content", "file.any")); } + + [Fact] + public void ExportsRuntimeTargets() + { + var win8Native = Path.Combine("native", "win8-x64", "Native.dll"); + var win8Runtime = Path.Combine("runtime", "win8-x64", "Runtime.dll"); + var linuxNative = Path.Combine("native", "linux", "Native.dll"); + + var description = CreateDescription( + new LockFileTargetLibrary() + { + RuntimeTargets = new List() + { + new LockFileRuntimeTarget( + path: win8Native, + runtime: "win8-x64", + assetType: "native" + ), + new LockFileRuntimeTarget( + path: win8Runtime, + runtime: "win8-x64", + assetType: "runtime" + ), + new LockFileRuntimeTarget( + path: linuxNative, + runtime: "linux", + assetType: "native" + ), + } + }); + + var result = ExportSingle(description); + result.RuntimeTargets.Should().HaveCount(2); + + var runtimeTarget = result.RuntimeTargets.Should().Contain(t => t.Runtime == "win8-x64").Subject; + var runtime = runtimeTarget.RuntimeAssemblies.Single(); + runtime.RelativePath.Should().Be(win8Runtime); + runtime.ResolvedPath.Should().Be(Path.Combine(PackagePath, win8Runtime)); + + var native = runtimeTarget.NativeLibraries.Single(); + native.RelativePath.Should().Be(win8Native); + native.ResolvedPath.Should().Be(Path.Combine(PackagePath, win8Native)); + + runtimeTarget = result.RuntimeTargets.Should().Contain(t => t.Runtime == "linux").Subject; + native = runtimeTarget.NativeLibraries.Single(); + native.RelativePath.Should().Be(linuxNative); + native.ResolvedPath.Should().Be(Path.Combine(PackagePath, linuxNative)); + } + + private LibraryExport ExportSingle(LibraryDescription description = null) + { + var rootProject = new Project() + { + Name = "RootProject", + CompilerName = "csc" + }; + + var rootProjectDescription = new ProjectDescription( + new LibraryRange(), + rootProject, + new LibraryRange[] { }, + new TargetFrameworkInformation(), + true); + + if (description == null) + { + description = rootProjectDescription; + } + else + { + description.Parents.Add(rootProjectDescription); + } + + var libraryManager = new LibraryManager(new[] { description }, new DiagnosticMessage[] { }, ""); + var allExports = new LibraryExporter(rootProjectDescription, libraryManager, "config", "runtime", "basepath", "solutionroot").GetAllExports(); + var export = allExports.Single(); + return export; + } + } } diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/CompositeResolverTests.cs b/test/Microsoft.Extensions.DependencyModel.Tests/CompositeResolverTests.cs index f1894aceb..c78bf7d4a 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/CompositeResolverTests.cs +++ b/test/Microsoft.Extensions.DependencyModel.Tests/CompositeResolverTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.DependencyModel.Resolution; using Moq; @@ -54,7 +55,14 @@ namespace StreamForwarderTests failTwo.Object }; - var library = new CompilationLibrary(string.Empty, string.Empty, string.Empty, string.Empty, null, null, false); + var library = new CompilationLibrary( + string.Empty, + string.Empty, + string.Empty, + string.Empty, + Enumerable.Empty(), + Enumerable.Empty(), + false); var resolver = new CompositeCompilationAssemblyResolver(resolvers); var result = resolver.TryResolveAssemblyPaths(library, null); @@ -82,7 +90,14 @@ namespace StreamForwarderTests }; var assemblies = new List(); - var library = new CompilationLibrary(string.Empty, string.Empty, string.Empty, string.Empty, null, null, false); + var library = new CompilationLibrary( + string.Empty, + string.Empty, + string.Empty, + string.Empty, + Enumerable.Empty(), + Enumerable.Empty(), + false); var resolver = new CompositeCompilationAssemblyResolver(resolvers); var result = resolver.TryResolveAssemblyPaths(library, assemblies); diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextBuilderTests.cs b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextBuilderTests.cs index 13eaefaa0..a92199f82 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextBuilderTests.cs +++ b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextBuilderTests.cs @@ -104,9 +104,24 @@ namespace Microsoft.Extensions.DependencyModel.Tests LibraryType.ReferenceAssembly, LibraryDependencyType.Default) }), + resourceAssemblies: new[] + { + new LibraryResourceAssembly( + new LibraryAsset("Dll", "en-US/Pack.Age.resources.dll", ""), + "en-US" + ) + }, runtimeAssemblies: new[] { new LibraryAsset("Dll", "lib/Pack.Age.dll", ""), + }, + runtimeTargets: new [] + { + new LibraryRuntimeTarget( + "win8-x64", + new [] { new LibraryAsset("Dll", "win8-x64/Pack.Age.dll", "") }, + new [] { new LibraryAsset("Dll", "win8-x64/Pack.Age.native.dll", "") } + ) } ), Export(ReferenceAssemblyDescription("System.Collections", @@ -126,6 +141,11 @@ namespace Microsoft.Extensions.DependencyModel.Tests lib.Version.Should().Be("1.2.3"); lib.Dependencies.Should().OnlyContain(l => l.Name == "System.Collections" && l.Version == "3.3.3"); lib.Assemblies.Should().OnlyContain(l => l.Path == "lib/Pack.Age.dll"); + lib.ResourceAssemblies.Should().OnlyContain(l => l.Path == "en-US/Pack.Age.resources.dll" && l.Locale == "en-US"); + + var target = lib.RuntimeTargets.Should().Contain(t => t.Runtime == "win8-x64").Subject; + target.Assemblies.Should().OnlyContain(l => l.Path == "win8-x64/Pack.Age.dll"); + target.NativeLibraries.Should().OnlyContain(l => l == "win8-x64/Pack.Age.native.dll"); var asm = context.RuntimeLibraries.Should().Contain(l => l.Name == "System.Collections").Subject; asm.Type.Should().Be("referenceassembly"); @@ -204,7 +224,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests public void SkipsBuildDependencies() { var context = Build(compilationExports: new[] - { + { Export(PackageDescription("Pack.Age", dependencies: new[] { @@ -225,18 +245,16 @@ namespace Microsoft.Extensions.DependencyModel.Tests private LibraryExport Export( LibraryDescription description, IEnumerable compilationAssemblies = null, - IEnumerable runtimeAssemblies = null) + IEnumerable runtimeAssemblies = null, + IEnumerable runtimeTargets = null, + IEnumerable resourceAssemblies = null) { - return new LibraryExport( - description, - compilationAssemblies ?? Enumerable.Empty(), - Enumerable.Empty(), - runtimeAssemblies ?? Enumerable.Empty(), - Enumerable.Empty(), - Enumerable.Empty(), - Enumerable.Empty(), - Enumerable.Empty() - ); + return LibraryExportBuilder.Create(description) + .WithCompilationAssemblies(compilationAssemblies) + .WithRuntimeAssemblies(runtimeAssemblies) + .WithRuntimeTargets(runtimeTargets) + .WithResourceAssemblies(resourceAssemblies) + .Build(); } private PackageDescription PackageDescription( diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonReaderTest.cs b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonReaderTest.cs index b5fb29a10..b48d812e5 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonReaderTest.cs +++ b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonReaderTest.cs @@ -183,6 +183,9 @@ namespace Microsoft.Extensions.DependencyModel.Tests ""runtimeTargets"": { ""lib/win7/System.Banana.dll"": { ""assetType"": ""runtime"", ""rid"": ""win7-x64""}, ""lib/win7/Banana.dll"": { ""assetType"": ""native"", ""rid"": ""win7-x64""} + }, + ""resources"": { + ""System.Banana.resources.dll"": { ""locale"": ""en-US"" } } } } @@ -211,6 +214,8 @@ namespace Microsoft.Extensions.DependencyModel.Tests package.Type.Should().Be("package"); package.Serviceable.Should().Be(false); package.Assemblies.Should().Contain(a => a.Path == "lib/dotnet5.4/System.Banana.dll"); + package.ResourceAssemblies.Should().Contain(a => a.Path == "System.Banana.resources.dll") + .Subject.Locale.Should().Be("en-US"); var target = package.RuntimeTargets.Should().Contain(t => t.Runtime == "win7-x64").Subject; target.Assemblies.Should().Contain(a => a.Path == "lib/win7/System.Banana.dll"); diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonWriterTests.cs b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonWriterTests.cs index 0e1639fde..2c0b76840 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonWriterTests.cs +++ b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonWriterTests.cs @@ -154,6 +154,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests "1.2.3", "HASH", new [] { RuntimeAssembly.Create("Banana.dll")}, + new [] { new ResourceAssembly("en-US\\Banana.Resource.dll", "en-US")}, new [] { new RuntimeTarget("win7-x64", @@ -187,6 +188,10 @@ namespace Microsoft.Extensions.DependencyModel.Tests nativeLibrary.Should().HavePropertyValue("rid", "win7-x64"); nativeLibrary.Should().HavePropertyValue("assetType", "native"); + var resourceAssemblies = library.Should().HavePropertyAsObject("resources").Subject; + var resourceAssembly = resourceAssemblies.Should().HavePropertyAsObject("en-US\\Banana.Resource.dll").Subject; + resourceAssembly.Should().HavePropertyValue("locale", "en-US"); + //libraries var libraries = result.Should().HavePropertyAsObject("libraries").Subject; library = libraries.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; @@ -224,6 +229,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests "1.2.3", "HASH", new [] { RuntimeAssembly.Create("Banana.dll")}, + new ResourceAssembly[] {}, new [] { new RuntimeTarget("win7-x64", @@ -283,6 +289,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests "1.2.3", "HASH", new [] { RuntimeAssembly.Create("Banana.dll")}, + new ResourceAssembly[] {}, new RuntimeTarget[] {}, new [] { new Dependency("Fruits.Abstract.dll","2.0.0") @@ -308,6 +315,74 @@ namespace Microsoft.Extensions.DependencyModel.Tests library.Should().HavePropertyValue("serviceable", true); } + [Fact] + public void WritesResourceAssembliesForNonPortable() + { + var result = Save(Create( + "Target", + "runtime", + false, + runtimeLibraries: new[] + { + new RuntimeLibrary( + "package", + "PackageName", + "1.2.3", + "HASH", + new RuntimeAssembly[] { }, + new [] + { + new ResourceAssembly("en-US/Fruits.resources.dll", "en-US") + }, + new RuntimeTarget[] { }, + new Dependency[] { }, + true + ), + })); + + var targets = result.Should().HavePropertyAsObject("targets").Subject; + var target = targets.Should().HavePropertyAsObject("Target/runtime").Subject; + var library = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; + var resources = library.Should().HavePropertyAsObject("resources").Subject; + var resource = resources.Should().HavePropertyAsObject("en-US/Fruits.resources.dll").Subject; + resource.Should().HavePropertyValue("locale", "en-US"); + } + + + [Fact] + public void WritesResourceAssembliesForPortable() + { + var result = Save(Create( + "Target", + "runtime", + true, + runtimeLibraries: new[] + { + new RuntimeLibrary( + "package", + "PackageName", + "1.2.3", + "HASH", + new RuntimeAssembly[] { }, + new [] + { + new ResourceAssembly("en-US/Fruits.resources.dll", "en-US") + }, + new RuntimeTarget[] { }, + new Dependency[] { }, + true + ), + })); + + var targets = result.Should().HavePropertyAsObject("targets").Subject; + var target = targets.Should().HavePropertyAsObject("Target").Subject; + var library = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; + var resources = library.Should().HavePropertyAsObject("resources").Subject; + var resource = resources.Should().HavePropertyAsObject("en-US/Fruits.resources.dll").Subject; + resource.Should().HavePropertyValue("locale", "en-US"); + } + + [Fact] public void WritesCompilationOptions() { From 7b84740e72275cdaad8aee84af254b13a9f4a387 Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Tue, 8 Mar 2016 12:14:59 -0800 Subject: [PATCH 12/13] reremove net45 build from portable app test project this somehow got restored during a bad merge, and is breaking the CI --- TestAssets/TestProjects/BuildTestPortableProject/project.json | 1 - 1 file changed, 1 deletion(-) diff --git a/TestAssets/TestProjects/BuildTestPortableProject/project.json b/TestAssets/TestProjects/BuildTestPortableProject/project.json index 43c20c90b..841c77d4d 100644 --- a/TestAssets/TestProjects/BuildTestPortableProject/project.json +++ b/TestAssets/TestProjects/BuildTestPortableProject/project.json @@ -2,7 +2,6 @@ "dependencies": { }, "frameworks": { - "net45": {}, "netstandardapp1.5": { "imports": [ "dnxcore50", From 12dd8d611286f8fb88791b76b4bca97475e71d8e Mon Sep 17 00:00:00 2001 From: Andrew Stanton-Nurse Date: Tue, 8 Mar 2016 14:44:53 -0800 Subject: [PATCH 13/13] fix bad merge that lost removal of net45 from portable app test --- test/dotnet-build.Tests/BuildPortableTests.cs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/test/dotnet-build.Tests/BuildPortableTests.cs b/test/dotnet-build.Tests/BuildPortableTests.cs index 6ecfad1f1..83e1e8544 100644 --- a/test/dotnet-build.Tests/BuildPortableTests.cs +++ b/test/dotnet-build.Tests/BuildPortableTests.cs @@ -22,23 +22,15 @@ namespace Microsoft.DotNet.Tools.Builder.Tests var outputBase = new DirectoryInfo(Path.Combine(testInstance.TestRoot, "bin", "Debug")); var netstandardappOutput = outputBase.Sub("netstandardapp1.5"); - var fxSubdirs = new[] { - netstandardappOutput, - outputBase.Sub("net45") - }; - foreach(var fxSubdir in fxSubdirs) - { - fxSubdir.Should() - .Exist().And - .HaveFiles(new[] - { - "BuildTestPortableProject.dll", - "BuildTestPortableProject.pdb" - }); - } - - netstandardappOutput.Should().HaveFile("BuildTestPortableProject.deps"); + netstandardappOutput.Should() + .Exist().And + .HaveFiles(new[] + { + "BuildTestPortableProject.deps", + "BuildTestPortableProject.dll", + "BuildTestPortableProject.pdb" + }); } } }