1use crate::config::Config;
2use crate::constants::DEFAULT_RUNTIME;
3use crate::data::lock::is_lockfile_out_of_date;
4use crate::dataflow;
5use crate::dataflow::find_command_result;
6use crate::dataflow::find_command_result::get_command_from_anywhere;
7use crate::dataflow::manifest_packages::ManifestResult;
8use crate::util::get_runtime_with_args;
9use std::ffi::OsString;
10use std::path::{Path, PathBuf};
11#[cfg(not(target_os = "wasi"))]
12use std::process::Command;
13use structopt::StructOpt;
14use thiserror::Error;
15use wapm_toml::Manifest;
16#[cfg(target_os = "wasi")]
17use wasm_bus_process::prelude::Command;
18
19#[derive(StructOpt, Debug)]
20pub struct RunOpt {
21 command: String,
23 #[structopt(long = "dir", multiple = true, group = "wasi")]
25 pre_opened_directories: Vec<String>,
26 #[structopt(multiple = true, parse(from_os_str))]
28 args: Vec<OsString>,
29}
30
31pub fn run(run_options: RunOpt) -> anyhow::Result<()> {
32 let command_name = run_options.command.as_str();
33 let args = &run_options.args;
34 let current_dir = crate::config::Config::get_current_dir()?;
35
36 match is_lockfile_out_of_date(¤t_dir) {
38 Ok(false) => {}
39 _ => dataflow::update(vec![], vec![], ¤t_dir)
40 .map(|_| ())
41 .map_err(|e| RunError::CannotRegenLockfile(command_name.to_string(), e))?,
42 }
43
44 let mut registry_error = Vec::new();
45 let find_command_result = get_command_from_anywhere(command_name);
46 if find_command_result.is_err() {
47 let package_info =
48 find_command_result::PackageInfoFromCommand::get(command_name.to_string());
49 registry_error = match package_info {
50 Err(e) => {
51 vec![format!("Error: {e}"), format!("")]
52 }
53 Ok(o) => vec![
54 format!(
55 "Command {:?} was not found, but the package \"{}@{}\" has this command.",
56 o.command, o.namespaced_package_name, o.version
57 ),
58 format!(
59 "You can install it with `wapm install {}@{}`.",
60 o.namespaced_package_name, o.version
61 ),
62 ],
63 };
64 }
65
66 let find_command_result::Command {
67 source: source_path_buf,
68 manifest_dir,
69 args: _,
70 module_name,
71 is_global,
72 prehashed_cache_key,
73 } = match find_command_result {
74 Err(find_command_result::Error::CommandNotFound {
75 command,
76 mut local_log,
77 mut global_log,
78 ..
79 }) => {
80 let mut log = vec![format!("Could not find command {:?}:", command)];
81 log.append(&mut local_log);
82 log.append(&mut global_log);
83 log.append(&mut registry_error);
84 return Err(anyhow!("{}", log.join("\r\n")));
85 },
86 Err(find_command_result::Error::ReadingLocalDirectory {
87 mut local_log,
88 ..
89 }) => {
90 local_log.append(&mut registry_error);
91 return Err(anyhow!("{}", local_log.join("\r\n")));
92 },
93 Err(find_command_result::Error::CommandNotFoundInLocalDirectoryAndErrorReadingGlobalDirectory {
94 local_log: mut log,
95 mut global_log,
96 ..
97 }) => {
98 log.append(&mut global_log);
99 log.append(&mut registry_error);
100 return Err(anyhow!("{}", log.join("\r\n")));
101 },
102 otherwise => otherwise?,
103 };
104
105 let run_dir = if is_global {
106 Config::get_globals_directory().unwrap()
107 } else {
108 current_dir
109 };
110
111 let manifest_dir = run_dir.join(manifest_dir);
112
113 do_run(
114 run_dir,
115 source_path_buf,
116 manifest_dir,
117 command_name,
118 &module_name,
119 &run_options.pre_opened_directories,
120 args,
121 prehashed_cache_key,
122 )
123}
124
125#[allow(clippy::too_many_arguments)]
126pub(crate) fn do_run(
127 run_dir: PathBuf,
128 source_path_buf: PathBuf,
129 manifest_dir: PathBuf,
130 command_name: &str,
131 module_name: &str,
132 pre_opened_directories: &[String],
133 args: &[OsString],
134 prehashed_cache_key: Option<String>,
135) -> anyhow::Result<()> {
136 debug!(
137 "Running module located at {:?}",
138 &run_dir.join(&source_path_buf)
139 );
140
141 run_dir.join(&source_path_buf).metadata().map_err(|_| {
142 RunError::SourceForCommandNotFound(
143 command_name.to_string(),
144 module_name.to_string(),
145 source_path_buf.to_string_lossy().to_string(),
146 )
147 })?;
148
149 let wasmer_extra_flags: Option<Vec<OsString>> =
150 match ManifestResult::find_in_directory(&manifest_dir) {
151 ManifestResult::Manifest(manifest) => manifest
152 .package
153 .wasmer_extra_flags
154 .map(|extra_flags| extra_flags.split_whitespace().map(OsString::from).collect()),
155 _ => None,
156 };
157
158 let mut wasi_preopened_dir_flags: Vec<OsString> = pre_opened_directories
159 .iter()
160 .map(|entry| OsString::from(format!("--dir={}", entry)))
161 .collect();
162
163 let mut disable_command_rename = false;
164
165 if let ManifestResult::Manifest(Manifest { package, fs, .. }) =
166 ManifestResult::find_in_directory(&manifest_dir)
167 {
168 disable_command_rename = package.disable_command_rename;
169
170 if let Some(ref fs) = fs {
171 for (guest_path, host_path) in fs.iter() {
173 wasi_preopened_dir_flags.push(OsString::from(format!(
174 "--mapdir={}:{}",
175 guest_path,
176 manifest_dir.join(host_path).to_string_lossy(),
177 )));
178 }
179 }
180 }
181
182 let (runtime, runtime_args) = get_runtime_with_args();
183
184 let mut cmd;
185 if cfg!(target_os = "wasi") {
186 debug!("Running wapm process: {:?}", source_path_buf);
187 cmd = Command::new(source_path_buf.to_string_lossy().as_ref());
188 cmd.args(args);
189 #[cfg(target_os = "wasi")]
190 for preopen in wasi_preopened_dir_flags
191 .iter()
192 .map(|a| a.to_string_lossy())
193 .filter_map(|a| a.split_once("=").map(|a| a.1.replace("\"", "")))
194 {
195 cmd.pre_open(preopen.to_string());
196 }
197 } else {
198 let using_default_runtime = Path::new(&runtime)
200 .file_name()
201 .map(|file_name| file_name.to_string_lossy().ends_with(DEFAULT_RUNTIME))
202 .unwrap_or(false);
203 let command_override_name = if !using_default_runtime || disable_command_rename {
204 None
205 } else {
206 Some(command_name.to_string())
207 };
208 let command_vec = create_run_command(
209 args,
210 wasmer_extra_flags,
211 wasi_preopened_dir_flags,
212 &run_dir,
213 source_path_buf,
214 command_override_name,
215 prehashed_cache_key,
216 )?;
217 debug!("Running command with args: {:?}", command_vec);
218 cmd = Command::new(&runtime);
219 cmd.args(&runtime_args);
220 cmd.args(&command_vec);
221 };
222
223 let mut child = cmd
224 .spawn()
225 .map_err(|e| -> RunError { RunError::ProcessFailed(runtime, format!("{:?}", e)) })?;
226
227 child.wait()?;
228 Ok(())
229}
230
231fn create_run_command<P: AsRef<Path>, P2: AsRef<Path>>(
232 args: &[OsString],
233 wasmer_extra_flags: Option<Vec<OsString>>,
234 wasi_preopened_dir_flags: Vec<OsString>,
235 directory: P,
236 wasm_file_path: P2,
237 override_command_name: Option<String>,
238 prehashed_cache_key: Option<String>,
239) -> anyhow::Result<Vec<OsString>> {
240 let mut path = PathBuf::new();
241 path.push(directory);
242 path.push(wasm_file_path);
243 let path_string = path.into_os_string();
244 let command_vec = vec![path_string];
245 let override_command_name = override_command_name
246 .map(|cn| vec![OsString::from(format!("--command-name={}", cn))])
247 .unwrap_or_default();
248 let prehashed_cache_key_flag = prehashed_cache_key
249 .map(|pck| vec![OsString::from(format!("--cache-key=\"{}\"", pck))])
250 .unwrap_or_default();
251
252 Ok([
256 command_vec.as_slice(),
257 &override_command_name,
258 &wasi_preopened_dir_flags,
259 wasmer_extra_flags.as_deref().unwrap_or_default(),
260 &prehashed_cache_key_flag,
261 &[OsString::from("--")],
262 args,
263 ]
264 .concat())
265}
266
267#[cfg(test)]
268mod test {
269 use crate::commands::run::create_run_command;
270 use crate::data::manifest::PACKAGES_DIR_NAME;
271 use crate::util::create_temp_dir;
272 use std::ffi::OsString;
273 use std::fs;
274 use std::path::PathBuf;
275
276 #[test]
277 fn create_run_command_vec() {
278 let args: Vec<OsString> = vec![OsString::from("arg1"), OsString::from("arg2")];
279 let tmp_dir = create_temp_dir().unwrap();
280 let dir: &std::path::Path = tmp_dir.as_ref();
281 let wapm_module_dir = dir.join(
282 [PACKAGES_DIR_NAME, "_", "foo@1.0.2"]
283 .iter()
284 .collect::<PathBuf>(),
285 );
286 fs::create_dir_all(&wapm_module_dir).unwrap();
287 let expected_dir = wapm_module_dir.join("foo_entry.wasm");
288 let expected_command = vec![
289 expected_dir.into_os_string(),
290 OsString::from("--"),
291 OsString::from("arg1"),
292 OsString::from("arg2"),
293 ];
294 let wasm_relative_path: PathBuf = ["wapm_packages", "_", "foo@1.0.2", "foo_entry.wasm"]
295 .iter()
296 .collect();
297 let actual_command =
298 create_run_command(&args, None, vec![], dir, wasm_relative_path, None, None).unwrap();
299 assert_eq!(expected_command, actual_command);
300 }
301}
302
303#[derive(Debug, Error)]
304enum RunError {
305 #[error("Failed to run command \"{0}\". {1}")]
306 CannotRegenLockfile(String, dataflow::Error),
307 #[error(
308 "The command \"{0}\" for module \"{1}\" is defined but the source at \"{2}\" does not exist.",
309 )]
310 SourceForCommandNotFound(String, String, String),
311 #[error("Failed to run {0}: {1}")]
312 ProcessFailed(String, String),
313}