use std::sync::Arc;
use anyhow::Context;
use derivative::*;
use once_cell::sync::OnceCell;
use semver::Version;
use virtual_fs::FileSystem;
use webc::{compat::SharedBytes, Container};
use crate::{
runtime::{
module_cache::ModuleHash,
resolver::{PackageId, PackageInfo, PackageSpecifier, ResolveError},
},
Runtime,
};
#[derive(Derivative, Clone)]
#[derivative(Debug)]
pub struct BinaryPackageCommand {
name: String,
metadata: webc::metadata::Command,
#[derivative(Debug = "ignore")]
pub(crate) atom: SharedBytes,
hash: OnceCell<ModuleHash>,
}
impl BinaryPackageCommand {
pub fn new(name: String, metadata: webc::metadata::Command, atom: SharedBytes) -> Self {
Self {
name,
metadata,
atom,
hash: OnceCell::new(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn metadata(&self) -> &webc::metadata::Command {
&self.metadata
}
pub fn atom(&self) -> &[u8] {
&self.atom
}
pub fn hash(&self) -> &ModuleHash {
self.hash.get_or_init(|| ModuleHash::sha256(self.atom()))
}
}
#[derive(Derivative, Clone)]
#[derivative(Debug)]
pub struct BinaryPackage {
pub package_name: String,
pub when_cached: Option<u128>,
pub entrypoint_cmd: Option<String>,
pub hash: OnceCell<ModuleHash>,
pub webc_fs: Arc<dyn FileSystem + Send + Sync>,
pub commands: Vec<BinaryPackageCommand>,
pub uses: Vec<String>,
pub version: Version,
pub module_memory_footprint: u64,
pub file_system_memory_footprint: u64,
}
impl BinaryPackage {
pub async fn from_webc(container: &Container, rt: &dyn Runtime) -> Result<Self, anyhow::Error> {
let source = rt.source();
let root = PackageInfo::from_manifest(container.manifest())?;
let root_id = PackageId {
package_name: root.name.clone(),
version: root.version.clone(),
};
let resolution = crate::runtime::resolver::resolve(&root_id, &root, &*source).await?;
let pkg = rt
.package_loader()
.load_package_tree(container, &resolution)
.await
.map_err(|e| anyhow::anyhow!(e))?;
Ok(pkg)
}
pub async fn from_registry(
specifier: &PackageSpecifier,
runtime: &dyn Runtime,
) -> Result<Self, anyhow::Error> {
let source = runtime.source();
let root_summary =
source
.latest(specifier)
.await
.map_err(|error| ResolveError::Registry {
package: specifier.clone(),
error,
})?;
let root = runtime.package_loader().load(&root_summary).await?;
let id = root_summary.package_id();
let resolution = crate::runtime::resolver::resolve(&id, &root_summary.pkg, &source)
.await
.context("Dependency resolution failed")?;
let pkg = runtime
.package_loader()
.load_package_tree(&root, &resolution)
.await
.map_err(|e| anyhow::anyhow!(e))?;
Ok(pkg)
}
pub fn get_command(&self, name: &str) -> Option<&BinaryPackageCommand> {
self.commands.iter().find(|cmd| cmd.name() == name)
}
pub fn entrypoint_bytes(&self) -> Option<&[u8]> {
self.entrypoint_cmd
.as_deref()
.and_then(|name| self.get_command(name))
.map(|entry| entry.atom())
}
pub fn hash(&self) -> ModuleHash {
*self.hash.get_or_init(|| {
if let Some(entry) = self.entrypoint_bytes() {
ModuleHash::sha256(entry)
} else {
ModuleHash::sha256(self.package_name.as_bytes())
}
})
}
}
#[cfg(test)]
mod tests {
use std::{collections::BTreeMap, path::Path};
use tempfile::TempDir;
use virtual_fs::AsyncReadExt;
use wapm_targz_to_pirita::{webc::v1::DirOrFile, FileMap, TransformManifestFunctions};
use crate::{runtime::task_manager::VirtualTaskManager, PluggableRuntime};
use super::*;
fn task_manager() -> Arc<dyn VirtualTaskManager + Send + Sync> {
cfg_if::cfg_if! {
if #[cfg(feature = "sys-threads")] {
Arc::new(crate::runtime::task_manager::tokio::TokioTaskManager::new(tokio::runtime::Handle::current()))
} else {
unimplemented!("Unable to get the task manager")
}
}
}
#[tokio::test]
#[cfg_attr(
not(feature = "sys-threads"),
ignore = "The tokio task manager isn't available on this platform"
)]
async fn fs_table_can_map_directories_to_different_names() {
let temp = TempDir::new().unwrap();
let wasmer_toml = r#"
[package]
name = "some/package"
version = "0.0.0"
description = "a dummy package"
[fs]
"/public" = "./out"
"#;
std::fs::write(temp.path().join("wasmer.toml"), wasmer_toml).unwrap();
let out = temp.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let file_txt = "Hello, World!";
std::fs::write(out.join("file.txt"), file_txt).unwrap();
let webc = construct_webc_in_memory(temp.path());
let webc = Container::from_bytes(webc).unwrap();
let tasks = task_manager();
let runtime = PluggableRuntime::new(tasks);
let pkg = BinaryPackage::from_webc(&webc, &runtime).await.unwrap();
let mut f = pkg
.webc_fs
.new_open_options()
.read(true)
.open("/public/file.txt")
.unwrap();
let mut buffer = String::new();
f.read_to_string(&mut buffer).await.unwrap();
assert_eq!(buffer, file_txt);
}
fn construct_webc_in_memory(dir: &Path) -> Vec<u8> {
let mut files = BTreeMap::new();
load_files_from_disk(&mut files, dir, dir);
let wasmer_toml = DirOrFile::File("wasmer.toml".into());
if let Some(toml_data) = files.remove(&wasmer_toml) {
files
.entry(DirOrFile::File("wapm.toml".into()))
.or_insert(toml_data);
}
let functions = TransformManifestFunctions::default();
wapm_targz_to_pirita::generate_webc_file(files, dir, &functions).unwrap()
}
fn load_files_from_disk(files: &mut FileMap, dir: &Path, base: &Path) {
let entries = dir.read_dir().unwrap();
for entry in entries {
let path = entry.unwrap().path();
let relative_path = path.strip_prefix(base).unwrap().to_path_buf();
if path.is_dir() {
load_files_from_disk(files, &path, base);
files.insert(DirOrFile::Dir(relative_path), Vec::new());
} else if path.is_file() {
let data = std::fs::read(&path).unwrap();
files.insert(DirOrFile::File(relative_path), data);
}
}
}
}