Skip to content

Commit fb8fbf2

Browse files
authored
feat(cli)!: Allow specifying WASI environment variables and CLI args (#1840)
1 parent 5efb54c commit fb8fbf2

File tree

7 files changed

+114
-17
lines changed

7 files changed

+114
-17
lines changed

cli/bin/exec.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,20 +122,37 @@ function getGrainrun() {
122122

123123
const grainrun = getGrainrun();
124124

125-
function execGrainrun(file, options, program, execOpts = { stdio: "inherit" }) {
125+
function execGrainrun(
126+
unprocessedArgs,
127+
file,
128+
options,
129+
program,
130+
execOpts = { stdio: "inherit" }
131+
) {
126132
const preopens = {};
127133
options.dir?.forEach((preopen) => {
128134
const [guestDir, hostDir = guestDir] = preopen.split("=");
129135
preopens[guestDir] = hostDir;
130136
});
131137

138+
const cliEnv = {};
139+
options.env?.forEach((env) => {
140+
const [name, ...rest] = env.split("=");
141+
const val = rest.join("=");
142+
cliEnv[name] = val;
143+
});
144+
132145
const env = {
146+
ENV_VARS: JSON.stringify(cliEnv),
133147
PREOPENS: JSON.stringify(preopens),
134148
NODE_OPTIONS: `--experimental-wasi-unstable-preview1 --no-warnings`,
135149
};
136150

137151
try {
138-
execSync(`${grainrun} ${file}`, { ...execOpts, env });
152+
execSync(`${grainrun} ${file} ${unprocessedArgs.join(" ")}`, {
153+
...execOpts,
154+
env,
155+
});
139156
} catch (e) {
140157
process.exit(e.status);
141158
}

cli/bin/grain.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class GrainCommand extends commander.Command {
107107
);
108108
cmd.forwardOption("--import-memory", "import the memory from `env.memory`");
109109
cmd.option("--dir <dir...>", "directory to preopen");
110+
cmd.option("--env <env...>", "WASI environment variables");
110111
cmd.forwardOption(
111112
"--compilation-mode <mode>",
112113
"compilation mode (advanced use only)"
@@ -164,6 +165,13 @@ class GrainCommand extends commander.Command {
164165
}
165166
}
166167

168+
let endOptsI = process.argv.findIndex((x) => x === "--");
169+
if (endOptsI === -1) {
170+
endOptsI = Infinity;
171+
}
172+
const argsToProcess = process.argv.slice(0, endOptsI);
173+
const unprocessedArgs = process.argv.slice(endOptsI + 1);
174+
167175
const program = new GrainCommand();
168176

169177
program
@@ -178,11 +186,8 @@ program
178186
.forwardOption("-o <filename>", "output filename")
179187
.action(function (file, options, program) {
180188
exec.grainc(file, options, program);
181-
if (options.o) {
182-
exec.grainrun(options.o, options, program);
183-
} else {
184-
exec.grainrun(file.replace(/\.gr$/, ".gr.wasm"), options, program);
185-
}
189+
const outFile = options.o ?? file.replace(/\.gr$/, ".gr.wasm");
190+
exec.grainrun(unprocessedArgs, outFile, options, program);
186191
});
187192

188193
program
@@ -195,7 +200,7 @@ program
195200
program
196201
.command("run <file>")
197202
.description("run a wasm file via grain's WASI runner")
198-
.action(exec.grainrun);
203+
.action((...args) => exec.grainrun(unprocessedArgs, ...args));
199204

200205
program
201206
.command("lsp")
@@ -218,4 +223,4 @@ program
218223
.forwardOption("-o <file|dir>", "output file or directory")
219224
.action(exec.grainformat);
220225

221-
program.parse(process.argv);
226+
program.parse(argsToProcess);

cli/bin/grainrun.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,11 @@ const { WASI } = require("wasi");
1717
const { argv, env } = require("process");
1818

1919
const preopens = JSON.parse(env.PREOPENS);
20-
21-
delete env.PREOPENS;
22-
delete env.NODE_OPTIONS;
20+
const envVars = JSON.parse(env.ENV_VARS);
2321

2422
const wasi = new WASI({
2523
args: argv.slice(2),
26-
env,
24+
env: envVars,
2725
preopens,
2826
});
2927

compiler/test/runner.re

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ let open_process = args => {
158158
(code, out, err);
159159
};
160160

161-
let run = (~num_pages=?, file) => {
161+
let run = (~num_pages=?, ~extra_args=[||], file) => {
162162
let mem_flags =
163163
switch (num_pages) {
164164
| Some(x) => [|
@@ -179,12 +179,22 @@ let run = (~num_pages=?, file) => {
179179
Filepath.to_string(test_data_dir),
180180
);
181181

182+
let extra_args =
183+
Array.map(
184+
arg =>
185+
switch (Sys.win32, arg) {
186+
| (true, "--") => "`--"
187+
| _ => arg
188+
},
189+
extra_args,
190+
);
182191
let cmd =
183192
Array.concat([
184193
[|"grain", "run"|],
185194
mem_flags,
186195
[|"-S", stdlib, "-I", Filepath.to_string(test_libs_dir), preopen|],
187196
[|file|],
197+
extra_args,
188198
]);
189199

190200
let (code, out, err) = open_process(cmd);
@@ -289,11 +299,12 @@ let makeNoWarningRunner = (test, name, prog) => {
289299
});
290300
};
291301

292-
let makeRunner = (test, ~num_pages=?, ~config_fn=?, name, prog, expected) => {
302+
let makeRunner =
303+
(test, ~num_pages=?, ~config_fn=?, ~extra_args=?, name, prog, expected) => {
293304
test(name, ({expect}) => {
294305
Config.preserve_all_configs(() => {
295306
ignore @@ compile(~num_pages?, ~config_fn?, name, module_header ++ prog);
296-
let (result, _) = run(~num_pages?, wasmfile(name));
307+
let (result, _) = run(~num_pages?, ~extra_args?, wasmfile(name));
297308
expect.string(result).toEqual(expected);
298309
})
299310
});

compiler/test/stdlib/sys.process.test.gr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ match (Process.argv()) {
1111

1212
// Just a smoke test
1313
match (Process.env()) {
14-
Ok(arr) => assert Array.length(arr) > 0,
14+
Ok(arr) => assert Array.length(arr) == 0,
1515
Err(err) => throw err,
1616
}
1717

compiler/test/suites/wasi_args.re

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
open Grain_tests.TestFramework;
2+
open Grain_tests.Runner;
3+
4+
describe("wasi args and env", ({test, testSkip}) => {
5+
let test_or_skip =
6+
Sys.backend_type == Other("js_of_ocaml") ? testSkip : test;
7+
8+
let assertRun = makeRunner(test_or_skip);
9+
10+
let print_wasi_info = {|
11+
include "sys/process"
12+
include "array"
13+
include "string"
14+
15+
match (Process.argv()) {
16+
Ok(args) => print(Array.slice(1, args)),
17+
_ => print("Error reading args")
18+
}
19+
match (Process.env()) {
20+
Ok(env) => print(env),
21+
_ => print("Error reading env")
22+
}
23+
|};
24+
25+
assertRun(
26+
~extra_args=[|"--", "a", "b"|],
27+
"print_args1",
28+
print_wasi_info,
29+
"[> \"a\", \"b\"]\n[> ]\n",
30+
);
31+
assertRun(
32+
~extra_args=[|"a", "b"|],
33+
"print_args2",
34+
print_wasi_info,
35+
"[> ]\n[> ]\n",
36+
);
37+
assertRun(
38+
~extra_args=[|"--env=FOO=bar", "a", "b"|],
39+
"print_args3",
40+
print_wasi_info,
41+
"[> ]\n[> \"FOO=bar\"]\n",
42+
);
43+
assertRun(
44+
~extra_args=[|"--env", "FOO=bar", "BAR=baz", "BAZ"|],
45+
"print_args4",
46+
print_wasi_info,
47+
"[> ]\n[> \"FOO=bar\", \"BAR=baz\", \"BAZ=\"]\n",
48+
);
49+
assertRun(
50+
~extra_args=[|"--env", "FOO=bar", "--", "a", "b"|],
51+
"print_args5",
52+
print_wasi_info,
53+
"[> \"a\", \"b\"]\n[> \"FOO=bar\"]\n",
54+
);
55+
assertRun(
56+
~extra_args=[|"--", "a", "b", "--env", "FOO=bar"|],
57+
"print_args6",
58+
print_wasi_info,
59+
"[> \"a\", \"b\", \"--env\", \"FOO=bar\"]\n[> ]\n",
60+
);
61+
});

stdlib/sys/process.gr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ provide let env = () => {
152152
let envc = WasmI32.load(envcPtr, 0n)
153153
let envvBufSize = WasmI32.load(envvBufSizePtr, 0n)
154154

155+
if (WasmI32.eqz(envc)) {
156+
Memory.free(envcPtr)
157+
return Ok([>]: Array<String>)
158+
}
159+
155160
let envvPtr = Memory.malloc(envc * 4n)
156161
let envvBufPtr = Memory.malloc(envvBufSize)
157162

0 commit comments

Comments
 (0)