diff options
Diffstat (limited to 'js/process.ts')
-rw-r--r-- | js/process.ts | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/js/process.ts b/js/process.ts new file mode 100644 index 000000000..0a1393ed0 --- /dev/null +++ b/js/process.ts @@ -0,0 +1,136 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import * as dispatch from "./dispatch"; +import * as flatbuffers from "./flatbuffers"; +import * as msg from "gen/msg_generated"; +import { assert, unreachable } from "./util"; +import { close, File } from "./files"; +import { ReadCloser, WriteCloser } from "./io"; + +/** How to handle subsubprocess stdio. + * + * "inherit" The default if unspecified. The child inherits from the + * corresponding parent descriptor. + * + * "piped" A new pipe should be arranged to connect the parent and child + * subprocesses. + * + * "null" This stream will be ignored. This is the equivalent of attaching the + * stream to /dev/null. + */ +export type ProcessStdio = "inherit" | "piped" | "null"; + +// TODO Maybe extend VSCode's 'CommandOptions'? +// tslint:disable-next-line:max-line-length +// See https://code.visualstudio.com/docs/editor/tasks-appendix#_schema-for-tasksjson +export interface RunOptions { + args: string[]; + cwd?: string; + stdout?: ProcessStdio; + stderr?: ProcessStdio; + stdin?: ProcessStdio; +} + +export class Process { + readonly rid: number; + readonly pid: number; + readonly stdin?: WriteCloser; + readonly stdout?: ReadCloser; + readonly stderr?: ReadCloser; + + // @internal + constructor(res: msg.RunRes) { + this.rid = res.rid(); + this.pid = res.pid(); + + if (res.stdinRid() > 0) { + this.stdin = new File(res.stdinRid()); + } + + if (res.stdoutRid() > 0) { + this.stdout = new File(res.stdoutRid()); + } + + if (res.stderrRid() > 0) { + this.stderr = new File(res.stderrRid()); + } + } + + async status(): Promise<ProcessStatus> { + return await runStatus(this.rid); + } + + close(): void { + close(this.rid); + } +} + +export interface ProcessStatus { + success: boolean; + code?: number; + signal?: number; // TODO: Make this a string, e.g. 'SIGTERM'. +} + +function stdioMap(s: ProcessStdio): msg.ProcessStdio { + switch (s) { + case "inherit": + return msg.ProcessStdio.Inherit; + case "piped": + return msg.ProcessStdio.Piped; + case "null": + return msg.ProcessStdio.Null; + default: + return unreachable(); + } +} + +export function run(opt: RunOptions): Process { + const builder = flatbuffers.createBuilder(); + const argsOffset = msg.Run.createArgsVector( + builder, + opt.args.map(a => builder.createString(a)) + ); + const cwdOffset = opt.cwd == null ? -1 : builder.createString(opt.cwd); + msg.Run.startRun(builder); + msg.Run.addArgs(builder, argsOffset); + if (opt.cwd != null) { + msg.Run.addCwd(builder, cwdOffset); + } + if (opt.stdin) { + msg.Run.addStdin(builder, stdioMap(opt.stdin!)); + } + if (opt.stdout) { + msg.Run.addStdout(builder, stdioMap(opt.stdout!)); + } + if (opt.stderr) { + msg.Run.addStderr(builder, stdioMap(opt.stderr!)); + } + const inner = msg.Run.endRun(builder); + const baseRes = dispatch.sendSync(builder, msg.Any.Run, inner); + assert(baseRes != null); + assert(msg.Any.RunRes === baseRes!.innerType()); + const res = new msg.RunRes(); + assert(baseRes!.inner(res) != null); + + return new Process(res); +} + +async function runStatus(rid: number): Promise<ProcessStatus> { + const builder = flatbuffers.createBuilder(); + msg.RunStatus.startRunStatus(builder); + msg.RunStatus.addRid(builder, rid); + const inner = msg.RunStatus.endRunStatus(builder); + + const baseRes = await dispatch.sendAsync(builder, msg.Any.RunStatus, inner); + assert(baseRes != null); + assert(msg.Any.RunStatusRes === baseRes!.innerType()); + const res = new msg.RunStatusRes(); + assert(baseRes!.inner(res) != null); + + if (res.gotSignal()) { + const signal = res.exitSignal(); + return { signal, success: false }; + } else { + const code = res.exitCode(); + return { code, success: code === 0 }; + } +} |