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