dotnet-installer/src/Microsoft.DotNet.Cli.Utils/Command.cs

286 lines
8.5 KiB
C#
Raw Normal View History

// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.DotNet.ProjectModel;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Cli.Utils
{
public class Command : ICommand
{
2015-12-07 14:18:09 -08:00
private readonly Process _process;
private StreamForwarder _stdOut;
private StreamForwarder _stdErr;
private bool _running = false;
2016-01-06 02:27:16 -08:00
private Command(CommandSpec commandSpec)
{
2016-01-06 02:27:16 -08:00
var psi = new ProcessStartInfo
{
2016-01-06 02:27:16 -08:00
FileName = commandSpec.Path,
Arguments = commandSpec.Args,
UseShellExecute = false
};
2016-01-06 02:27:16 -08:00
_process = new Process
{
StartInfo = psi
};
2015-12-07 14:18:09 -08:00
2016-01-06 02:27:16 -08:00
ResolutionStrategy = commandSpec.ResolutionStrategy;
}
public static Command CreateDotNet(
string commandName,
IEnumerable<string> args,
NuGetFramework framework = null,
string configuration = Constants.DefaultConfiguration)
{
return Create("dotnet",
new[] { commandName }.Concat(args),
framework,
configuration: configuration);
}
/// <summary>
/// Create a command with the specified arg array. Args will be
/// escaped properly to ensure that exactly the strings in this
/// array will be present in the corresponding argument array
/// in the command's process.
/// </summary>
public static Command Create(
string commandName,
IEnumerable<string> args,
NuGetFramework framework = null,
string configuration = Constants.DefaultConfiguration,
string outputPath = null,
string applicationName = null)
{
var commandSpec = CommandResolver.TryResolveCommandSpec(commandName,
args,
framework,
configuration: configuration,
outputPath: outputPath,
applicationName: applicationName);
if (commandSpec == null)
{
throw new CommandUnknownException(commandName);
}
var command = new Command(commandSpec);
return command;
}
public static Command Create(CommandSpec commandSpec)
{
return new Command(commandSpec);
}
2016-04-27 16:04:26 -07:00
public static Command CreateForScript(
2016-04-27 16:04:26 -07:00
string commandName,
IEnumerable<string> args,
Project project,
string[] inferredExtensionList)
{
2016-04-27 16:04:26 -07:00
var commandSpec = CommandResolver.TryResolveScriptCommandSpec(commandName,
args,
project,
inferredExtensionList);
2015-10-15 12:18:45 -07:00
2016-01-06 02:27:16 -08:00
if (commandSpec == null)
{
2016-01-06 02:27:16 -08:00
throw new CommandUnknownException(commandName);
}
2016-01-06 02:27:16 -08:00
var command = new Command(commandSpec);
2016-01-06 02:27:16 -08:00
return command;
}
public CommandResult Execute()
{
Reporter.Verbose.WriteLine($"Running {_process.StartInfo.FileName} {_process.StartInfo.Arguments}");
ThrowIfRunning();
_running = true;
_process.EnableRaisingEvents = true;
#if DEBUG
2015-10-18 21:24:26 -07:00
var sw = Stopwatch.StartNew();
2015-11-01 16:21:10 -08:00
Reporter.Verbose.WriteLine($"> {FormatProcessInfo(_process.StartInfo)}".White());
#endif
2016-04-27 16:04:26 -07:00
using (PerfTrace.Current.CaptureTiming($"{Path.GetFileNameWithoutExtension(_process.StartInfo.FileName)} {_process.StartInfo.Arguments}"))
{
_process.Start();
2015-11-29 10:58:13 -08:00
2016-04-27 16:04:26 -07:00
Reporter.Verbose.WriteLine($"Process ID: {_process.Id}");
2015-11-29 10:58:13 -08:00
var taskOut = _stdOut?.BeginRead(_process.StandardOutput);
var taskErr = _stdErr?.BeginRead(_process.StandardError);
2016-04-27 16:04:26 -07:00
_process.WaitForExit();
taskOut?.Wait();
taskErr?.Wait();
2016-04-27 16:04:26 -07:00
}
var exitCode = _process.ExitCode;
#if DEBUG
2015-10-22 05:31:24 -07:00
var message = $"< {FormatProcessInfo(_process.StartInfo)} exited with {exitCode} in {sw.ElapsedMilliseconds} ms.";
2015-10-20 02:42:29 -07:00
if (exitCode == 0)
{
2015-11-01 16:21:10 -08:00
Reporter.Verbose.WriteLine(message.Green());
2015-10-20 02:42:29 -07:00
}
else
{
2015-11-01 16:21:10 -08:00
Reporter.Verbose.WriteLine(message.Red().Bold());
2015-10-20 02:42:29 -07:00
}
#endif
return new CommandResult(
this._process.StartInfo,
exitCode,
_stdOut?.CapturedOutput,
_stdErr?.CapturedOutput);
}
public ICommand WorkingDirectory(string projectDirectory)
{
_process.StartInfo.WorkingDirectory = projectDirectory;
return this;
}
public ICommand EnvironmentVariable(string name, string value)
2015-10-30 15:40:13 -07:00
{
#if NET451
_process.StartInfo.EnvironmentVariables[name] = value;
#else
2015-11-01 06:24:31 -08:00
_process.StartInfo.Environment[name] = value;
#endif
2015-10-30 15:40:13 -07:00
return this;
}
public ICommand CaptureStdOut()
{
ThrowIfRunning();
EnsureStdOut();
2015-12-07 14:18:09 -08:00
_stdOut.Capture();
return this;
}
public ICommand CaptureStdErr()
{
ThrowIfRunning();
EnsureStdErr();
2015-12-07 14:18:09 -08:00
_stdErr.Capture();
return this;
}
public ICommand ForwardStdOut(TextWriter to = null, bool onlyIfVerbose = false, bool ansiPassThrough = true)
{
ThrowIfRunning();
2015-11-01 16:21:10 -08:00
if (!onlyIfVerbose || CommandContext.IsVerbose())
{
EnsureStdOut();
2015-11-01 16:21:10 -08:00
if (to == null)
{
_stdOut.ForwardTo(writeLine: Reporter.Output.WriteLine);
2016-02-12 15:58:35 -08:00
EnvironmentVariable(CommandContext.Variables.AnsiPassThru, ansiPassThrough.ToString());
2015-11-01 16:21:10 -08:00
}
else
{
_stdOut.ForwardTo(writeLine: to.WriteLine);
2015-11-01 16:21:10 -08:00
}
}
return this;
}
public ICommand ForwardStdErr(TextWriter to = null, bool onlyIfVerbose = false, bool ansiPassThrough = true)
{
ThrowIfRunning();
2015-11-01 16:21:10 -08:00
if (!onlyIfVerbose || CommandContext.IsVerbose())
{
EnsureStdErr();
2015-11-01 16:21:10 -08:00
if (to == null)
{
_stdErr.ForwardTo(writeLine: Reporter.Error.WriteLine);
2016-02-12 15:58:35 -08:00
EnvironmentVariable(CommandContext.Variables.AnsiPassThru, ansiPassThrough.ToString());
2015-11-01 16:21:10 -08:00
}
else
{
_stdErr.ForwardTo(writeLine: to.WriteLine);
2015-11-01 16:21:10 -08:00
}
}
return this;
}
public ICommand OnOutputLine(Action<string> handler)
{
ThrowIfRunning();
EnsureStdOut();
_stdOut.ForwardTo(writeLine: handler);
return this;
}
public ICommand OnErrorLine(Action<string> handler)
{
ThrowIfRunning();
EnsureStdErr();
_stdErr.ForwardTo(writeLine: handler);
return this;
}
public CommandResolutionStrategy ResolutionStrategy { get; }
public string CommandName => _process.StartInfo.FileName;
public string CommandArgs => _process.StartInfo.Arguments;
private string FormatProcessInfo(ProcessStartInfo info)
{
if (string.IsNullOrWhiteSpace(info.Arguments))
{
return info.FileName;
}
return info.FileName + " " + info.Arguments;
}
private void EnsureStdOut()
{
_stdOut = _stdOut ?? new StreamForwarder();
_process.StartInfo.RedirectStandardOutput = true;
}
private void EnsureStdErr()
{
_stdErr = _stdErr ?? new StreamForwarder();
_process.StartInfo.RedirectStandardError = true;
}
private void ThrowIfRunning([CallerMemberName] string memberName = null)
{
if (_running)
{
throw new InvalidOperationException($"Unable to invoke {memberName} after the command has been run");
}
}
2015-12-07 14:18:09 -08:00
}
}