зеркало из https://github.com/github/codeql.git
197 строки
6.3 KiB
C#
197 строки
6.3 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace Semmle.Util
|
|
{
|
|
/// <summary>
|
|
/// Utility to construct a build command.
|
|
/// </summary>
|
|
public class CommandBuilder
|
|
{
|
|
private enum EscapeMode { Process, Cmd };
|
|
|
|
private readonly StringBuilder arguments;
|
|
private bool firstCommand;
|
|
private string? executable;
|
|
private readonly EscapeMode escapingMode;
|
|
private readonly string? workingDirectory;
|
|
private readonly IDictionary<string, string>? environment;
|
|
private readonly bool silent;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="T:Semmle.Autobuild.CommandBuilder"/> class.
|
|
/// </summary>
|
|
/// <param name="workingDirectory">The working directory (<code>null</code> for current directory).</param>
|
|
/// <param name="environment">Additional environment variables.</param>
|
|
/// <param name="silent">Whether this command should be run silently.</param>
|
|
public CommandBuilder(IBuildActions actions, string? workingDirectory = null, IDictionary<string, string>? environment = null, bool silent = false)
|
|
{
|
|
arguments = new StringBuilder();
|
|
if (actions.IsWindows())
|
|
{
|
|
executable = "cmd.exe";
|
|
arguments.Append("/C");
|
|
escapingMode = EscapeMode.Cmd;
|
|
}
|
|
else
|
|
{
|
|
escapingMode = EscapeMode.Process;
|
|
}
|
|
|
|
firstCommand = true;
|
|
this.workingDirectory = workingDirectory;
|
|
this.environment = environment;
|
|
this.silent = silent;
|
|
}
|
|
|
|
public CommandBuilder CallBatFile(string batFile, string? argumentsOpt = null)
|
|
{
|
|
NextCommand();
|
|
arguments.Append(" CALL");
|
|
QuoteArgument(batFile);
|
|
Argument(argumentsOpt);
|
|
return this;
|
|
}
|
|
|
|
private static readonly char[] specialChars = { ' ', '\t', '\n', '\v', '\"' };
|
|
private static readonly char[] cmdMetacharacter = { '(', ')', '%', '!', '^', '\"', '<', '>', '&', '|', ' ' };
|
|
|
|
/// <summary>
|
|
/// Appends the given argument to the command line.
|
|
/// </summary>
|
|
/// <param name="argument">The argument to append.</param>
|
|
/// <param name="force">Whether to always quote the argument.</param>
|
|
///
|
|
/// <remarks>
|
|
/// This implementation is copied from
|
|
/// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
|
/// </remarks>
|
|
private void ArgvQuote(string argument, bool force)
|
|
{
|
|
var cmd = escapingMode == EscapeMode.Cmd;
|
|
if (!force &&
|
|
!string.IsNullOrEmpty(argument) &&
|
|
argument.IndexOfAny(specialChars) == -1)
|
|
{
|
|
arguments.Append(argument);
|
|
}
|
|
else
|
|
{
|
|
if (cmd)
|
|
arguments.Append('^');
|
|
|
|
arguments.Append('\"');
|
|
|
|
for (var it = 0; ; ++it)
|
|
{
|
|
var numBackslashes = 0;
|
|
while (it != argument.Length && argument[it] == '\\')
|
|
{
|
|
++it;
|
|
++numBackslashes;
|
|
}
|
|
|
|
if (it == argument.Length)
|
|
{
|
|
arguments.Append('\\', numBackslashes * 2);
|
|
break;
|
|
}
|
|
|
|
if (argument[it] == '\"')
|
|
{
|
|
arguments.Append('\\', numBackslashes * 2 + 1);
|
|
if (cmd)
|
|
arguments.Append('^');
|
|
arguments.Append(arguments[it]);
|
|
}
|
|
else
|
|
{
|
|
arguments.Append('\\', numBackslashes);
|
|
if (cmd && cmdMetacharacter.Any(c => c == argument[it]))
|
|
arguments.Append('^');
|
|
|
|
arguments.Append(argument[it]);
|
|
}
|
|
}
|
|
|
|
if (cmd)
|
|
arguments.Append('^');
|
|
|
|
arguments.Append('\"');
|
|
}
|
|
}
|
|
|
|
public CommandBuilder QuoteArgument(string argumentsOpt)
|
|
{
|
|
if (argumentsOpt is not null)
|
|
{
|
|
NextArgument();
|
|
ArgvQuote(argumentsOpt, false);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void NextArgument()
|
|
{
|
|
if (arguments.Length > 0)
|
|
arguments.Append(' ');
|
|
}
|
|
|
|
public CommandBuilder Argument(string? argumentsOpt)
|
|
{
|
|
if (argumentsOpt is not null)
|
|
{
|
|
NextArgument();
|
|
arguments.Append(argumentsOpt);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void NextCommand()
|
|
{
|
|
if (firstCommand)
|
|
firstCommand = false;
|
|
else
|
|
arguments.Append(" &&");
|
|
}
|
|
|
|
public CommandBuilder RunCommand(string exe, string? argumentsOpt = null, bool quoteExe = true)
|
|
{
|
|
var (exe0, arg0) =
|
|
escapingMode == EscapeMode.Process && exe.EndsWith(".exe", System.StringComparison.Ordinal)
|
|
? ("mono", exe) // Linux
|
|
: (exe, null);
|
|
|
|
NextCommand();
|
|
if (executable is null)
|
|
{
|
|
executable = exe0;
|
|
}
|
|
else
|
|
{
|
|
if (quoteExe)
|
|
QuoteArgument(exe0);
|
|
else
|
|
Argument(exe0);
|
|
}
|
|
Argument(arg0);
|
|
Argument(argumentsOpt);
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a build script that contains just this command.
|
|
/// </summary>
|
|
public BuildScript Script
|
|
{
|
|
get
|
|
{
|
|
if (executable is null)
|
|
throw new System.InvalidOperationException("executable is null");
|
|
return BuildScript.Create(executable, arguments.ToString(), silent, workingDirectory, environment);
|
|
}
|
|
}
|
|
}
|
|
}
|