summaryrefslogtreecommitdiff
path: root/cli/js/process.ts
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2019-10-04 20:28:51 -0400
committerGitHub <noreply@github.com>2019-10-04 20:28:51 -0400
commitb81e5db17aa8b3088d6034ddf86b79c69410f012 (patch)
tree579e4c23d60d1b0d038156bc28a04f74ea87b2f0 /cli/js/process.ts
parent9049213867d30f7df090a83b6baf3e0717a4d2d2 (diff)
Merge deno_cli_snapshots into deno_cli (#3064)
Diffstat (limited to 'cli/js/process.ts')
-rw-r--r--cli/js/process.ts307
1 files changed, 307 insertions, 0 deletions
diff --git a/cli/js/process.ts b/cli/js/process.ts
new file mode 100644
index 000000000..0c77929f9
--- /dev/null
+++ b/cli/js/process.ts
@@ -0,0 +1,307 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import { sendSync, sendAsync } from "./dispatch_json.ts";
+import * as dispatch from "./dispatch.ts";
+import { File, close } from "./files.ts";
+import { ReadCloser, WriteCloser } from "./io.ts";
+import { readAll } from "./buffer.ts";
+import { assert, unreachable } from "./util.ts";
+import { build } from "./build.ts";
+
+/** How to handle subprocess 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'?
+// See https://code.visualstudio.com/docs/editor/tasks-appendix#_schema-for-tasksjson
+export interface RunOptions {
+ args: string[];
+ cwd?: string;
+ env?: { [key: string]: string };
+ stdout?: ProcessStdio | number;
+ stderr?: ProcessStdio | number;
+ stdin?: ProcessStdio | number;
+}
+
+interface RunStatusResponse {
+ gotSignal: boolean;
+ exitCode: number;
+ exitSignal: number;
+}
+
+async function runStatus(rid: number): Promise<ProcessStatus> {
+ const res = (await sendAsync(dispatch.OP_RUN_STATUS, {
+ rid
+ })) as RunStatusResponse;
+
+ if (res.gotSignal) {
+ const signal = res.exitSignal;
+ return { signal, success: false };
+ } else {
+ const code = res.exitCode;
+ return { code, success: code === 0 };
+ }
+}
+
+/** Send a signal to process under given PID. Unix only at this moment.
+ * If pid is negative, the signal will be sent to the process group identified
+ * by -pid.
+ * Requires the `--allow-run` flag.
+ */
+export function kill(pid: number, signo: number): void {
+ sendSync(dispatch.OP_KILL, { pid, signo });
+}
+
+export class Process {
+ readonly rid: number;
+ readonly pid: number;
+ readonly stdin?: WriteCloser;
+ readonly stdout?: ReadCloser;
+ readonly stderr?: ReadCloser;
+
+ // @internal
+ constructor(res: RunResponse) {
+ this.rid = res.rid;
+ this.pid = res.pid;
+
+ if (res.stdinRid && res.stdinRid > 0) {
+ this.stdin = new File(res.stdinRid);
+ }
+
+ if (res.stdoutRid && res.stdoutRid > 0) {
+ this.stdout = new File(res.stdoutRid);
+ }
+
+ if (res.stderrRid && res.stderrRid > 0) {
+ this.stderr = new File(res.stderrRid);
+ }
+ }
+
+ async status(): Promise<ProcessStatus> {
+ return await runStatus(this.rid);
+ }
+
+ /** Buffer the stdout and return it as Uint8Array after EOF.
+ * You must set stdout to "piped" when creating the process.
+ * This calls close() on stdout after its done.
+ */
+ async output(): Promise<Uint8Array> {
+ if (!this.stdout) {
+ throw new Error("Process.output: stdout is undefined");
+ }
+ try {
+ return await readAll(this.stdout);
+ } finally {
+ this.stdout.close();
+ }
+ }
+
+ /** Buffer the stderr and return it as Uint8Array after EOF.
+ * You must set stderr to "piped" when creating the process.
+ * This calls close() on stderr after its done.
+ */
+ async stderrOutput(): Promise<Uint8Array> {
+ if (!this.stderr) {
+ throw new Error("Process.stderrOutput: stderr is undefined");
+ }
+ try {
+ return await readAll(this.stderr);
+ } finally {
+ this.stderr.close();
+ }
+ }
+
+ close(): void {
+ close(this.rid);
+ }
+
+ kill(signo: number): void {
+ kill(this.pid, signo);
+ }
+}
+
+export interface ProcessStatus {
+ success: boolean;
+ code?: number;
+ signal?: number; // TODO: Make this a string, e.g. 'SIGTERM'.
+}
+
+// TODO: this method is only used to validate proper option, probably can be renamed
+function stdioMap(s: string): string {
+ switch (s) {
+ case "inherit":
+ case "piped":
+ case "null":
+ return s;
+ default:
+ return unreachable();
+ }
+}
+
+function isRid(arg: unknown): arg is number {
+ return !isNaN(arg as number);
+}
+
+interface RunResponse {
+ rid: number;
+ pid: number;
+ stdinRid: number | null;
+ stdoutRid: number | null;
+ stderrRid: number | null;
+}
+/**
+ * Spawns new subprocess.
+ *
+ * Subprocess uses same working directory as parent process unless `opt.cwd`
+ * is specified.
+ *
+ * Environmental variables for subprocess can be specified using `opt.env`
+ * mapping.
+ *
+ * By default subprocess inherits stdio of parent process. To change that
+ * `opt.stdout`, `opt.stderr` and `opt.stdin` can be specified independently -
+ * they can be set to either `ProcessStdio` or `rid` of open file.
+ */
+export function run(opt: RunOptions): Process {
+ assert(opt.args.length > 0);
+ let env: Array<[string, string]> = [];
+ if (opt.env) {
+ env = Array.from(Object.entries(opt.env));
+ }
+
+ let stdin = stdioMap("inherit");
+ let stdout = stdioMap("inherit");
+ let stderr = stdioMap("inherit");
+ let stdinRid = 0;
+ let stdoutRid = 0;
+ let stderrRid = 0;
+
+ if (opt.stdin) {
+ if (isRid(opt.stdin)) {
+ stdinRid = opt.stdin;
+ } else {
+ stdin = stdioMap(opt.stdin);
+ }
+ }
+
+ if (opt.stdout) {
+ if (isRid(opt.stdout)) {
+ stdoutRid = opt.stdout;
+ } else {
+ stdout = stdioMap(opt.stdout);
+ }
+ }
+
+ if (opt.stderr) {
+ if (isRid(opt.stderr)) {
+ stderrRid = opt.stderr;
+ } else {
+ stderr = stdioMap(opt.stderr);
+ }
+ }
+
+ const req = {
+ args: opt.args.map(String),
+ cwd: opt.cwd,
+ env,
+ stdin,
+ stdout,
+ stderr,
+ stdinRid,
+ stdoutRid,
+ stderrRid
+ };
+
+ const res = sendSync(dispatch.OP_RUN, req) as RunResponse;
+ return new Process(res);
+}
+
+// From `kill -l`
+enum LinuxSignal {
+ SIGHUP = 1,
+ SIGINT = 2,
+ SIGQUIT = 3,
+ SIGILL = 4,
+ SIGTRAP = 5,
+ SIGABRT = 6,
+ SIGBUS = 7,
+ SIGFPE = 8,
+ SIGKILL = 9,
+ SIGUSR1 = 10,
+ SIGSEGV = 11,
+ SIGUSR2 = 12,
+ SIGPIPE = 13,
+ SIGALRM = 14,
+ SIGTERM = 15,
+ SIGSTKFLT = 16,
+ SIGCHLD = 17,
+ SIGCONT = 18,
+ SIGSTOP = 19,
+ SIGTSTP = 20,
+ SIGTTIN = 21,
+ SIGTTOU = 22,
+ SIGURG = 23,
+ SIGXCPU = 24,
+ SIGXFSZ = 25,
+ SIGVTALRM = 26,
+ SIGPROF = 27,
+ SIGWINCH = 28,
+ SIGIO = 29,
+ SIGPWR = 30,
+ SIGSYS = 31
+}
+
+// From `kill -l`
+enum MacOSSignal {
+ SIGHUP = 1,
+ SIGINT = 2,
+ SIGQUIT = 3,
+ SIGILL = 4,
+ SIGTRAP = 5,
+ SIGABRT = 6,
+ SIGEMT = 7,
+ SIGFPE = 8,
+ SIGKILL = 9,
+ SIGBUS = 10,
+ SIGSEGV = 11,
+ SIGSYS = 12,
+ SIGPIPE = 13,
+ SIGALRM = 14,
+ SIGTERM = 15,
+ SIGURG = 16,
+ SIGSTOP = 17,
+ SIGTSTP = 18,
+ SIGCONT = 19,
+ SIGCHLD = 20,
+ SIGTTIN = 21,
+ SIGTTOU = 22,
+ SIGIO = 23,
+ SIGXCPU = 24,
+ SIGXFSZ = 25,
+ SIGVTALRM = 26,
+ SIGPROF = 27,
+ SIGWINCH = 28,
+ SIGINFO = 29,
+ SIGUSR1 = 30,
+ SIGUSR2 = 31
+}
+
+/** Signals numbers. This is platform dependent.
+ */
+export const Signal = {};
+
+export function setSignals(): void {
+ if (build.os === "mac") {
+ Object.assign(Signal, MacOSSignal);
+ } else {
+ Object.assign(Signal, LinuxSignal);
+ }
+}