2015-11-16 11:21:57 -08:00
|
|
|
|
// 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;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.IO;
|
2016-01-30 21:47:50 -08:00
|
|
|
|
using System.Linq;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
using System.Runtime.CompilerServices;
|
2015-11-28 00:28:45 -08:00
|
|
|
|
using NuGet.Frameworks;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
|
|
|
|
namespace Microsoft.DotNet.Cli.Utils
|
|
|
|
|
{
|
2016-02-09 16:35:43 -08:00
|
|
|
|
public class Command : ICommand
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2015-12-07 14:18:09 -08:00
|
|
|
|
private readonly Process _process;
|
|
|
|
|
private readonly StreamForwarder _stdOut;
|
|
|
|
|
private readonly StreamForwarder _stdErr;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
|
|
|
|
private bool _running = false;
|
|
|
|
|
|
2016-01-06 02:27:16 -08:00
|
|
|
|
private Command(CommandSpec commandSpec)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2016-01-06 02:27:16 -08:00
|
|
|
|
var psi = new ProcessStartInfo
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2016-01-06 02:27:16 -08:00
|
|
|
|
FileName = commandSpec.Path,
|
|
|
|
|
Arguments = commandSpec.Args,
|
2015-10-06 10:46:43 -07:00
|
|
|
|
RedirectStandardError = true,
|
|
|
|
|
RedirectStandardOutput = true
|
|
|
|
|
};
|
|
|
|
|
|
2016-01-06 02:27:16 -08:00
|
|
|
|
_stdOut = new StreamForwarder();
|
|
|
|
|
_stdErr = new StreamForwarder();
|
2016-02-09 16:35:43 -08:00
|
|
|
|
|
2016-01-06 02:27:16 -08:00
|
|
|
|
_process = new Process
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
StartInfo = psi
|
|
|
|
|
};
|
2015-12-07 14:18:09 -08:00
|
|
|
|
|
2016-01-06 02:27:16 -08:00
|
|
|
|
ResolutionStrategy = commandSpec.ResolutionStrategy;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2016-01-30 21:47:50 -08:00
|
|
|
|
public static Command CreateDotNet(string commandName, IEnumerable<string> args, NuGetFramework framework = null, bool useComSpec = false)
|
|
|
|
|
{
|
|
|
|
|
return Create("dotnet", new[] { commandName }.Concat(args), framework, useComSpec);
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-22 14:03:40 -08:00
|
|
|
|
/// <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>
|
|
|
|
|
/// <param name="commandName"></param>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
/// <param name="framework"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static Command Create(string commandName, IEnumerable<string> args, NuGetFramework framework = null, bool useComSpec = false)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2016-01-30 21:47:50 -08:00
|
|
|
|
var commandSpec = CommandResolver.TryResolveCommandSpec(commandName, args, framework, useComSpec);
|
2015-10-15 12:18:45 -07:00
|
|
|
|
|
2016-01-06 02:27:16 -08:00
|
|
|
|
if (commandSpec == null)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2016-01-06 02:27:16 -08:00
|
|
|
|
throw new CommandUnknownException(commandName);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
2016-01-22 14:03:40 -08:00
|
|
|
|
|
2016-01-06 02:27:16 -08:00
|
|
|
|
var command = new Command(commandSpec);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
2016-01-06 02:27:16 -08:00
|
|
|
|
return command;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
2016-02-09 16:35:43 -08:00
|
|
|
|
|
2015-10-21 03:11:27 -07:00
|
|
|
|
public CommandResult Execute()
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
2016-01-22 14:03:40 -08:00
|
|
|
|
|
2015-11-20 19:00:56 -08:00
|
|
|
|
Reporter.Verbose.WriteLine($"Running {_process.StartInfo.FileName} {_process.StartInfo.Arguments}");
|
|
|
|
|
|
2015-10-06 10:46:43 -07:00
|
|
|
|
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());
|
2015-10-06 10:46:43 -07:00
|
|
|
|
#endif
|
|
|
|
|
_process.Start();
|
2015-11-29 10:58:13 -08:00
|
|
|
|
|
|
|
|
|
Reporter.Verbose.WriteLine($"Process ID: {_process.Id}");
|
|
|
|
|
|
2015-12-07 14:18:09 -08:00
|
|
|
|
var threadOut = _stdOut.BeginRead(_process.StandardOutput);
|
|
|
|
|
var threadErr = _stdErr.BeginRead(_process.StandardError);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
2015-10-21 03:11:27 -07:00
|
|
|
|
_process.WaitForExit();
|
2015-12-07 14:18:09 -08:00
|
|
|
|
threadOut.Join();
|
|
|
|
|
threadErr.Join();
|
2015-10-21 03:11:27 -07:00
|
|
|
|
|
|
|
|
|
var exitCode = _process.ExitCode;
|
2015-10-06 10:46:43 -07:00
|
|
|
|
|
2015-10-18 19:02:09 -07:00
|
|
|
|
#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
|
|
|
|
}
|
2015-10-18 19:02:09 -07:00
|
|
|
|
#endif
|
|
|
|
|
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return new CommandResult(
|
2016-01-24 01:04:39 -08:00
|
|
|
|
this._process.StartInfo,
|
2015-10-06 10:46:43 -07:00
|
|
|
|
exitCode,
|
2016-02-04 18:18:08 -08:00
|
|
|
|
_stdOut.CapturedOutput,
|
|
|
|
|
_stdErr.CapturedOutput);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 16:35:43 -08:00
|
|
|
|
public ICommand WorkingDirectory(string projectDirectory)
|
2015-11-17 18:02:08 -08:00
|
|
|
|
{
|
|
|
|
|
_process.StartInfo.WorkingDirectory = projectDirectory;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 16:35:43 -08:00
|
|
|
|
public ICommand EnvironmentVariable(string name, string value)
|
2015-10-30 15:40:13 -07:00
|
|
|
|
{
|
2016-02-10 16:13:30 -08:00
|
|
|
|
#if NET451
|
|
|
|
|
_process.StartInfo.EnvironmentVariables[name] = value;
|
|
|
|
|
#else
|
2015-11-01 06:24:31 -08:00
|
|
|
|
_process.StartInfo.Environment[name] = value;
|
2016-02-10 16:13:30 -08:00
|
|
|
|
#endif
|
2015-10-30 15:40:13 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 16:35:43 -08:00
|
|
|
|
public ICommand CaptureStdOut()
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdOut.Capture();
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 16:35:43 -08:00
|
|
|
|
public ICommand CaptureStdErr()
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-12-07 14:18:09 -08:00
|
|
|
|
_stdErr.Capture();
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 16:35:43 -08:00
|
|
|
|
public ICommand ForwardStdOut(TextWriter to = null, bool onlyIfVerbose = false)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-11-01 16:21:10 -08:00
|
|
|
|
if (!onlyIfVerbose || CommandContext.IsVerbose())
|
|
|
|
|
{
|
|
|
|
|
if (to == null)
|
|
|
|
|
{
|
2016-02-04 18:18:08 -08:00
|
|
|
|
_stdOut.ForwardTo(writeLine: Reporter.Output.WriteLine);
|
2015-11-01 16:21:10 -08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-02-04 18:18:08 -08:00
|
|
|
|
_stdOut.ForwardTo(writeLine: to.WriteLine);
|
2015-11-01 16:21:10 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 16:35:43 -08:00
|
|
|
|
public ICommand ForwardStdErr(TextWriter to = null, bool onlyIfVerbose = false)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2015-11-01 16:21:10 -08:00
|
|
|
|
if (!onlyIfVerbose || CommandContext.IsVerbose())
|
|
|
|
|
{
|
|
|
|
|
if (to == null)
|
|
|
|
|
{
|
2016-02-04 18:18:08 -08:00
|
|
|
|
_stdErr.ForwardTo(writeLine: Reporter.Error.WriteLine);
|
2015-11-01 16:21:10 -08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-02-04 18:18:08 -08:00
|
|
|
|
_stdErr.ForwardTo(writeLine: to.WriteLine);
|
2015-11-01 16:21:10 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 16:35:43 -08:00
|
|
|
|
public ICommand OnOutputLine(Action<string> handler)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2016-02-04 18:18:08 -08:00
|
|
|
|
_stdOut.ForwardTo(writeLine: handler);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-09 16:35:43 -08:00
|
|
|
|
public ICommand OnErrorLine(Action<string> handler)
|
2015-10-06 10:46:43 -07:00
|
|
|
|
{
|
|
|
|
|
ThrowIfRunning();
|
2016-02-04 18:18:08 -08:00
|
|
|
|
_stdErr.ForwardTo(writeLine: handler);
|
2015-10-06 10:46:43 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-10 13:06:33 -08:00
|
|
|
|
public CommandResolutionStrategy ResolutionStrategy { get; }
|
|
|
|
|
|
|
|
|
|
public string CommandName => _process.StartInfo.FileName;
|
2015-12-14 15:44:58 -08:00
|
|
|
|
|
2015-10-21 03:11:27 -07:00
|
|
|
|
private string FormatProcessInfo(ProcessStartInfo info)
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(info.Arguments))
|
|
|
|
|
{
|
|
|
|
|
return info.FileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return info.FileName + " " + info.Arguments;
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-06 10:46:43 -07:00
|
|
|
|
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
|
|
|
|
}
|
2015-10-06 10:46:43 -07:00
|
|
|
|
}
|