Skip to main content

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        ModuleInput,
17        module_cache::{
18            HashedModuleData,
19            progress::{ModuleLoadProgress, ModuleLoadProgressReporter},
20        },
21        resolver::{PackageSummary, QueryError},
22    },
23};
24use webc::Container;
25
26/// Special wasix runtime implementation for the CLI.
27///
28/// Wraps an underlying runtime and adds progress monitoring for package
29/// compilation.
30#[derive(Debug)]
31pub struct MonitoringRuntime<R> {
32    pub runtime: Arc<R>,
33    progress: ProgressBar,
34    quiet_mode: bool,
35}
36
37impl<R> MonitoringRuntime<R> {
38    pub fn new(runtime: R, progress: ProgressBar, quiet_mode: bool) -> Self {
39        MonitoringRuntime {
40            runtime: Arc::new(runtime),
41            progress,
42            quiet_mode,
43        }
44    }
45}
46
47impl<R: wasmer_wasix::Runtime + Send + Sync> wasmer_wasix::Runtime for MonitoringRuntime<R> {
48    fn networking(&self) -> &virtual_net::DynVirtualNetworking {
49        self.runtime.networking()
50    }
51
52    fn task_manager(&self) -> &Arc<dyn wasmer_wasix::VirtualTaskManager> {
53        self.runtime.task_manager()
54    }
55
56    fn package_loader(
57        &self,
58    ) -> Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync> {
59        let inner = self.runtime.package_loader();
60        Arc::new(MonitoringPackageLoader {
61            inner,
62            progress: self.progress.clone(),
63        })
64    }
65
66    fn module_cache(
67        &self,
68    ) -> Arc<dyn wasmer_wasix::runtime::module_cache::ModuleCache + Send + Sync> {
69        self.runtime.module_cache()
70    }
71
72    fn source(&self) -> Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync> {
73        let inner = self.runtime.source();
74        Arc::new(MonitoringSource {
75            inner,
76            progress: self.progress.clone(),
77        })
78    }
79
80    fn engine(&self) -> Engine {
81        self.runtime.engine()
82    }
83
84    fn new_store(&self) -> wasmer::Store {
85        self.runtime.new_store()
86    }
87
88    fn http_client(&self) -> Option<&wasmer_wasix::http::DynHttpClient> {
89        self.runtime.http_client()
90    }
91
92    fn tty(&self) -> Option<&(dyn wasmer_wasix::os::TtyBridge + Send + Sync)> {
93        self.runtime.tty()
94    }
95
96    fn additional_imports(
97        &self,
98        module: &Module,
99        store: &mut wasmer::StoreMut,
100    ) -> anyhow::Result<(wasmer::Imports, wasmer_wasix::runtime::InstantiationState)> {
101        self.runtime.additional_imports(module, store)
102    }
103
104    fn prepare_imports(
105        &self,
106        module: &Module,
107        store: &mut wasmer::StoreMut,
108        imports: &mut wasmer::Imports,
109    ) -> anyhow::Result<wasmer_wasix::runtime::InstantiationState> {
110        self.runtime.prepare_imports(module, store, imports)
111    }
112
113    fn configure_new_instance(
114        &self,
115        module: &Module,
116        store: &mut wasmer::StoreMut,
117        instance: &wasmer::Instance,
118        imported_memory: Option<&wasmer::Memory>,
119        state: wasmer_wasix::runtime::InstantiationState,
120    ) -> anyhow::Result<()> {
121        self.runtime
122            .configure_new_instance(module, store, instance, imported_memory, state)
123    }
124
125    #[cfg(feature = "journal")]
126    fn read_only_journals<'a>(
127        &'a self,
128    ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynReadableJournal>> + 'a> {
129        self.runtime.read_only_journals()
130    }
131
132    #[cfg(feature = "journal")]
133    fn writable_journals<'a>(
134        &'a self,
135    ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynJournal>> + 'a> {
136        self.runtime.writable_journals()
137    }
138
139    #[cfg(feature = "journal")]
140    fn active_journal(&self) -> Option<&'_ wasmer_wasix::journal::DynJournal> {
141        self.runtime.active_journal()
142    }
143
144    fn resolve_module<'a>(
145        &'a self,
146        input: ModuleInput<'a>,
147        engine: Option<&Engine>,
148        on_progress: Option<ModuleLoadProgressReporter>,
149    ) -> BoxFuture<'a, Result<Module, SpawnError>> {
150        // If a progress reporter is already provided, or quiet mode is enabled,
151        // just delegate to the inner runtime.
152        if on_progress.is_some() || self.quiet_mode {
153            return self.runtime.resolve_module(input, engine, on_progress);
154        }
155
156        // Compile with progress monitoring through the progress bar.
157
158        use std::fmt::Write as _;
159
160        let short_hash = input.hash().short_hash();
161        let progress_msg = match &input {
162            ModuleInput::Bytes(_) | ModuleInput::Hashed(_) => {
163                format!("Compiling module ({short_hash})")
164            }
165            ModuleInput::Command(cmd) => format!("Compiling {}", cmd.name()),
166        };
167
168        let pb = self.progress.clone();
169
170        let on_progress = Some(ModuleLoadProgressReporter::new({
171            let base_msg = progress_msg.clone();
172            move |prog| {
173                let msg = match prog {
174                    ModuleLoadProgress::CompilingModule(c) => {
175                        let mut msg = base_msg.clone();
176                        if let (Some(step), Some(step_count)) =
177                            (c.phase_step(), c.phase_step_count())
178                        {
179                            pb.set_length(step_count);
180                            pb.set_position(step);
181                            // Note: writing to strings can not fail.
182                            if step_count != 0 {
183                                write!(
184                                    &mut msg,
185                                    " ({:.0}%)",
186                                    100.0 * step as f32 / step_count as f32
187                                )
188                                .unwrap();
189                            }
190                        };
191                        pb.tick();
192
193                        msg
194                    }
195                    _ => base_msg.clone(),
196                };
197
198                pb.set_message(msg);
199                Ok(())
200            }
201        }));
202
203        let engine = engine.cloned();
204
205        let style = indicatif::ProgressStyle::default_bar()
206            .template("{spinner} {wide_bar:.cyan/blue} {msg}")
207            .expect("invalid progress bar template");
208        self.progress.set_style(style);
209
210        self.progress.reset();
211        if self.progress.is_hidden() {
212            self.progress
213                .set_draw_target(indicatif::ProgressDrawTarget::stderr());
214        }
215        self.progress.set_message(progress_msg);
216
217        let f = async move {
218            let res = self
219                .runtime
220                .resolve_module(input, engine.as_ref(), on_progress)
221                .await;
222
223            // Hide the progress bar and reset it to the default spinner style.
224            // Needed because future module downloads should not show a bar.
225            self.progress
226                .set_style(indicatif::ProgressStyle::default_spinner());
227            self.progress.reset();
228            self.progress.finish_and_clear();
229
230            res
231        };
232
233        Box::pin(f)
234    }
235}
236
237#[derive(Debug)]
238struct MonitoringSource {
239    inner: Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync>,
240    progress: ProgressBar,
241}
242
243#[async_trait::async_trait]
244impl wasmer_wasix::runtime::resolver::Source for MonitoringSource {
245    async fn query(&self, package: &PackageSource) -> Result<Vec<PackageSummary>, QueryError> {
246        self.progress.set_message(format!("Looking up {package}"));
247        self.inner.query(package).await
248    }
249}
250
251#[derive(Debug)]
252struct MonitoringPackageLoader {
253    inner: Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync>,
254    progress: ProgressBar,
255}
256
257#[async_trait::async_trait]
258impl wasmer_wasix::runtime::package_loader::PackageLoader for MonitoringPackageLoader {
259    async fn load(&self, summary: &PackageSummary) -> Result<Container, Error> {
260        let pkg_id = summary.package_id();
261        self.progress.set_message(format!("Downloading {pkg_id}"));
262
263        self.inner.load(summary).await
264    }
265
266    async fn load_package_tree(
267        &self,
268        root: &Container,
269        resolution: &wasmer_wasix::runtime::resolver::Resolution,
270        root_is_local_dir: bool,
271    ) -> Result<BinaryPackage, Error> {
272        self.inner
273            .load_package_tree(root, resolution, root_is_local_dir)
274            .await
275    }
276}