Skip to content

Commit 1c99297

Browse files
committed
Partially revert CLI argument changes from bytecodealliance#6737
This commit is a partial revert of bytecodealliance#6737. That change was reverted in bytecodealliance#6830 for the 12.0.0 release of Wasmtime and otherwise it's currently slated to get released with the 13.0.0 release of Wasmtime. Discussion at today's Wasmtime meeting concluded that it's best to couple this change with bytecodealliance#6925 as a single release rather than spread out across multiple releases. This commit is thus the revert of bytecodealliance#6737, although it's a partial revert in that I've kept many of the new tests added to showcase the differences before/after when the change lands. This means that Wasmtime 13.0.0 will exhibit the same CLI behavior as 12.0.0 and all prior releases. The 14.0.0 release will have both a new CLI and new argument passing semantics. I'll revert this revert (aka re-land bytecodealliance#6737) once the 13.0.0 release branch is created and `main` becomes 14.0.0.
1 parent c56cc24 commit 1c99297

File tree

3 files changed

+94
-82
lines changed

3 files changed

+94
-82
lines changed

src/bin/wasmtime.rs

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! See `wasmtime --help` for usage.
55
66
use anyhow::Result;
7-
use clap::Parser;
7+
use clap::{error::ErrorKind, Parser};
88
use wasmtime_cli::commands::{
99
CompileCommand, ConfigCommand, ExploreCommand, RunCommand, SettingsCommand, WastCommand,
1010
};
@@ -27,24 +27,10 @@ use wasmtime_cli::commands::{
2727
\n\
2828
Invoking a specific function (e.g. `add`) in a WebAssembly module:\n\
2929
\n \
30-
wasmtime example.wasm --invoke add 1 2\n",
31-
32-
// This option enables the pattern below where we ask clap to parse twice
33-
// sorta: once where it's trying to find a subcommand and once assuming
34-
// a subcommand doesn't get passed. Clap should then, apparently,
35-
// fill in the `subcommand` if found and otherwise fill in the
36-
// `RunCommand`.
37-
args_conflicts_with_subcommands = true
30+
wasmtime example.wasm --invoke add 1 2\n"
3831
)]
39-
struct Wasmtime {
40-
#[clap(subcommand)]
41-
subcommand: Option<Subcommand>,
42-
#[clap(flatten)]
43-
run: RunCommand,
44-
}
45-
46-
#[derive(Parser)]
47-
enum Subcommand {
32+
enum Wasmtime {
33+
// !!! IMPORTANT: if subcommands are added or removed, update `parse_module` in `src/commands/run.rs`. !!!
4834
/// Controls Wasmtime configuration settings
4935
Config(ConfigCommand),
5036
/// Compiles a WebAssembly module.
@@ -62,20 +48,26 @@ enum Subcommand {
6248
impl Wasmtime {
6349
/// Executes the command.
6450
pub fn execute(self) -> Result<()> {
65-
let subcommand = self.subcommand.unwrap_or(Subcommand::Run(self.run));
66-
match subcommand {
67-
Subcommand::Config(c) => c.execute(),
68-
Subcommand::Compile(c) => c.execute(),
69-
Subcommand::Explore(c) => c.execute(),
70-
Subcommand::Run(c) => c.execute(),
71-
Subcommand::Settings(c) => c.execute(),
72-
Subcommand::Wast(c) => c.execute(),
51+
match self {
52+
Self::Config(c) => c.execute(),
53+
Self::Compile(c) => c.execute(),
54+
Self::Explore(c) => c.execute(),
55+
Self::Run(c) => c.execute(),
56+
Self::Settings(c) => c.execute(),
57+
Self::Wast(c) => c.execute(),
7358
}
7459
}
7560
}
7661

7762
fn main() -> Result<()> {
78-
Wasmtime::parse().execute()
63+
Wasmtime::try_parse()
64+
.unwrap_or_else(|e| match e.kind() {
65+
ErrorKind::InvalidSubcommand | ErrorKind::UnknownArgument => {
66+
Wasmtime::Run(RunCommand::parse())
67+
}
68+
_ => e.exit(),
69+
})
70+
.execute()
7971
}
8072

8173
#[test]

src/commands/run.rs

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
)]
77

88
use anyhow::{anyhow, bail, Context as _, Error, Result};
9+
use clap::builder::{OsStringValueParser, TypedValueParser};
910
use clap::Parser;
1011
use once_cell::sync::Lazy;
12+
use std::ffi::OsStr;
13+
use std::ffi::OsString;
1114
use std::fs::File;
1215
use std::io::Write;
1316
use std::path::{Path, PathBuf};
@@ -35,6 +38,18 @@ use wasmtime_wasi_threads::WasiThreadsCtx;
3538
// #[cfg(feature = "wasi-http")]
3639
// use wasmtime_wasi_http::WasiHttpCtx;
3740

41+
fn parse_module(s: OsString) -> anyhow::Result<PathBuf> {
42+
// Do not accept wasmtime subcommand names as the module name
43+
match s.to_str() {
44+
Some("help") | Some("config") | Some("run") | Some("wast") | Some("compile") => {
45+
bail!("module name cannot be the same as a subcommand")
46+
}
47+
#[cfg(unix)]
48+
Some("-") => Ok(PathBuf::from("/dev/stdin")),
49+
_ => Ok(s.into()),
50+
}
51+
}
52+
3853
fn parse_env_var(s: &str) -> Result<(String, Option<String>)> {
3954
let mut parts = s.splitn(2, '=');
4055
Ok((
@@ -103,7 +118,7 @@ static AFTER_HELP: Lazy<String> = Lazy::new(|| crate::FLAG_EXPLANATIONS.to_strin
103118

104119
/// Runs a WebAssembly module
105120
#[derive(Parser)]
106-
#[structopt(name = "run", after_help = AFTER_HELP.as_str())]
121+
#[structopt(name = "run", trailing_var_arg = true, after_help = AFTER_HELP.as_str())]
107122
pub struct RunCommand {
108123
#[clap(flatten)]
109124
common: CommonOptions,
@@ -177,6 +192,14 @@ pub struct RunCommand {
177192
#[clap(long = "wasi-nn-graph", value_name = "FORMAT::HOST_DIR", value_parser = parse_graphs)]
178193
graphs: Vec<(String, String)>,
179194

195+
/// The path of the WebAssembly module to run
196+
#[clap(
197+
required = true,
198+
value_name = "MODULE",
199+
value_parser = OsStringValueParser::new().try_map(parse_module),
200+
)]
201+
module: PathBuf,
202+
180203
/// Load the given WebAssembly module before the main module
181204
#[clap(
182205
long = "preload",
@@ -220,6 +243,11 @@ pub struct RunCommand {
220243
#[clap(long = "coredump-on-trap", value_name = "PATH")]
221244
coredump_on_trap: Option<String>,
222245

246+
// NOTE: this must come last for trailing varargs
247+
/// The arguments to pass to the module
248+
#[clap(value_name = "ARGS")]
249+
module_args: Vec<String>,
250+
223251
/// Maximum size, in bytes, that a linear memory is allowed to reach.
224252
///
225253
/// Growth beyond this limit will cause `memory.grow` instructions in
@@ -258,14 +286,6 @@ pub struct RunCommand {
258286
#[clap(long)]
259287
wmemcheck: bool,
260288

261-
/// The WebAssembly module to run and arguments to pass to it.
262-
///
263-
/// Arguments passed to the wasm module will be configured as WASI CLI
264-
/// arguments unless the `--invoke` CLI argument is passed in which case
265-
/// arguments will be interpreted as arguments to the function specified.
266-
#[clap(value_name = "WASM", trailing_var_arg = true, required = true)]
267-
module_and_args: Vec<PathBuf>,
268-
269289
/// Indicates that the implementation of WASI preview1 should be backed by
270290
/// the preview2 implementation for components.
271291
///
@@ -337,7 +357,7 @@ impl RunCommand {
337357
let engine = Engine::new(&config)?;
338358

339359
// Read the wasm module binary either as `*.wat` or a raw binary.
340-
let main = self.load_module(&engine, &self.module_and_args[0])?;
360+
let main = self.load_module(&engine, &self.module)?;
341361

342362
// Validate coredump-on-trap argument
343363
if let Some(coredump_path) = self.coredump_on_trap.as_ref() {
@@ -429,12 +449,8 @@ impl RunCommand {
429449
// Load the main wasm module.
430450
match self
431451
.load_main_module(&mut store, &mut linker, &main, modules)
432-
.with_context(|| {
433-
format!(
434-
"failed to run main module `{}`",
435-
self.module_and_args[0].display()
436-
)
437-
}) {
452+
.with_context(|| format!("failed to run main module `{}`", self.module.display()))
453+
{
438454
Ok(()) => (),
439455
Err(e) => {
440456
// Exit the process if Wasmtime understands the error;
@@ -483,25 +499,27 @@ impl RunCommand {
483499
Ok(listeners)
484500
}
485501

486-
fn compute_argv(&self) -> Result<Vec<String>> {
502+
fn compute_argv(&self) -> Vec<String> {
487503
let mut result = Vec::new();
488504

489-
for (i, arg) in self.module_and_args.iter().enumerate() {
490-
// For argv[0], which is the program name. Only include the base
491-
// name of the main wasm module, to avoid leaking path information.
492-
let arg = if i == 0 {
493-
arg.components().next_back().unwrap().as_os_str()
494-
} else {
495-
arg.as_ref()
496-
};
497-
result.push(
498-
arg.to_str()
499-
.ok_or_else(|| anyhow!("failed to convert {arg:?} to utf-8"))?
500-
.to_string(),
501-
);
505+
// Add argv[0], which is the program name. Only include the base name of the
506+
// main wasm module, to avoid leaking path information.
507+
result.push(
508+
self.module
509+
.components()
510+
.next_back()
511+
.map(|c| c.as_os_str())
512+
.and_then(OsStr::to_str)
513+
.unwrap_or("")
514+
.to_owned(),
515+
);
516+
517+
// Add the remaining arguments.
518+
for arg in self.module_args.iter() {
519+
result.push(arg.clone());
502520
}
503521

504-
Ok(result)
522+
result
505523
}
506524

507525
fn setup_epoch_handler(
@@ -510,7 +528,7 @@ impl RunCommand {
510528
modules: Vec<(String, Module)>,
511529
) -> Box<dyn FnOnce(&mut Store<Host>)> {
512530
if let Some(Profile::Guest { path, interval }) = &self.profile {
513-
let module_name = self.module_and_args[0].to_str().unwrap_or("<main module>");
531+
let module_name = self.module.to_str().unwrap_or("<main module>");
514532
let interval = *interval;
515533
store.data_mut().guest_profiler =
516534
Some(Arc::new(GuestProfiler::new(module_name, interval, modules)));
@@ -616,10 +634,9 @@ impl RunCommand {
616634
CliLinker::Core(linker) => {
617635
// Use "" as a default module name.
618636
let module = module.unwrap_core();
619-
linker.module(&mut *store, "", &module).context(format!(
620-
"failed to instantiate {:?}",
621-
self.module_and_args[0]
622-
))?;
637+
linker
638+
.module(&mut *store, "", &module)
639+
.context(format!("failed to instantiate {:?}", self.module))?;
623640

624641
// If a function to invoke was given, invoke it.
625642
let func = if let Some(name) = &self.invoke {
@@ -678,7 +695,7 @@ impl RunCommand {
678695
is experimental and may break in the future"
679696
);
680697
}
681-
let mut args = self.module_and_args.iter().skip(1);
698+
let mut args = self.module_args.iter();
682699
let mut values = Vec::new();
683700
for ty in ty.params() {
684701
let val = match args.next() {
@@ -691,9 +708,6 @@ impl RunCommand {
691708
}
692709
}
693710
};
694-
let val = val
695-
.to_str()
696-
.ok_or_else(|| anyhow!("argument is not valid utf-8: {val:?}"))?;
697711
values.push(match ty {
698712
// TODO: integer parsing here should handle hexadecimal notation
699713
// like `0x0...`, but the Rust standard library currently only
@@ -751,9 +765,7 @@ impl RunCommand {
751765
if !err.is::<wasmtime::Trap>() {
752766
return err;
753767
}
754-
let source_name = self.module_and_args[0]
755-
.to_str()
756-
.unwrap_or_else(|| "unknown");
768+
let source_name = self.module.to_str().unwrap_or_else(|| "unknown");
757769

758770
if let Err(coredump_err) = generate_coredump(&err, &source_name, coredump_path) {
759771
eprintln!("warning: coredump failed to generate: {}", coredump_err);
@@ -990,7 +1002,7 @@ impl RunCommand {
9901002

9911003
fn set_preview1_ctx(&self, store: &mut Store<Host>) -> Result<()> {
9921004
let mut builder = WasiCtxBuilder::new();
993-
builder.inherit_stdio().args(&self.compute_argv()?)?;
1005+
builder.inherit_stdio().args(&self.compute_argv())?;
9941006

9951007
for (key, value) in self.vars.iter() {
9961008
let value = match value {
@@ -1022,7 +1034,7 @@ impl RunCommand {
10221034

10231035
fn set_preview2_ctx(&self, store: &mut Store<Host>) -> Result<()> {
10241036
let mut builder = preview2::WasiCtxBuilder::new();
1025-
builder.inherit_stdio().args(&self.compute_argv()?);
1037+
builder.inherit_stdio().args(&self.compute_argv());
10261038

10271039
for (key, value) in self.vars.iter() {
10281040
let value = match value {

tests/all/cli_tests.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,7 @@ fn wasm_flags() -> Result<()> {
604604
let stdout = run_wasmtime(&[
605605
"run",
606606
"tests/all/cli_tests/print-arguments.wat",
607+
"--",
607608
"--argument",
608609
"-for",
609610
"the",
@@ -619,15 +620,15 @@ fn wasm_flags() -> Result<()> {
619620
command\n\
620621
"
621622
);
622-
let stdout = run_wasmtime(&["run", "tests/all/cli_tests/print-arguments.wat", "-"])?;
623+
let stdout = run_wasmtime(&["run", "tests/all/cli_tests/print-arguments.wat", "--", "-"])?;
623624
assert_eq!(
624625
stdout,
625626
"\
626627
print-arguments.wat\n\
627628
-\n\
628629
"
629630
);
630-
let stdout = run_wasmtime(&["run", "tests/all/cli_tests/print-arguments.wat", "--"])?;
631+
let stdout = run_wasmtime(&["run", "tests/all/cli_tests/print-arguments.wat", "--", "--"])?;
631632
assert_eq!(
632633
stdout,
633634
"\
@@ -648,7 +649,6 @@ fn wasm_flags() -> Result<()> {
648649
"\
649650
print-arguments.wat\n\
650651
--\n\
651-
--\n\
652652
-a\n\
653653
b\n\
654654
"
@@ -658,30 +658,37 @@ fn wasm_flags() -> Result<()> {
658658

659659
#[test]
660660
fn name_same_as_builtin_command() -> Result<()> {
661-
// a bare subcommand shouldn't run successfully
661+
// A bare subcommand shouldn't run successfully.
662+
//
663+
// This is ambiguous between a missing module argument and a module named
664+
// `run` with no other options.
662665
let output = get_wasmtime_command()?
663666
.current_dir("tests/all/cli_tests")
664667
.arg("run")
665668
.output()?;
666669
assert!(!output.status.success());
667670

668-
// a `--` prefix should let everything else get interpreted as a wasm
669-
// module and arguments, even if the module has a name like `run`
671+
// Currently even `--` isn't enough to disambiguate, so this still is an
672+
// error.
673+
//
674+
// NB: this will change in Wasmtime 14 when #6737 is relanded.
670675
let output = get_wasmtime_command()?
671676
.current_dir("tests/all/cli_tests")
672677
.arg("--")
673678
.arg("run")
674679
.output()?;
675-
assert!(output.status.success(), "expected success got {output:#?}");
680+
assert!(!output.status.success(), "expected failure got {output:#?}");
676681

677-
// Passing options before the subcommand should work and doesn't require
678-
// `--` to disambiguate
682+
// Passing `--foo` options before the module is also not enough to
683+
// disambiguate for now.
684+
//
685+
// NB: this will change in Wasmtime 14 when #6737 is relanded.
679686
let output = get_wasmtime_command()?
680687
.current_dir("tests/all/cli_tests")
681688
.arg("--disable-cache")
682689
.arg("run")
683690
.output()?;
684-
assert!(output.status.success(), "expected success got {output:#?}");
691+
assert!(!output.status.success(), "expected failure got {output:#?}");
685692
Ok(())
686693
}
687694

@@ -701,6 +708,7 @@ fn wasm_flags_without_subcommand() -> Result<()> {
701708
let output = get_wasmtime_command()?
702709
.current_dir("tests/all/cli_tests/")
703710
.arg("print-arguments.wat")
711+
.arg("--")
704712
.arg("-foo")
705713
.arg("bar")
706714
.output()?;

0 commit comments

Comments
 (0)