wasmer_cli/commands/run/
runtime.rs

1//! Provides CLI-specific Wasix components.
2
3use std::{sync::Arc, time::Duration};
4
5use anyhow::Error;
6use futures::future::BoxFuture;
7use indicatif::ProgressBar;
8use std::io::IsTerminal as _;
9use wasmer::{Engine, Module};
10use wasmer_config::package::PackageSource;
11use wasmer_types::ModuleHash;
12use wasmer_wasix::{
13    SpawnError,
14    bin_factory::{BinaryPackage, BinaryPackageCommand},
15    runtime::{
16        module_cache::HashedModuleData,
17        resolver::{PackageSummary, QueryError},
18    },
19};
20use webc::Container;
21
22/// Special wasix runtime implementation for the CLI.
23///
24/// Wraps an undelrying runtime and adds progress monitoring for package
25/// compilation.
26#[derive(Debug)]
27pub struct MonitoringRuntime<R> {
28    pub runtime: Arc<R>,
29    progress: ProgressBar,
30    quiet_mode: bool,
31}
32
33impl<R> MonitoringRuntime<R> {
34    pub fn new(runtime: R, progress: ProgressBar, quiet_mode: bool) -> Self {
35        MonitoringRuntime {
36            runtime: Arc::new(runtime),
37            progress,
38            quiet_mode,
39        }
40    }
41}
42
43impl<R: wasmer_wasix::Runtime + Send + Sync> wasmer_wasix::Runtime for MonitoringRuntime<R> {
44    fn networking(&self) -> &virtual_net::DynVirtualNetworking {
45        self.runtime.networking()
46    }
47
48    fn task_manager(&self) -> &Arc<dyn wasmer_wasix::VirtualTaskManager> {
49        self.runtime.task_manager()
50    }
51
52    fn package_loader(
53        &self,
54    ) -> Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync> {
55        let inner = self.runtime.package_loader();
56        Arc::new(MonitoringPackageLoader {
57            inner,
58            progress: self.progress.clone(),
59        })
60    }
61
62    fn module_cache(
63        &self,
64    ) -> Arc<dyn wasmer_wasix::runtime::module_cache::ModuleCache + Send + Sync> {
65        self.runtime.module_cache()
66    }
67
68    fn source(&self) -> Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync> {
69        let inner = self.runtime.source();
70        Arc::new(MonitoringSource {
71            inner,
72            progress: self.progress.clone(),
73        })
74    }
75
76    fn engine(&self) -> Engine {
77        self.runtime.engine()
78    }
79
80    fn new_store(&self) -> wasmer::Store {
81        self.runtime.new_store()
82    }
83
84    fn http_client(&self) -> Option<&wasmer_wasix::http::DynHttpClient> {
85        self.runtime.http_client()
86    }
87
88    fn tty(&self) -> Option<&(dyn wasmer_wasix::os::TtyBridge + Send + Sync)> {
89        self.runtime.tty()
90    }
91
92    #[cfg(feature = "journal")]
93    fn read_only_journals<'a>(
94        &'a self,
95    ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynReadableJournal>> + 'a> {
96        self.runtime.read_only_journals()
97    }
98
99    #[cfg(feature = "journal")]
100    fn writable_journals<'a>(
101        &'a self,
102    ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynJournal>> + 'a> {
103        self.runtime.writable_journals()
104    }
105
106    #[cfg(feature = "journal")]
107    fn active_journal(&self) -> Option<&'_ wasmer_wasix::journal::DynJournal> {
108        self.runtime.active_journal()
109    }
110
111    fn load_hashed_module(
112        &self,
113        module: HashedModuleData,
114        engine: Option<&Engine>,
115    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
116        if self.quiet_mode {
117            Box::pin(self.runtime.load_hashed_module(module, engine))
118        } else {
119            let hash = *module.hash();
120            let fut = self.runtime.load_hashed_module(module, engine);
121            Box::pin(compile_with_progress(
122                &self.progress,
123                fut,
124                hash,
125                None,
126                self.quiet_mode,
127            ))
128        }
129    }
130
131    fn load_hashed_module_sync(
132        &self,
133        wasm: HashedModuleData,
134        engine: Option<&Engine>,
135    ) -> Result<Module, wasmer_wasix::SpawnError> {
136        if self.quiet_mode {
137            self.runtime.load_hashed_module_sync(wasm, engine)
138        } else {
139            let hash = *wasm.hash();
140            compile_with_progress_sync(
141                &self.progress,
142                move || self.runtime.load_hashed_module_sync(wasm, engine),
143                &hash,
144                None,
145            )
146        }
147    }
148
149    fn load_command_module(
150        &self,
151        cmd: &BinaryPackageCommand,
152    ) -> BoxFuture<'_, Result<Module, SpawnError>> {
153        if self.quiet_mode {
154            self.runtime.load_command_module(cmd)
155        } else {
156            let fut = self.runtime.load_command_module(cmd);
157
158            Box::pin(compile_with_progress(
159                &self.progress,
160                fut,
161                *cmd.hash(),
162                Some(cmd.name().to_owned()),
163                self.quiet_mode,
164            ))
165        }
166    }
167
168    fn load_command_module_sync(
169        &self,
170        cmd: &wasmer_wasix::bin_factory::BinaryPackageCommand,
171    ) -> Result<Module, wasmer_wasix::SpawnError> {
172        if self.quiet_mode {
173            self.runtime.load_command_module_sync(cmd)
174        } else {
175            compile_with_progress_sync(
176                &self.progress,
177                || self.runtime.load_command_module_sync(cmd),
178                cmd.hash(),
179                Some(cmd.name()),
180            )
181        }
182    }
183}
184
185async fn compile_with_progress<'a, F, T>(
186    bar: &ProgressBar,
187    fut: F,
188    hash: ModuleHash,
189    name: Option<String>,
190    quiet_mode: bool,
191) -> T
192where
193    F: std::future::Future<Output = T> + Send + 'a,
194    T: Send + 'static,
195{
196    if quiet_mode {
197        fut.await
198    } else {
199        let should_clear = bar.is_finished() || bar.is_hidden();
200        show_compile_progress(bar, &hash, name.as_deref());
201        let res = fut.await;
202        if should_clear {
203            bar.finish_and_clear();
204        }
205
206        res
207    }
208}
209
210fn compile_with_progress_sync<F, T>(
211    bar: &ProgressBar,
212    f: F,
213    hash: &ModuleHash,
214    name: Option<&str>,
215) -> T
216where
217    F: FnOnce() -> T,
218{
219    let should_clear = bar.is_finished() || bar.is_hidden();
220    show_compile_progress(bar, hash, name);
221    let res = f();
222    if should_clear {
223        bar.finish_and_clear();
224    }
225    res
226}
227
228fn show_compile_progress(bar: &ProgressBar, hash: &ModuleHash, name: Option<&str>) {
229    // Only show a spinner if we're running in a TTY
230    let hash = hash.to_string();
231    let hash = &hash[0..8];
232    let msg = if let Some(name) = name {
233        format!("Compiling WebAssembly module for command '{name}' ({hash})...")
234    } else {
235        format!("Compiling WebAssembly module {hash}...")
236    };
237
238    bar.set_message(msg);
239    bar.enable_steady_tick(Duration::from_millis(100));
240
241    if bar.is_finished() || bar.is_hidden() {
242        bar.reset();
243    }
244}
245
246#[derive(Debug)]
247struct MonitoringSource {
248    inner: Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync>,
249    progress: ProgressBar,
250}
251
252#[async_trait::async_trait]
253impl wasmer_wasix::runtime::resolver::Source for MonitoringSource {
254    async fn query(&self, package: &PackageSource) -> Result<Vec<PackageSummary>, QueryError> {
255        self.progress.set_message(format!("Looking up {package}"));
256        self.inner.query(package).await
257    }
258}
259
260#[derive(Debug)]
261struct MonitoringPackageLoader {
262    inner: Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync>,
263    progress: ProgressBar,
264}
265
266#[async_trait::async_trait]
267impl wasmer_wasix::runtime::package_loader::PackageLoader for MonitoringPackageLoader {
268    async fn load(&self, summary: &PackageSummary) -> Result<Container, Error> {
269        let pkg_id = summary.package_id();
270        self.progress.set_message(format!("Downloading {pkg_id}"));
271
272        self.inner.load(summary).await
273    }
274
275    async fn load_package_tree(
276        &self,
277        root: &Container,
278        resolution: &wasmer_wasix::runtime::resolver::Resolution,
279        root_is_local_dir: bool,
280    ) -> Result<BinaryPackage, Error> {
281        self.inner
282            .load_package_tree(root, resolution, root_is_local_dir)
283            .await
284    }
285}