xref: /wasmtime-44.0.1/src/bin/wasmtime.rs (revision 72004aad)
1 //! The `wasmtime` command line tool.
2 //!
3 //! Primarily used to run WebAssembly modules.
4 //! See `wasmtime --help` for usage.
5 
6 use anyhow::Result;
7 use clap::Parser;
8 
9 /// Wasmtime WebAssembly Runtime
10 #[derive(Parser, PartialEq)]
11 #[command(
12     version = version(),
13     after_help = "If a subcommand is not provided, the `run` subcommand will be used.\n\
14                   \n\
15                   Usage examples:\n\
16                   \n\
17                   Running a WebAssembly module with a start function:\n\
18                   \n  \
19                   wasmtime example.wasm
20                   \n\
21                   Passing command line arguments to a WebAssembly module:\n\
22                   \n  \
23                   wasmtime example.wasm arg1 arg2 arg3\n\
24                   \n\
25                   Invoking a specific function (e.g. `add`) in a WebAssembly module:\n\
26                   \n  \
27                   wasmtime --invoke add example.wasm 1 2\n",
28 
29     // This option enables the pattern below where we ask clap to parse twice
30     // sorta: once where it's trying to find a subcommand and once assuming
31     // a subcommand doesn't get passed. Clap should then, apparently,
32     // fill in the `subcommand` if found and otherwise fill in the
33     // `RunCommand`.
34     args_conflicts_with_subcommands = true
35 )]
36 struct Wasmtime {
37     #[cfg(not(feature = "run"))]
38     #[command(subcommand)]
39     subcommand: Subcommand,
40 
41     #[cfg(feature = "run")]
42     #[command(subcommand)]
43     subcommand: Option<Subcommand>,
44     #[command(flatten)]
45     #[cfg(feature = "run")]
46     run: wasmtime_cli::commands::RunCommand,
47 }
48 
49 /// If WASMTIME_VERSION_INFO is set, use it, otherwise use CARGO_PKG_VERSION.
50 fn version() -> &'static str {
51     option_env!("WASMTIME_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION"))
52 }
53 
54 #[derive(Parser, PartialEq)]
55 enum Subcommand {
56     /// Runs a WebAssembly module
57     #[cfg(feature = "run")]
58     Run(wasmtime_cli::commands::RunCommand),
59 
60     /// Controls Wasmtime configuration settings
61     #[cfg(feature = "cache")]
62     Config(wasmtime_cli::commands::ConfigCommand),
63 
64     /// Compiles a WebAssembly module.
65     #[cfg(feature = "compile")]
66     Compile(wasmtime_cli::commands::CompileCommand),
67 
68     /// Explore the compilation of a WebAssembly module to native code.
69     #[cfg(feature = "explore")]
70     Explore(wasmtime_cli::commands::ExploreCommand),
71 
72     /// Serves requests from a wasi-http proxy component.
73     #[cfg(feature = "serve")]
74     Serve(wasmtime_cli::commands::ServeCommand),
75 
76     /// Displays available Cranelift settings for a target.
77     #[cfg(feature = "cranelift")]
78     Settings(wasmtime_cli::commands::SettingsCommand),
79 
80     /// Runs a WebAssembly test script file
81     #[cfg(feature = "wast")]
82     Wast(wasmtime_cli::commands::WastCommand),
83 }
84 
85 impl Wasmtime {
86     /// Executes the command.
87     pub fn execute(self) -> Result<()> {
88         #[cfg(feature = "run")]
89         let subcommand = self.subcommand.unwrap_or(Subcommand::Run(self.run));
90         #[cfg(not(feature = "run"))]
91         let subcommand = self.subcommand;
92 
93         match subcommand {
94             #[cfg(feature = "run")]
95             Subcommand::Run(c) => c.execute(),
96 
97             #[cfg(feature = "cache")]
98             Subcommand::Config(c) => c.execute(),
99 
100             #[cfg(feature = "compile")]
101             Subcommand::Compile(c) => c.execute(),
102 
103             #[cfg(feature = "explore")]
104             Subcommand::Explore(c) => c.execute(),
105 
106             #[cfg(feature = "serve")]
107             Subcommand::Serve(c) => c.execute(),
108 
109             #[cfg(feature = "cranelift")]
110             Subcommand::Settings(c) => c.execute(),
111 
112             #[cfg(feature = "wast")]
113             Subcommand::Wast(c) => c.execute(),
114         }
115     }
116 }
117 
118 fn main() -> Result<()> {
119     #[cfg(feature = "old-cli")]
120     return old_cli::main();
121 
122     #[cfg(not(feature = "old-cli"))]
123     return Wasmtime::parse().execute();
124 }
125 
126 #[test]
127 fn verify_cli() {
128     use clap::CommandFactory;
129     Wasmtime::command().debug_assert()
130 }
131 
132 #[cfg(feature = "old-cli")]
133 mod old_cli {
134     use crate::Wasmtime;
135     use anyhow::{bail, Result};
136     use clap::error::ErrorKind;
137     use clap::Parser;
138     use wasmtime_cli::old_cli as old;
139 
140     enum WhichCli {
141         Old,
142         New,
143         Unspecified,
144     }
145 
146     const DEFAULT_OLD_BEHAVIOR: bool = true;
147 
148     fn which_cli() -> Result<WhichCli> {
149         Ok(match std::env::var("WASMTIME_NEW_CLI") {
150             Ok(s) if s == "0" => WhichCli::Old,
151             Ok(s) if s == "1" => WhichCli::New,
152             Ok(_) => bail!("the `WASMTIME_NEW_CLI` should either be `0` or `1`"),
153             Err(_) => WhichCli::Unspecified,
154         })
155     }
156 
157     pub fn main() -> Result<()> {
158         match which_cli()? {
159             // If the old or the new CLI is explicitly selected, then run that
160             // variant no questions asked.
161             WhichCli::New => return Wasmtime::parse().execute(),
162             WhichCli::Old => return try_parse_old().unwrap_or_else(|e| e.exit()).execute(),
163 
164             // fall through below to run an unspecified version of the CLI
165             WhichCli::Unspecified => {}
166         }
167 
168         // Here it's not specified which version of the CLI should be used, so
169         // both the old and the new CLI parsers are used and depending on the
170         // results an execution happens.
171         let new = Wasmtime::try_parse();
172         let old = try_parse_old();
173         match (new, old) {
174             // Both parsers succeeded. This means that it's likely that no
175             // options to configure execution, e.g. `--disable-logging`, were
176             // used in the old parser or the new.
177             //
178             // The result of parsing can still differ though. For example:
179             //
180             //      wasmtime foo.wasm --invoke foo
181             //
182             // would previously be parsed as passing `--invoke` to Wasmtime but
183             // the new parser instead parses that as passing the flag to
184             // `foo.wasm`.
185             //
186             // In this situation use the `DEFAULT_OLD_BEHAVIOR` constant to
187             // dictate which wins and additionally print a warning message.
188             (Ok(new), Ok(old)) => {
189                 if new == old {
190                     return new.execute();
191                 }
192                 if DEFAULT_OLD_BEHAVIOR {
193                     eprintln!(
194                         "\
195 warning: this CLI invocation of Wasmtime will be parsed differently in future
196          Wasmtime versions -- see this online issue for more information:
197          https://github.com/bytecodealliance/wasmtime/issues/7384
198 
199          Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing,
200          however this behavior can also be temporarily configured with an
201          environment variable:
202 
203          - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or
204          - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and use the latest behavior\
205 "
206                     );
207                     old.execute()
208                 } else {
209                     // this error message is not statically reachable due to
210                     // `DEFAULT_OLD_BEHAVIOR=true` at this time, but when that
211                     // changes this should be updated to have a more accurate
212                     // set of text.
213                     assert!(false);
214                     eprintln!(
215                         "\
216 warning: this CLI invocation of Wasmtime is parsed differently than it was
217          previously -- see this online issue for more information:
218          https://github.com/bytecodealliance/wasmtime/issues/7384
219 
220          Wasmtime will now execute with the new (>= Wasmtime XXX) CLI parsing,
221          however this behavior can also be temporarily configured with an
222          environment variable:
223 
224          - WASMTIME_NEW_CLI=0 to indicate old semantics are desired instead of the new, or
225          - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and silences this warning\
226 "
227                     );
228                     new.execute()
229                 }
230             }
231 
232             // Here the new parser succeeded where the old one failed. This
233             // could indicate for example that new options are being passed on
234             // the CLI.
235             //
236             // In this situation assume that the new parser is what's intended
237             // so execute those semantics.
238             (Ok(new), Err(_old)) => new.execute(),
239 
240             // Here the new parser failed and the old parser succeeded. This
241             // could indicate for example passing old CLI flags.
242             //
243             // Here assume that the old semantics are desired but emit a warning
244             // indicating that this will change in the future.
245             (Err(_new), Ok(old)) => {
246                 eprintln!(
247                     "\
248 warning: this CLI invocation of Wasmtime is going to break in the future -- for
249          more information see this issue online:
250          https://github.com/bytecodealliance/wasmtime/issues/7384
251 
252          Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing,
253          however this behavior can also be temporarily configured with an
254          environment variable:
255 
256          - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or
257          - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and see the error\
258 "
259                 );
260                 old.execute()
261             }
262 
263             // Both parsers failed to parse the CLI invocation.
264             //
265             // This could mean that someone manually passed an old flag
266             // incorrectly. This could also mean that a new flag was passed
267             // incorrectly. Clap also models `--help` requests as errors here so
268             // this could also mean that a `--help` flag was passed.
269             //
270             // The current assumption in any case is that there's a human
271             // interacting with the CLI at this point. They may or may not be
272             // aware of the old CLI vs new CLI but if we're going to print an
273             // error message then now's probably as good a time as any to nudge
274             // them towards the new CLI. Any preexisting scripts which parsed
275             // the old CLI should not hit this case which means that all old
276             // succesful parses will not go through here.
277             //
278             // In any case, display the error for the new CLI, including new
279             // help text.
280             (Err(new), Err(_old)) => new.exit(),
281         }
282     }
283 
284     fn try_parse_old() -> clap::error::Result<Wasmtime> {
285         match old::Wasmtime::try_parse() {
286             Ok(old) => Ok(convert(old)),
287             Err(e) => {
288                 if let ErrorKind::InvalidSubcommand | ErrorKind::UnknownArgument = e.kind() {
289                     if let Ok(run) = old::RunCommand::try_parse() {
290                         return Ok(Wasmtime {
291                             subcommand: None,
292                             run: run.convert(),
293                         });
294                     }
295                 }
296                 Err(e)
297             }
298         }
299     }
300 
301     fn convert(old: old::Wasmtime) -> Wasmtime {
302         let subcommand = match old {
303             old::Wasmtime::Compile(c) => crate::Subcommand::Compile(c.convert()),
304             old::Wasmtime::Run(c) => crate::Subcommand::Run(c.convert()),
305         };
306         let mut run = wasmtime_cli::commands::RunCommand::parse_from::<_, &str>(["x", "y"]);
307         run.module_and_args = Vec::new();
308         Wasmtime {
309             subcommand: Some(subcommand),
310             run,
311         }
312     }
313 }
314