using System;
using System.Collections.Generic;
using System.IO;
namespace Semmle.Autobuild.Shared
{
///
/// A build script.
///
public abstract class BuildScript
{
///
/// Run this build script.
///
///
/// The interface used to implement the build actions.
///
///
/// A call back that is called every time a new process is started. The
/// argument to the call back is a textual representation of the process.
///
///
/// A call back that is called every time a new process exits. The first
/// argument to the call back is the exit code, and the second argument is
/// an exit message.
///
/// The exit code from this build script.
public abstract int Run(IBuildActions actions, Action startCallback, Action exitCallBack);
///
/// Run this build command.
///
///
/// The interface used to implement the build actions.
///
///
/// A call back that is called every time a new process is started. The
/// argument to the call back is a textual representation of the process.
///
///
/// A call back that is called every time a new process exits. The first
/// argument to the call back is the exit code, and the second argument is
/// an exit message.
///
/// Contents of standard out.
/// The exit code from this build script.
public abstract int Run(IBuildActions actions, Action startCallback, Action exitCallBack, out IList stdout);
private class BuildCommand : BuildScript
{
private readonly string exe, arguments;
private readonly string? workingDirectory;
private readonly IDictionary? environment;
private readonly bool silent;
///
/// Create a simple build command.
///
/// The executable to run.
/// The arguments to the executable, or null.
/// Whether this command should run silently.
/// The working directory (null for current directory).
/// Additional environment variables.
public BuildCommand(string exe, string? argumentsOpt, bool silent, string? workingDirectory = null, IDictionary? environment = null)
{
this.exe = exe;
this.arguments = argumentsOpt ?? "";
this.silent = silent;
this.workingDirectory = workingDirectory;
this.environment = environment;
}
public override string ToString() => exe + " " + arguments;
public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack)
{
startCallback(this.ToString(), silent);
var ret = 1;
var retMessage = "";
try
{
ret = actions.RunProcess(exe, arguments, workingDirectory, environment);
}
catch (Exception ex)
when (ex is System.ComponentModel.Win32Exception || ex is FileNotFoundException)
{
retMessage = ex.Message;
}
exitCallBack(ret, retMessage, silent);
return ret;
}
public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack, out IList stdout)
{
startCallback(this.ToString(), silent);
var ret = 1;
var retMessage = "";
try
{
ret = actions.RunProcess(exe, arguments, workingDirectory, environment, out stdout);
}
catch (Exception ex)
when (ex is System.ComponentModel.Win32Exception || ex is FileNotFoundException)
{
retMessage = ex.Message;
stdout = Array.Empty();
}
exitCallBack(ret, retMessage, silent);
return ret;
}
}
private class ReturnBuildCommand : BuildScript
{
private readonly Func func;
public ReturnBuildCommand(Func func)
{
this.func = func;
}
public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack) => func(actions);
public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack, out IList stdout)
{
stdout = Array.Empty();
return func(actions);
}
}
private class BindBuildScript : BuildScript
{
private readonly BuildScript s1;
private readonly Func, int, BuildScript>? s2a;
private readonly Func? s2b;
public BindBuildScript(BuildScript s1, Func, int, BuildScript> s2)
{
this.s1 = s1;
this.s2a = s2;
}
public BindBuildScript(BuildScript s1, Func s2)
{
this.s1 = s1;
this.s2b = s2;
}
public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack)
{
int ret1;
if (s2a is not null)
{
ret1 = s1.Run(actions, startCallback, exitCallBack, out var stdout1);
return s2a(stdout1, ret1).Run(actions, startCallback, exitCallBack);
}
if (s2b is not null)
{
ret1 = s1.Run(actions, startCallback, exitCallBack);
return s2b(ret1).Run(actions, startCallback, exitCallBack);
}
throw new InvalidOperationException("Unexpected error");
}
public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack, out IList stdout)
{
var ret1 = s1.Run(actions, startCallback, exitCallBack, out var stdout1);
var ret2 = (s2a is not null ? s2a(stdout1, ret1) : s2b!(ret1)).Run(actions, startCallback, exitCallBack, out var stdout2);
var @out = new List();
@out.AddRange(stdout1);
@out.AddRange(stdout2);
stdout = @out;
return ret2;
}
}
///
/// Creates a simple build script that runs the specified exe.
///
/// The arguments to the executable, or null.
/// Whether the executable should run silently.
/// The working directory (null for current directory).
/// Additional environment variables.
public static BuildScript Create(string exe, string? argumentsOpt, bool silent, string? workingDirectory, IDictionary? environment) =>
new BuildCommand(exe, argumentsOpt, silent, workingDirectory, environment);
///
/// Creates a simple build script that runs the specified function.
///
public static BuildScript Create(Func func) =>
new ReturnBuildCommand(func);
///
/// Creates a build script that downloads the specified file.
///
public static BuildScript DownloadFile(string address, string fileName, Action exceptionCallback) =>
Create(actions =>
{
if (actions.GetDirectoryName(fileName) is string dir && !string.IsNullOrWhiteSpace(dir))
actions.CreateDirectory(dir);
try
{
actions.DownloadFile(address, fileName);
return 0;
}
catch (Exception e)
{
exceptionCallback(e);
return 1;
}
});
///
/// Creates a build script that runs , followed by running the script
/// produced by on the exit code from .
///
public static BuildScript Bind(BuildScript s1, Func s2) =>
new BindBuildScript(s1, s2);
///
/// Creates a build script that runs , followed by running the script
/// produced by on the exit code and standard output from
/// .
///
public static BuildScript Bind(BuildScript s1, Func, int, BuildScript> s2) =>
new BindBuildScript(s1, s2);
private const int successCode = 0;
///
/// The empty build script that always returns exit code 0.
///
public static BuildScript Success { get; } = Create(actions => successCode);
private const int failureCode = 1;
///
/// The empty build script that always returns exit code 1.
///
public static BuildScript Failure { get; } = Create(actions => failureCode);
private static bool Succeeded(int i) => i == successCode;
public static BuildScript operator &(BuildScript s1, BuildScript s2) =>
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? s2 : Create(actions => ret1));
public static BuildScript operator &(BuildScript s1, Func s2) =>
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? s2() : Create(actions => ret1));
public static BuildScript operator |(BuildScript s1, BuildScript s2) =>
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? Success : s2);
public static BuildScript operator |(BuildScript s1, Func s2) =>
new BindBuildScript(s1, ret1 => Succeeded(ret1) ? Success : s2());
///
/// Creates a build script that attempts to run the build script ,
/// always returning a successful exit code.
///
public static BuildScript Try(BuildScript s) => s | Success;
///
/// Creates a build script that deletes the given directory.
///
public static BuildScript DeleteDirectory(string dir) =>
Create(actions =>
{
if (string.IsNullOrEmpty(dir) || !actions.DirectoryExists(dir))
return failureCode;
try
{
actions.DirectoryDelete(dir, true);
}
catch // lgtm[cs/catch-of-all-exceptions]
{
return failureCode;
}
return successCode;
});
///
/// Creates a build script that deletes the given file.
///
public static BuildScript DeleteFile(string file) =>
Create(actions =>
{
if (string.IsNullOrEmpty(file) || !actions.FileExists(file))
return failureCode;
try
{
actions.FileDelete(file);
}
catch // lgtm[cs/catch-of-all-exceptions]
{
return failureCode;
}
return successCode;
});
}
}