diff --git a/toolchainlib/src/utils/exec.luau b/toolchainlib/src/utils/exec.luau deleted file mode 100644 index 308ae2d..0000000 --- a/toolchainlib/src/utils/exec.luau +++ /dev/null @@ -1,186 +0,0 @@ ---> Builder pattern class to spawn, manage and kill child processes - -local process = require("@lune/process") -local task = require("@lune/task") - -local Option = require("../../lune_packages/option") -type Option = Option.Option - -local CommandBuilder = {} -type CommandBuilderFields = { - program: string, - args: { string }, - retries: Option, - ignoreErrors: Option, - stdioStrategy: Option, -} -export type CommandBuilder = typeof(setmetatable({} :: CommandBuilderFields, { __index = CommandBuilder })) -export type StdioStrategy = "pipe" | "forward" | "none" -export type IoStrategyMapping = { - stdout: Option, - stderr: Option, -} -export type ChildProcess = { - _thread: thread, - _pid: string, - _status: ChildStatus, - start: (self: ChildProcess) -> (), - waitForChild: (self: ChildProcess) -> ChildStatus, - kill: (self: ChildProcess) -> (), -} -export type ChildStatus = { ok: boolean, code: number, io: { - stdout: string, - stderr: string, -} } - -local DEFAULT_STDIO_STRATEGY: IoStrategyMapping = { - stdout = Option.Some("pipe" :: StdioStrategy), - stderr = Option.Some("pipe" :: StdioStrategy), -} -local DEFAULT_RETRIES = 0 -local DEFAULT_IGNORE_ERRORS = false - -function CommandBuilder.new(program: string) - return setmetatable( - { - program = program, - args = {}, - retries = Option.None, - ignoreErrors = Option.None, - stdioStrategy = Option.None :: Option, - } :: CommandBuilderFields, - { - __index = CommandBuilder, - } - ) -end - -function CommandBuilder.withArg(self: CommandBuilder, arg: string): CommandBuilder - table.insert(self.args, arg) - return self -end - -function CommandBuilder.withArgs(self: CommandBuilder, args: { string }): CommandBuilder - for _, arg in args do - self:withArg(arg) - end - - return self -end - -function CommandBuilder.withMaxRetries(self: CommandBuilder, retries: number): CommandBuilder - self.retries = Option.Some(retries) :: Option - return self -end - -function CommandBuilder.withIgnoreErrors(self: CommandBuilder, yes: boolean): CommandBuilder - self.ignoreErrors = Option.Some(yes) :: Option - return self -end - -function CommandBuilder.withStdioStrategy( - self: CommandBuilder, - strategy: StdioStrategy | IoStrategyMapping -): CommandBuilder - self.stdioStrategy = Option.Some(if typeof(strategy) == "string" - then { - stdout = Option.Some(strategy), - stderr = Option.Some(strategy), - } - else strategy) :: Option - return self -end - -local function intoSpawnOptionsStdioKind(strategy: StdioStrategy): process.SpawnOptionsStdioKind - if strategy == "pipe" then - return "default" - end - - if strategy == "forward" then - return "forward" - end - - if strategy == "none" then - return "none" - end - - error(`Non-strategy provided: {strategy}`) -end - -function CommandBuilder.intoChildProcess(self: CommandBuilder): ChildProcess - local child = { - _thread = coroutine.create(function(this: ChildProcess) - local retries = self.retries:unwrapOr(DEFAULT_RETRIES) - local ignoreErrors = self.ignoreErrors:unwrapOr(DEFAULT_IGNORE_ERRORS) - local argsList = table.concat(self.args, " ") - - for _ = 0, retries do - local spawned = process.spawn( - if process.os == "windows" - then `(Start-Process {self.program} -Passthru -Wait -NoNewWindow -ArgumentList \"{argsList}\").Id` - else `{self.program} {argsList} & echo $!`, - {}, - { - stdio = self.stdioStrategy - :orOpt(Option.Some(DEFAULT_STDIO_STRATEGY)) - :map(function(mappings: IoStrategyMapping) - local translatedMappings: process.SpawnOptionsStdio = {} - for field, value in mappings do - translatedMappings[field] = - intoSpawnOptionsStdioKind((value :: Option):unwrap()) - end - - return translatedMappings - end) - :unwrap(), - shell = true, - } - ) - - if spawned.ok then - local lines = spawned.stdout:split("\n") - - -- TODO: Abstract upvalues here into a channels primitive - this._pid = assert(table.remove(lines, 1), "Failed to get PID") - this._status = { - code = spawned.code, - ok = spawned.code == 0 and not ignoreErrors, - io = { - stdout = table.concat(lines, "\n"), - stderr = spawned.stderr, - }, - } - break - end - end - end), - - start = function(self: ChildProcess) - coroutine.resume(self._thread, self) - end, - - waitForChild = function(self: ChildProcess): ChildStatus - while coroutine.status(self._thread) ~= "dead" or self._status == nil do - task.wait(0.1) - end - - return self._status - end, - - kill = function(self: ChildProcess) - coroutine.close(self._thread) - local killResult = process.spawn( - if process.os == "windows" then `Stop-Process -Id {self._pid} -Force` else `kill {self._pid}`, - { - shell = true, - } - ) - - assert(killResult.ok, `Failed to kill process with PID {self._pid}`) - end, - } :: ChildProcess - - return child -end - -return CommandBuilder