Merge pull request #727 from piotrpMSFT/piotrpMSFT/issue679/CommandResolver

Packaged Commands
Fixes #697 
Fixes #226 
Fixes #118
This commit is contained in:
Piotr Puszkiewicz 2016-01-11 01:09:19 -08:00
commit 6803bfd1ac
38 changed files with 815 additions and 360 deletions

1
.gitignore vendored
View file

@ -262,3 +262,4 @@ _Pvt_Extensions
# Exceptions
# Build Scripts
!scripts/build/
test/PackagedCommands/Consumers/*/project.json

View file

@ -34,9 +34,8 @@ else {
header "Compiling"
_ "$RepoRoot\scripts\compile\compile.ps1" @("$Configuration")
# Put stage2 on the PATH now that we have a build
$env:PATH = "$Stage2Dir\bin;$env:PATH"
$env:DOTNET_HOME = "$Stage2Dir"
header "Setting Stage2 as PATH and DOTNET_TOOLS"
setPathAndHome "$Stage2Dir"
header "Running Tests"
_ "$RepoRoot\scripts\test\runtests.ps1"

View file

@ -37,12 +37,11 @@ fi
header "Compiling"
$REPOROOT/scripts/compile/compile.sh
# Put stage2 on the PATH now that we have a build
export DOTNET_TOOLS=$STAGE1_DIR
export PATH=$STAGE2_DIR/bin:$PATH
header "Setting Stage2 as PATH, DOTNET_HOME, and DOTNET_TOOLS"
export DOTNET_HOME=$STAGE2_DIR && export DOTNET_TOOLS=$STAGE2DIR && export PATH=$STAGE2_DIR/bin:$PATH
header "Testing stage2..."
DOTNET_HOME=$STAGE2_DIR DOTNET_TOOLS=$STAGE2_DIR $REPOROOT/scripts/test/runtests.sh
header "Running Tests"
$REPOROOT/scripts/test/runtests.sh
header "Validating Dependencies"
$REPOROOT/scripts/test/validate-dependencies.sh

View file

@ -20,4 +20,4 @@ $PackageDir = "$RepoRoot\artifacts\packages\dnvm"
setEnvIfDefault "DOTNET_INSTALL_DIR" "$(Convert-Path "$PSScriptRoot\..")\.dotnet_stage0\win7-x64"
setEnvIfDefault "DOTNET_BUILD_VERSION" "0.1.0.0"
setPathAndHomeIfDefault "$Stage2Dir"
setEnvIfDefault "CONFIGURATION" "Debug"
setVarIfDefault "Configuration" "Debug"

View file

@ -26,13 +26,26 @@ function setEnvIfDefault([string]$envVarName, [string]$value)
}
}
function setVarIfDefault([string]$varName, [string]$value)
{
If (-not (Get-Variable -name $varName -ErrorAction SilentlyContinue))
{
Set-Variable -name $varName -value $value -scope 1
}
}
function setPathAndHomeIfDefault([string]$rootPath)
{
If ($env:DOTNET_HOME -eq $null)
{
setPathAndHome $rootPath
}
}
function setPathAndHome([string]$rootPath)
{
$env:DOTNET_HOME=$rootPath
$env:PATH="$rootPath\bin;$env:PATH"
}
}
function _([string]$command)

View file

@ -11,7 +11,4 @@ $env:PATH = "$env:DOTNET_INSTALL_DIR\cli\bin;$StartPath"
_ "$RepoRoot\scripts\compile\compile-stage.ps1" @("$Tfm","$Rid","$Configuration","$Stage1Dir","$RepoRoot","$HostDir")
# Copy dnx into stage 1
cp -rec "$DnxRoot\" "$Stage1Dir\bin\dnx\"
$env:PATH=$StartPath

View file

@ -23,11 +23,4 @@ export PATH=$DOTNET_INSTALL_DIR/bin:$PATH
header "Building stage1 dotnet using downloaded stage0 ..."
OUTPUT_DIR=$STAGE1_DIR $REPOROOT/scripts/compile/compile-stage.sh
# Copy DNX in to stage1
cp -R $DNX_ROOT $STAGE1_DIR/bin/dnx
# Copy and CHMOD the dotnet-dnx script
cp $REPOROOT/scripts/dotnet-dnx.sh $STAGE1_DIR/bin/dotnet-dnx
chmod a+x $STAGE1_DIR/bin/dotnet-dnx
export PATH=$StartPath

View file

@ -0,0 +1,69 @@
#
# 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.
#
. "$PSScriptRoot\..\common\_common.ps1"
$TestPackagesPath = "$RepoRoot\tests\packages"
if((Test-Path $TestPackagesPath) -eq 0)
{
mkdir $TestPackagesPath;
}
"v1", "v2" |
foreach {
dotnet pack "$RepoRoot\test\PackagedCommands\Commands\dotnet-hello\$_\dotnet-hello"
cp "$RepoRoot\test\PackagedCommands\Commands\dotnet-hello\$_\dotnet-hello\bin\Debug\*.nupkg" -Destination $TestPackagesPath
if (!$?) {
error "Command failed: dotnet pack"
Exit 1
}
}
# workaround for dotnet-restore from the root failing for these tests since their dependencies aren't built yet
dir "$RepoRoot\test\PackagedCommands\Consumers" | where {$_.PsIsContainer} |
foreach {
pushd "$RepoRoot\test\PackagedCommands\Consumers\$_"
copy project.json.template project.json
popd
}
#restore test projects
pushd "$RepoRoot\test\PackagedCommands\Consumers"
dotnet restore -s "$TestPackagesPath" --no-cache --ignore-failed-sources --parallel
if (!$?) {
error "Command failed: dotnet restore"
Exit 1
}
popd
#compile apps
dir "$RepoRoot\test\PackagedCommands\Consumers" | where {$_.PsIsContainer} | where {$_.Name.Contains("Direct")} |
foreach {
pushd "$RepoRoot\test\PackagedCommands\Consumers\$_"
dotnet compile
popd
}
#run test
dir "$RepoRoot\test\PackagedCommands\Consumers" | where {$_.PsIsContainer} | where {$_.Name.Contains("AppWith")} |
foreach {
$testName = "test\PackagedCommands\Consumers\$_"
pushd "$RepoRoot\$testName"
$outputArray = dotnet hello | Out-String
$output = [string]::Join('\n', $outputArray).Trim("`r", "`n")
del "project.json"
if ($output -ne "hello") {
error "Test Failed: $testName\dotnet hello"
error " printed $output"
Exit 1
}
info "Test passed: $testName"
popd
}
Exit 0

View file

@ -0,0 +1,72 @@
#!/usr/bin/env bash
#
# 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.
#
set -e
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
source "$DIR/../common/_common.sh"
TestPackagesPath="$REPOROOT/tests/packages"
mkdir "$REPOROOT/tests"
mkdir "$TestPackagesPath"
dotnet pack "$REPOROOT/test/PackagedCommands/Commands/dotnet-hello/v1/dotnet-hello"
cp "$REPOROOT/test/PackagedCommands/Commands/dotnet-hello/v1/dotnet-hello/bin/Debug/"*.nupkg "$TestPackagesPath"
dotnet pack "$REPOROOT/test/PackagedCommands/Commands/dotnet-hello/v2/dotnet-hello"
cp "$REPOROOT/test/PackagedCommands/Commands/dotnet-hello/v2/dotnet-hello/bin/Debug/"*.nupkg "$TestPackagesPath"
# enable restore for test projects
for test in $(ls -l "$REPOROOT/test/PackagedCommands/Consumers" | grep ^d | awk '{print $9}' | grep "AppWith")
do
pushd "$REPOROOT/test/PackagedCommands/Consumers/$test"
cp "project.json.template" "project.json"
popd
done
# restore test projects
pushd "$REPOROOT/test/PackagedCommands/Consumers"
dotnet restore -s "$TestPackagesPath" --no-cache --ignore-failed-sources --parallel
popd
#compile tests with direct dependencies
for test in $(ls -l "$REPOROOT/test/PackagedCommands/Consumers" | grep ^d | awk '{print $9}' | grep "Direct")
do
pushd "$REPOROOT/test/PackagedCommands/Consumers/$test"
dotnet compile
popd
done
#run test
for test in $(ls -l "$REPOROOT/test/PackagedCommands/Consumers" | grep ^d | awk '{print $9}' | grep "AppWith")
do
testName="test/PackagedCommands/Consumers/$test"
pushd "$REPOROOT/$testName"
output=$(dotnet hello)
rm "project.json"
if [ "$output" == "Hello" ] ;
then
echo "Test Passed: $testName"
else
error "Test Failed: $testName/dotnet hello"
error " printed $output"
exit 1
fi
popd
done

View file

@ -59,6 +59,13 @@ $TestProjects | ForEach-Object {
popd
& $RepoRoot\scripts\test\package-command-test.ps1
$exitCode = $LastExitCode
if ($exitCode -ne 0) {
$failCount += 1
$failingTests += "package-command-test"
}
if ($failCount -ne 0) {
Write-Host -ForegroundColor Red "The following tests failed."
$failingTests | ForEach-Object {

View file

@ -12,6 +12,7 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli
SOURCE="$(readlink "$SOURCE")"
[[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
source "$DIR/../common/_common.sh"
@ -51,6 +52,12 @@ do
fi
done
"$REPOROOT/scripts/test/package-command-test.sh"
if [ $? -ne 0 ]; then
failCount+=1
failedTests+=("package-command-test.sh")
fi
for test in ${failedTests[@]}
do
error "$test.dll failed. Logs in '$TestBinRoot/${test}-testResults.xml'"

View file

@ -5,12 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Microsoft.DotNet.ProjectModel;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Cli.Utils
@ -21,179 +16,48 @@ namespace Microsoft.DotNet.Cli.Utils
private readonly StreamForwarder _stdOut;
private readonly StreamForwarder _stdErr;
public enum CommandResolutionStrategy
{
//command loaded from a nuget package
NugetPackage,
//command loaded from the same directory as the executing assembly
BaseDirectory,
//command loaded from path
Path,
//command not found
None
}
private bool _running = false;
private Command(string executable, string args, CommandResolutionStrategy resolutionStrategy)
private Command(CommandSpec commandSpec)
{
// Set the things we need
var psi = new ProcessStartInfo()
var psi = new ProcessStartInfo
{
FileName = executable,
Arguments = args,
FileName = commandSpec.Path,
Arguments = commandSpec.Args,
RedirectStandardError = true,
RedirectStandardOutput = true
};
_process = new Process()
_stdOut = new StreamForwarder();
_stdErr = new StreamForwarder();
_process = new Process
{
StartInfo = psi
};
_stdOut = new StreamForwarder();
_stdErr = new StreamForwarder();
ResolutionStrategy = resolutionStrategy;
ResolutionStrategy = commandSpec.ResolutionStrategy;
}
public static Command Create(string executable, IEnumerable<string> args, NuGetFramework framework = null)
public static Command Create(string commandName, IEnumerable<string> args, NuGetFramework framework = null)
{
return Create(executable, string.Join(" ", args), framework);
return Create(commandName, string.Join(" ", args), framework);
}
public static Command Create(string executable, string args, NuGetFramework framework = null)
public static Command Create(string commandName, string args, NuGetFramework framework = null)
{
var commandSpec = CommandResolver.TryResolveCommandSpec(commandName, args, framework);
var resolutionStrategy = CommandResolutionStrategy.None;
ResolveExecutablePath(ref executable, ref args, ref resolutionStrategy, framework);
return new Command(executable, args, resolutionStrategy);
}
private static void ResolveExecutablePath(ref string executable, ref string args, ref CommandResolutionStrategy resolutionStrategy, NuGetFramework framework = null)
{
executable =
ResolveExecutablePathFromProject(executable, framework, ref resolutionStrategy) ??
ResolveExecutableFromPath(executable, ref args, ref resolutionStrategy);
}
private static string ResolveExecutableFromPath(string executable, ref string args, ref CommandResolutionStrategy resolutionStrategy)
{
resolutionStrategy = CommandResolutionStrategy.Path;
foreach (string suffix in Constants.RunnableSuffixes)
if (commandSpec == null)
{
var fullExecutable = Path.GetFullPath(Path.Combine(
AppContext.BaseDirectory, executable + suffix));
if (File.Exists(fullExecutable))
{
executable = fullExecutable;
resolutionStrategy = CommandResolutionStrategy.BaseDirectory;
// In priority order we've found the best runnable extension, so break.
break;
}
throw new CommandUnknownException(commandName);
}
var command = new Command(commandSpec);
// On Windows, we want to avoid using "cmd" if possible (it mangles the colors, and a bunch of other things)
// So, do a quick path search to see if we can just directly invoke it
var useCmd = ShouldUseCmd(executable);
if (useCmd)
{
var comSpec = Environment.GetEnvironmentVariable("ComSpec");
// wrap 'executable' within quotes to deal woth space in its path.
args = $"/S /C \"\"{executable}\" {args}\"";
executable = comSpec;
}
return executable;
return command;
}
private static string ResolveExecutablePathFromProject(string executable, NuGetFramework framework, ref CommandResolutionStrategy resolutionStrategy)
{
if (framework == null) return null;
var projectRootPath = Directory.GetCurrentDirectory();
if (!File.Exists(Path.Combine(projectRootPath, Project.FileName))) return null;
var commandName = Path.GetFileNameWithoutExtension(executable);
var projectContext = ProjectContext.Create(projectRootPath, framework);
var commandPackage = projectContext.LibraryManager.GetLibraries()
.Where(l => l.GetType() == typeof (PackageDescription))
.Select(l => l as PackageDescription)
.FirstOrDefault(p =>
{
var fileNames = p.Library.Files
.Select(Path.GetFileName)
.Where(n => Path.GetFileNameWithoutExtension(n) == commandName)
.ToList();
return fileNames.Contains(commandName + FileNameSuffixes.DotNet.Exe) &&
fileNames.Contains(commandName + FileNameSuffixes.DotNet.DynamicLib) &&
fileNames.Contains(commandName + FileNameSuffixes.Deps);
});
if (commandPackage == null) return null;
var commandPath = commandPackage.Library.Files
.First(f => Path.GetFileName(f) == commandName + FileNameSuffixes.DotNet.Exe);
resolutionStrategy = CommandResolutionStrategy.NugetPackage;
return Path.Combine(projectContext.PackagesDirectory, commandPackage.Path, commandPath);
}
private static bool ShouldUseCmd(string executable)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var extension = Path.GetExtension(executable);
if (!string.IsNullOrEmpty(extension))
{
return !string.Equals(extension, ".exe", StringComparison.Ordinal);
}
else if (executable.Contains(Path.DirectorySeparatorChar))
{
// It's a relative path without an extension
if (File.Exists(executable + ".exe"))
{
// It refers to an exe!
return false;
}
}
else
{
// Search the path to see if we can find it
foreach (var path in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator))
{
var candidate = Path.Combine(path, executable + ".exe");
if (File.Exists(candidate))
{
// We found an exe!
return false;
}
}
}
// It's a non-exe :(
return true;
}
// Non-windows never uses cmd
return false;
}
public CommandResult Execute()
{
Reporter.Verbose.WriteLine($"Running {_process.StartInfo.FileName} {_process.StartInfo.Arguments}");
@ -334,147 +198,4 @@ namespace Microsoft.DotNet.Cli.Utils
}
}
}
public sealed class StreamForwarder
{
private const int DefaultBufferSize = 256;
private readonly int _bufferSize;
private StringBuilder _builder;
private StringWriter _capture;
private Action<string> _write;
private Action<string> _writeLine;
public StreamForwarder(int bufferSize = DefaultBufferSize)
{
_bufferSize = bufferSize;
}
public void Capture()
{
if (_capture != null)
{
throw new InvalidOperationException("Already capturing stream!");
}
_capture = new StringWriter();
}
public string GetCapturedOutput()
{
return _capture?.GetStringBuilder()?.ToString();
}
public void ForwardTo(Action<string> write, Action<string> writeLine)
{
if (writeLine == null)
{
throw new ArgumentNullException(nameof(writeLine));
}
if (_writeLine != null)
{
throw new InvalidOperationException("Already handling stream!");
}
_write = write;
_writeLine = writeLine;
}
public Thread BeginRead(TextReader reader)
{
var thread = new Thread(() => Read(reader)) { IsBackground = true };
thread.Start();
return thread;
}
public void Read(TextReader reader)
{
_builder = new StringBuilder();
var buffer = new char[_bufferSize];
int n;
while ((n = reader.Read(buffer, 0, _bufferSize)) > 0)
{
_builder.Append(buffer, 0, n);
WriteBlocks();
}
WriteRemainder();
}
private void WriteBlocks()
{
int n = _builder.Length;
if (n == 0)
{
return;
}
int offset = 0;
bool sawReturn = false;
for (int i = 0; i < n; i++)
{
char c = _builder[i];
switch (c)
{
case '\r':
sawReturn = true;
continue;
case '\n':
WriteLine(_builder.ToString(offset, i - offset - (sawReturn ? 1 : 0)));
offset = i + 1;
break;
}
sawReturn = false;
}
// If the buffer contains no line breaks and _write is
// supported, send the buffer content.
if (!sawReturn &&
(offset == 0) &&
((_write != null) || (_writeLine == null)))
{
WriteRemainder();
}
else
{
_builder.Remove(0, offset);
}
}
private void WriteRemainder()
{
if (_builder.Length == 0)
{
return;
}
Write(_builder.ToString());
_builder.Clear();
}
private void WriteLine(string str)
{
if (_capture != null)
{
_capture.WriteLine(str);
}
// If _write is supported, so is _writeLine.
if (_writeLine != null)
{
_writeLine(str);
}
}
private void Write(string str)
{
if (_capture != null)
{
_capture.Write(str);
}
if (_write != null)
{
_write(str);
}
else if (_writeLine != null)
{
_writeLine(str);
}
}
}
}

View file

@ -2,9 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.DotNet.Cli.Utils
{

View file

@ -0,0 +1,17 @@
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 path
Path,
//command not found
None
}
}

View file

@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using System.IO;
using NuGet.Frameworks;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Graph;
using NuGet.Packaging;
namespace Microsoft.DotNet.Cli.Utils
{
internal static class CommandResolver
{
public static CommandSpec TryResolveCommandSpec(string commandName, string args, NuGetFramework framework = null)
{
return ResolveFromRootedCommand(commandName, args) ??
ResolveFromProjectDependencies(commandName, args, framework) ??
ResolveFromProjectTools(commandName, args) ??
ResolveFromPath(commandName, args);
}
private static CommandSpec ResolveFromPath(string commandName, string args)
{
var commandPath = Env.GetCommandPath(commandName);
if (commandPath == null) return null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
Path.GetExtension(commandPath).Equals(".cmd", StringComparison.OrdinalIgnoreCase))
{
var preferredCommandPath = Env.GetCommandPath(commandName, ".exe");
if (preferredCommandPath != null)
{
commandPath = Environment.GetEnvironmentVariable("ComSpec");
args = $"/S /C \"\"{preferredCommandPath}\" {args}\"";
}
}
return new CommandSpec(commandPath, args, CommandResolutionStrategy.Path);
}
private static CommandSpec ResolveFromRootedCommand(string commandName, string args)
{
if (Path.IsPathRooted(commandName))
{
return new CommandSpec(commandName, args, CommandResolutionStrategy.Path);
}
return null;
}
public static CommandSpec ResolveFromProjectDependencies(string commandName, string args,
NuGetFramework framework)
{
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 = GetDepsPath(projectContext, Constants.DefaultConfiguration);
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);
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, string args)
{
var context = GetProjectContext(FrameworkConstants.CommonFrameworks.DnxCore50);
if (context == null)
{
return null;
}
var commandLibrary = context.ProjectFile.Tools
.FirstOrDefault(l => l.Name == commandName);
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, string args, string packageDir)
{
var commandPackage = new PackageFolderReader(packageDir);
var files = commandPackage.GetFiles();
return ConfigureCommandFromPackage(commandName, args, files, packageDir);
}
private static CommandSpec ConfigureCommandFromPackage(string commandName, string args,
PackageDescription commandPackage, ProjectContext projectContext, string depsPath = null)
{
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, string args,
IEnumerable<string> 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 dllPath = files
.Where(f => Path.GetFileName(f) == commandName + FileNameSuffixes.DotNet.DynamicLib)
.Select(f => Path.Combine(packageDir, f))
.FirstOrDefault();
fileName = CoreHost.Path;
if (depsPath != null)
{
args = $"--tpaFile:\"{depsPath}\" {args}";
}
args = $"\"{dllPath}\" {args}";
}
else
{
fileName = Path.Combine(packageDir, commandPath);
}
return new CommandSpec(fileName, args, CommandResolutionStrategy.NugetPackage);
}
private static string GetDepsPath(ProjectContext context, string buildConfiguration)
{
return Path.Combine(context.GetOutputDirectoryPath(buildConfiguration),
context.ProjectFile.Name + FileNameSuffixes.Deps);
}
}
}

View file

@ -0,0 +1,18 @@
namespace Microsoft.DotNet.Cli.Utils
{
internal class CommandSpec
{
public CommandSpec(string path, string args, CommandResolutionStrategy resolutionStrategy)
{
Path = path;
Args = args;
ResolutionStrategy = resolutionStrategy;
}
public string Path { get; }
public string Args { get; }
public CommandResolutionStrategy ResolutionStrategy { get; }
}
}

View file

@ -0,0 +1,19 @@
using System;
namespace Microsoft.DotNet.Cli.Utils
{
public class CommandUnknownException : Exception
{
public CommandUnknownException()
{
}
public CommandUnknownException(string commandName) : base($"No executable found matching command \"{commandName}\"")
{
}
public CommandUnknownException(string commandName, Exception innerException) : base($"No executable found matching command \"{commandName}\"", innerException)
{
}
}
}

View file

@ -1,10 +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.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace Microsoft.DotNet.Cli.Utils
@ -13,12 +9,7 @@ namespace Microsoft.DotNet.Cli.Utils
{
public static readonly string ProjectFileName = "project.json";
public static readonly string ExeSuffix = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
// Priority order of runnable suffixes to look for and run
public static readonly string[] RunnableSuffixes = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? new string[] { ".exe", ".cmd", ".bat" }
: new string[] { string.Empty };
public static readonly string HostExecutableName = "corehost" + ExeSuffix;
public static readonly string DefaultConfiguration = "Debug";
public static readonly string BinDirectoryName = "bin";

View file

@ -1,10 +1,9 @@
using System.IO;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Cli.Compiler.Common
namespace Microsoft.DotNet.Cli.Utils
{
internal static class CoreHost
public static class CoreHost
{
internal static string _path;

View file

@ -0,0 +1,150 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
namespace Microsoft.DotNet.Cli.Utils
{
public sealed class StreamForwarder
{
private const int DefaultBufferSize = 256;
private readonly int _bufferSize;
private StringBuilder _builder;
private StringWriter _capture;
private Action<string> _write;
private Action<string> _writeLine;
public StreamForwarder(int bufferSize = DefaultBufferSize)
{
_bufferSize = bufferSize;
}
public void Capture()
{
if (_capture != null)
{
throw new InvalidOperationException("Already capturing stream!");
}
_capture = new StringWriter();
}
public string GetCapturedOutput()
{
return _capture?.GetStringBuilder()?.ToString();
}
public void ForwardTo(Action<string> write, Action<string> writeLine)
{
if (writeLine == null)
{
throw new ArgumentNullException(nameof(writeLine));
}
if (_writeLine != null)
{
throw new InvalidOperationException("Already handling stream!");
}
_write = write;
_writeLine = writeLine;
}
public Thread BeginRead(TextReader reader)
{
var thread = new Thread(() => Read(reader)) { IsBackground = true };
thread.Start();
return thread;
}
public void Read(TextReader reader)
{
_builder = new StringBuilder();
var buffer = new char[_bufferSize];
int n;
while ((n = reader.Read(buffer, 0, _bufferSize)) > 0)
{
_builder.Append(buffer, 0, n);
WriteBlocks();
}
WriteRemainder();
}
private void WriteBlocks()
{
int n = _builder.Length;
if (n == 0)
{
return;
}
int offset = 0;
bool sawReturn = false;
for (int i = 0; i < n; i++)
{
char c = _builder[i];
switch (c)
{
case '\r':
sawReturn = true;
continue;
case '\n':
WriteLine(_builder.ToString(offset, i - offset - (sawReturn ? 1 : 0)));
offset = i + 1;
break;
}
sawReturn = false;
}
// If the buffer contains no line breaks and _write is
// supported, send the buffer content.
if (!sawReturn &&
(offset == 0) &&
((_write != null) || (_writeLine == null)))
{
WriteRemainder();
}
else
{
_builder.Remove(0, offset);
}
}
private void WriteRemainder()
{
if (_builder.Length == 0)
{
return;
}
Write(_builder.ToString());
_builder.Clear();
}
private void WriteLine(string str)
{
if (_capture != null)
{
_capture.WriteLine(str);
}
// If _write is supported, so is _writeLine.
if (_writeLine != null)
{
_writeLine(str);
}
}
private void Write(string str)
{
if (_capture != null)
{
_capture.Write(str);
}
if (_write != null)
{
_write(str);
}
else if (_writeLine != null)
{
_writeLine(str);
}
}
}
}

View file

@ -9,6 +9,12 @@
},
"frameworks": {
"dnxcore50": { }
"dnxcore50": { }
},
"scripts": {
"postcompile": [
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.dll\"",
"../../scripts/build/place-binary \"%compile:OutputDir%/%project:Name%.pdb\""
]
}
}

View file

@ -44,9 +44,25 @@ Common Commands:
{
DebugHelper.HandleDebugSwitch(ref args);
try
{
return ProcessArgs(args);
}
catch (CommandUnknownException e)
{
Console.WriteLine(e.Message);
return 1;
}
}
private static int ProcessArgs(string[] args)
{
// CommandLineApplication is a bit restrictive, so we parse things ourselves here. Individual apps should use CLA.
var verbose = false;
var success = true;
var success = true;
var command = string.Empty;
var lastArg = 0;
for (; lastArg < args.Length; lastArg++)
@ -77,7 +93,8 @@ Common Commands:
break;
}
}
if (!success) {
if (!success)
{
PrintHelp();
return 1;
}
@ -89,7 +106,7 @@ Common Commands:
return RunHelpCommand(appArgs);
}
return Command.Create("dotnet-" + command, appArgs, new NuGetFramework("DNXCore", Version.Parse("5.0")))
return Command.Create("dotnet-" + command, appArgs, FrameworkConstants.CommonFrameworks.DnxCore50)
.EnvironmentVariable(CommandContext.Variables.Verbose, verbose.ToString())
.EnvironmentVariable(CommandContext.Variables.AnsiPassThru, bool.TrueString)
.ForwardStdErr()

View file

@ -6,7 +6,7 @@ using Microsoft.DotNet.ProjectModel.Compilation;
namespace Microsoft.DotNet.Cli.Compiler.Common
{
internal static class LibraryExporterExtensions
public static class LibraryExporterExtensions
{
internal static void CopyProjectDependenciesTo(this LibraryExporter exporter, string path, params ProjectDescription[] except)
{
@ -17,7 +17,7 @@ namespace Microsoft.DotNet.Cli.Compiler.Common
.CopyTo(path);
}
internal static void WriteDepsTo(this IEnumerable<LibraryExport> exports, string path)
public static void WriteDepsTo(this IEnumerable<LibraryExport> exports, string path)
{
File.WriteAllLines(path, exports.SelectMany(GenerateLines));
}

View file

@ -202,10 +202,5 @@ namespace Microsoft.DotNet.Cli.Compiler.Common
appConfig.Save(stream);
}
}
public static string GetDepsPath(this ProjectContext context, string buildConfiguration)
{
return Path.Combine(context.GetOutputDirectoryPath(buildConfiguration), context.ProjectFile.Name + ".deps");
}
}
}

View file

@ -148,7 +148,7 @@ namespace Microsoft.DotNet.Tools.Build
{
var pathCommands = CompilerUtil.GetCommandsInvokedByCompile(project)
.Select(commandName => Command.Create(commandName, "", project.TargetFramework))
.Where(c => Command.CommandResolutionStrategy.Path.Equals(c.ResolutionStrategy));
.Where(c => c.ResolutionStrategy.Equals(CommandResolutionStrategy.Path));
foreach (var pathCommand in pathCommands)
{

View file

@ -6,10 +6,12 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.Dnx.Runtime.Common.CommandLine;
using Microsoft.DotNet.Cli.Compiler.Common;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Compilation;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.ProjectModel.Utilities;
using NuGet.Frameworks;
using Microsoft.Extensions.DependencyModel;
@ -350,6 +352,17 @@ namespace Microsoft.DotNet.Tools.Compiler
runtimeContext
.MakeCompilationOutputRunnable(outputPath, args.ConfigValue);
}
else if (!string.IsNullOrEmpty(context.ProjectFile.TestRunner))
{
var projectContext =
ProjectContext.Create(context.ProjectDirectory, context.TargetFramework,
new[] { PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier()});
projectContext
.CreateExporter(args.ConfigValue)
.GetDependencies(LibraryType.Package)
.WriteDepsTo(Path.Combine(outputPath, projectContext.ProjectFile.Name + FileNameSuffixes.Deps));
}
return PrintSummary(diagnostics, sw, success);
}

View file

@ -43,7 +43,7 @@ namespace Microsoft.DotNet.Tools.Restore
{
var project = ProjectReader.GetProject(restoreTask.ProjectPath);
RestoreTools(project, restoreTask.Arguments);
RestoreTools(project, restoreTask);
}
return projectRestoreResult;
@ -60,7 +60,6 @@ namespace Microsoft.DotNet.Tools.Restore
return -2;
}
});
return app.Execute(args);
@ -101,33 +100,39 @@ namespace Microsoft.DotNet.Tools.Restore
return firstArg.EndsWith(Project.FileName) && File.Exists(firstArg);
}
private static void RestoreTools(Project project, IEnumerable<string> args)
private static void RestoreTools(Project project, RestoreTask restoreTask)
{
foreach (var tooldep in project.Tools)
{
RestoreTool(tooldep, args);
RestoreTool(tooldep, restoreTask);
}
}
private static void RestoreTool(LibraryRange tooldep, IEnumerable<string> args)
private static void RestoreTool(LibraryRange tooldep, RestoreTask restoreTask)
{
var tempPath = Path.Combine(Directory.GetCurrentDirectory(), Guid.NewGuid().ToString(), "bin");
var tempPath = Path.Combine(restoreTask.ProjectDirectory, Guid.NewGuid().ToString(), "bin");
RestoreToolToPath(tooldep, args, tempPath);
RestoreToolToPath(tooldep, restoreTask.Arguments, tempPath);
CreateDepsInPackageCache(tooldep, tempPath);
PersistLockFile(tooldep, tempPath);
PersistLockFile(tooldep, tempPath, restoreTask.ProjectDirectory);
Directory.Delete(tempPath, true);
}
private static void PersistLockFile(LibraryRange tooldep, string tempPath)
private static void PersistLockFile(LibraryRange tooldep, string tempPath, string projectPath)
{
var targetPath = Path.Combine(Directory.GetCurrentDirectory(), "artifacts", "Tools", tooldep.Name);
if (Directory.Exists(targetPath)) Directory.Delete(targetPath, true);
Directory.CreateDirectory(targetPath);
File.Move(Path.Combine(tempPath, "project.lock.json"), Path.Combine(targetPath, "project.lock.json"));
var sourcePath = Path.Combine(tempPath, "project.lock.json");
var targetDir = Path.Combine(projectPath, "artifacts", "Tools", tooldep.Name);
var targetPath = Path.Combine(targetDir, "project.lock.json");
if (Directory.Exists(targetDir)) Directory.Delete(targetDir, true);
Directory.CreateDirectory(targetDir);
Console.WriteLine($"Writing '{sourcePath}' to '{targetPath}'");
File.Move(sourcePath, targetPath);
}
private static void CreateDepsInPackageCache(LibraryRange toolLibrary, string projectPath)
@ -156,6 +161,9 @@ namespace Microsoft.DotNet.Tools.Restore
{
Directory.CreateDirectory(tempPath);
var projectPath = Path.Combine(tempPath, Project.FileName);
Console.WriteLine($"Restoring Tool '{tooldep.Name}' for '{projectPath}' in '{tempPath}'");
File.WriteAllText(projectPath, GenerateProjectJsonContents(new[] {"dnxcore50"}));
Dnx.RunPackageInstall(tooldep, projectPath, args);
Dnx.RunRestore(new [] { $"\"{projectPath}\"", "--runtime", $"{DefaultRid}"}.Concat(args));

View file

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Tools.Restore
{
@ -6,6 +9,10 @@ namespace Microsoft.DotNet.Tools.Restore
{
public string ProjectPath { get; set; }
public IEnumerable<string> Arguments { get; set; }
public IEnumerable<string> Arguments { get; set; }
public string ProjectDirectory => ProjectPath.EndsWith(Project.FileName, StringComparison.OrdinalIgnoreCase)
? Path.GetDirectoryName(ProjectPath)
: ProjectPath;
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello");
}
}
}

View file

@ -0,0 +1,14 @@
{
"version": "1.0.0",
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616"
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

View file

@ -0,0 +1,14 @@
{
"version": "2.0.0",
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616"
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

View file

@ -0,0 +1,21 @@
{
"version": "1.0.0-*",
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616",
"dotnet-hello": { "version": "1.0.0", "target": "package" }
},
"frameworks": {
"dnxcore50": { }
},
"testRunner": "must-be-specified-to-generate-deps",
"tools": {
"dotnet-hello": { "version": "2.0.0", "target": "package" }
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

View file

@ -0,0 +1,17 @@
{
"version": "1.0.0-*",
"compilationOptions": {
"emitEntryPoint": true
},
"testRunner": "must-be-specified-to-generate-deps",
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616",
"dotnet-hello": {"version": "1.0.0", "target": "package"}
},
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace ConsoleApplication
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

View file

@ -0,0 +1,18 @@
{
"version": "1.0.0-*",
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616"
},
"frameworks": {
"dnxcore50": { }
},
"tools": {
"dotnet-hello": { "version": "1.0.0", "target": "package" }
}
}