dotnet-installer/src/Microsoft.DotNet.Cli.Utils/Command.cs
Austin Wise 468a0f38a5 Fix color printing and verbosity.
Only apply the ANSI passthrough for child commands. Otherwise the
escape codes are printed to the console on Windows

Make Report.Write process escape codes so that forwarded output will have
its escape codes processed.

Also make verbose be inherited by all child processes.
2016-02-12 15:52:11 -08:00

237 lines
7.3 KiB
C#

// 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 NuGet.Frameworks;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Cli.Utils
{
public class Command : ICommand
{
private readonly Process _process;
private readonly StreamForwarder _stdOut;
private readonly StreamForwarder _stdErr;
private bool _running = false;
private Command(CommandSpec commandSpec)
{
var psi = new ProcessStartInfo
{
FileName = commandSpec.Path,
Arguments = commandSpec.Args,
RedirectStandardError = true,
RedirectStandardOutput = true
};
_stdOut = new StreamForwarder();
_stdErr = new StreamForwarder();
_process = new Process
{
StartInfo = psi
};
ResolutionStrategy = commandSpec.ResolutionStrategy;
}
public static Command CreateDotNet(string commandName, IEnumerable<string> args, NuGetFramework framework = null)
{
return Create("dotnet", new[] { commandName }.Concat(args), framework);
}
/// <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)
{
var commandSpec = CommandResolver.TryResolveCommandSpec(commandName, args, framework);
if (commandSpec == null)
{
throw new CommandUnknownException(commandName);
}
var command = new Command(commandSpec);
return command;
}
public static Command CreateForScript(string commandName, IEnumerable<string> args, Project project, string[] inferredExtensionList)
{
var commandSpec = CommandResolver.TryResolveScriptCommandSpec(commandName, args, project, inferredExtensionList);
if (commandSpec == null)
{
throw new CommandUnknownException(commandName);
}
var command = new Command(commandSpec);
return command;
}
public CommandResult Execute()
{
Reporter.Verbose.WriteLine($"Running {_process.StartInfo.FileName} {_process.StartInfo.Arguments}");
ThrowIfRunning();
_running = true;
_process.EnableRaisingEvents = true;
#if DEBUG
var sw = Stopwatch.StartNew();
Reporter.Verbose.WriteLine($"> {FormatProcessInfo(_process.StartInfo)}".White());
#endif
_process.Start();
Reporter.Verbose.WriteLine($"Process ID: {_process.Id}");
var threadOut = _stdOut.BeginRead(_process.StandardOutput);
var threadErr = _stdErr.BeginRead(_process.StandardError);
_process.WaitForExit();
threadOut.Join();
threadErr.Join();
var exitCode = _process.ExitCode;
#if DEBUG
var message = $"< {FormatProcessInfo(_process.StartInfo)} exited with {exitCode} in {sw.ElapsedMilliseconds} ms.";
if (exitCode == 0)
{
Reporter.Verbose.WriteLine(message.Green());
}
else
{
Reporter.Verbose.WriteLine(message.Red().Bold());
}
#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)
{
#if NET451
_process.StartInfo.EnvironmentVariables[name] = value;
#else
_process.StartInfo.Environment[name] = value;
#endif
return this;
}
public ICommand CaptureStdOut()
{
ThrowIfRunning();
_stdOut.Capture();
return this;
}
public ICommand CaptureStdErr()
{
ThrowIfRunning();
_stdErr.Capture();
return this;
}
public ICommand ForwardStdOut(TextWriter to = null, bool onlyIfVerbose = false, bool ansiPassThrough = true)
{
ThrowIfRunning();
if (!onlyIfVerbose || CommandContext.IsVerbose())
{
if (to == null)
{
_stdOut.ForwardTo(writeLine: Reporter.Output.WriteLine);
_process.StartInfo.Environment[CommandContext.Variables.AnsiPassThru] = ansiPassThrough.ToString();
}
else
{
_stdOut.ForwardTo(writeLine: to.WriteLine);
}
}
return this;
}
public ICommand ForwardStdErr(TextWriter to = null, bool onlyIfVerbose = false, bool ansiPassThrough = true)
{
ThrowIfRunning();
if (!onlyIfVerbose || CommandContext.IsVerbose())
{
if (to == null)
{
_stdErr.ForwardTo(writeLine: Reporter.Error.WriteLine);
_process.StartInfo.Environment[CommandContext.Variables.AnsiPassThru] = ansiPassThrough.ToString();
}
else
{
_stdErr.ForwardTo(writeLine: to.WriteLine);
}
}
return this;
}
public ICommand OnOutputLine(Action<string> handler)
{
ThrowIfRunning();
_stdOut.ForwardTo(writeLine: handler);
return this;
}
public ICommand OnErrorLine(Action<string> handler)
{
ThrowIfRunning();
_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 ThrowIfRunning([CallerMemberName] string memberName = null)
{
if (_running)
{
throw new InvalidOperationException($"Unable to invoke {memberName} after the command has been run");
}
}
}
}