using System; using System.Collections.Generic; using System.IO; namespace Semmle.Autobuild { /// /// 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); class BuildCommand : BuildScript { readonly string exe, arguments, workingDirectory; readonly IDictionary environment; /// /// Create a simple build command. /// /// The executable to run. /// The arguments to the executable, or null. /// The working directory (null for current directory). /// Additional environment variables. public BuildCommand(string exe, string argumentsOpt, string workingDirectory = null, IDictionary environment = null) { this.exe = exe; this.arguments = argumentsOpt ?? ""; 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()); 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); return ret; } public override int Run(IBuildActions actions, Action startCallback, Action exitCallBack, out IList stdout) { startCallback(this.ToString()); 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 = new string[0]; } exitCallBack(ret, retMessage); return ret; } } class ReturnBuildCommand : BuildScript { 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 = new string[0]; return func(actions); } } class BindBuildScript : BuildScript { readonly BuildScript s1; readonly Func, int, BuildScript> s2a; 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 != null) { ret1 = s1.Run(actions, startCallback, exitCallBack, out var stdout1); return s2a(stdout1, ret1).Run(actions, startCallback, exitCallBack); } ret1 = s1.Run(actions, startCallback, exitCallBack); return s2b(ret1).Run(actions, startCallback, exitCallBack); } 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 != 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. /// The working directory (null for current directory). /// Additional environment variables. public static BuildScript Create(string exe, string argumentsOpt, string workingDirectory, IDictionary environment) => new BuildCommand(exe, argumentsOpt, 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 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); const int SuccessCode = 0; /// /// The empty build script that always returns exit code 0. /// public static readonly BuildScript Success = Create(actions => SuccessCode); const int FailureCode = 1; /// /// The empty build script that always returns exit code 1. /// public static readonly BuildScript Failure = Create(actions => FailureCode); 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 { 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 { return FailureCode; } return SuccessCode; }); } }