Copy spin-core and spin-app crates from prototype
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
This commit is contained in:
parent
0de1d494a4
commit
216a5f7a23
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.17.0"
|
||||
|
@ -37,6 +43,12 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aliasable"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
|
||||
|
||||
[[package]]
|
||||
name = "ambient-authority"
|
||||
version = "0.0.1"
|
||||
|
@ -2183,6 +2195,29 @@ version = "6.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbb50b356159620db6ac971c6d5c9ab788c9cc38a6f49619fca2a27acb062ca"
|
||||
dependencies = [
|
||||
"aliasable",
|
||||
"ouroboros_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros_macro"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a0d9d1a6191c4f391f87219d1ea42b23f09ee84d64763cd05ee6ea88d9f384d"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "outbound-http"
|
||||
version = "0.2.0"
|
||||
|
@ -3268,6 +3303,19 @@ dependencies = [
|
|||
"wit-bindgen-wasmtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin-app"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"ouroboros",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"spin-core",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin-build"
|
||||
version = "0.2.0"
|
||||
|
@ -3349,6 +3397,19 @@ dependencies = [
|
|||
"wit-bindgen-wasmtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"tracing",
|
||||
"wasi-cap-std-sync",
|
||||
"wasi-common",
|
||||
"wasmtime",
|
||||
"wasmtime-wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin-engine"
|
||||
version = "0.2.0"
|
||||
|
@ -4312,6 +4373,24 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi-tokio"
|
||||
version = "0.39.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab325bba31ae9286b8ebdc18d32a43d6471312c9bc4e477240be444e00ec4f4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cap-std 0.25.2",
|
||||
"io-extras 0.15.0",
|
||||
"io-lifetimes 0.7.3",
|
||||
"lazy_static",
|
||||
"rustix 0.35.9",
|
||||
"tokio",
|
||||
"wasi-cap-std-sync",
|
||||
"wasi-common",
|
||||
"wiggle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.82"
|
||||
|
@ -4590,6 +4669,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"wasi-cap-std-sync",
|
||||
"wasi-common",
|
||||
"wasi-tokio",
|
||||
"wasmtime",
|
||||
"wiggle",
|
||||
]
|
||||
|
@ -4677,6 +4757,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"wasmtime",
|
||||
"wiggle-macro",
|
||||
"witx",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -71,8 +71,10 @@ e2e-tests = []
|
|||
[workspace]
|
||||
members = [
|
||||
"crates/abi-conformance",
|
||||
"crates/app",
|
||||
"crates/build",
|
||||
"crates/config",
|
||||
"crates/core",
|
||||
"crates/engine",
|
||||
"crates/http",
|
||||
"crates/loader",
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "spin-app"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
async-trait = "0.1"
|
||||
ouroboros = "0.15"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
spin-core = { path = "../core" }
|
||||
thiserror = "1.0"
|
|
@ -0,0 +1,51 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use spin_core::{EngineBuilder, HostComponent, HostComponentsData};
|
||||
|
||||
use crate::AppComponent;
|
||||
|
||||
pub trait DynamicHostComponent: HostComponent {
|
||||
fn update_data(&self, data: &mut Self::Data, component: &AppComponent) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
impl<DHC: DynamicHostComponent> DynamicHostComponent for Arc<DHC> {
|
||||
fn update_data(&self, data: &mut Self::Data, component: &AppComponent) -> anyhow::Result<()> {
|
||||
(**self).update_data(data, component)
|
||||
}
|
||||
}
|
||||
|
||||
type DataUpdater =
|
||||
Box<dyn Fn(&mut HostComponentsData, &AppComponent) -> anyhow::Result<()> + Send + Sync>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DynamicHostComponents {
|
||||
data_updaters: Vec<DataUpdater>,
|
||||
}
|
||||
|
||||
impl DynamicHostComponents {
|
||||
pub fn add_dynamic_host_component<T: Send + Sync, DHC: DynamicHostComponent>(
|
||||
&mut self,
|
||||
engine_builder: &mut EngineBuilder<T>,
|
||||
host_component: DHC,
|
||||
) -> anyhow::Result<()> {
|
||||
let host_component = Arc::new(host_component);
|
||||
let handle = engine_builder.add_host_component(host_component.clone())?;
|
||||
self.data_updaters
|
||||
.push(Box::new(move |host_components_data, component| {
|
||||
let data = host_components_data.get_or_insert(handle);
|
||||
host_component.update_data(data, component)
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_data(
|
||||
&self,
|
||||
host_components_data: &mut HostComponentsData,
|
||||
component: &AppComponent,
|
||||
) -> anyhow::Result<()> {
|
||||
for data_updater in &self.data_updaters {
|
||||
data_updater(host_components_data, component)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,268 @@
|
|||
mod host_component;
|
||||
pub mod locked;
|
||||
pub mod values;
|
||||
|
||||
use ouroboros::self_referencing;
|
||||
use serde::Deserialize;
|
||||
use spin_core::{wasmtime, Engine, EngineBuilder, StoreBuilder};
|
||||
|
||||
use host_component::DynamicHostComponents;
|
||||
use locked::{ContentPath, LockedApp, LockedComponent, LockedComponentSource, LockedTrigger};
|
||||
|
||||
pub use async_trait::async_trait;
|
||||
pub use host_component::DynamicHostComponent;
|
||||
pub use locked::Variable;
|
||||
|
||||
// TODO(lann): Should this migrate to spin-loader?
|
||||
#[async_trait]
|
||||
pub trait Loader {
|
||||
async fn load_app(&self, uri: &str) -> anyhow::Result<LockedApp>;
|
||||
|
||||
async fn load_module(
|
||||
&self,
|
||||
engine: &wasmtime::Engine,
|
||||
source: &LockedComponentSource,
|
||||
) -> anyhow::Result<spin_core::Module>;
|
||||
|
||||
async fn mount_files(
|
||||
&self,
|
||||
store_builder: &mut StoreBuilder,
|
||||
component: &AppComponent,
|
||||
) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
pub struct AppLoader {
|
||||
inner: Box<dyn Loader + Send + Sync>,
|
||||
dynamic_host_components: DynamicHostComponents,
|
||||
}
|
||||
|
||||
impl AppLoader {
|
||||
pub fn new(loader: impl Loader + Send + Sync + 'static) -> Self {
|
||||
Self {
|
||||
inner: Box::new(loader),
|
||||
dynamic_host_components: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_dynamic_host_component<T: Send + Sync, DHC: DynamicHostComponent>(
|
||||
&mut self,
|
||||
engine_builder: &mut EngineBuilder<T>,
|
||||
host_component: DHC,
|
||||
) -> anyhow::Result<()> {
|
||||
self.dynamic_host_components
|
||||
.add_dynamic_host_component(engine_builder, host_component)
|
||||
}
|
||||
|
||||
pub async fn load_app(&self, uri: String) -> Result<App> {
|
||||
let locked = self
|
||||
.inner
|
||||
.load_app(&uri)
|
||||
.await
|
||||
.map_err(Error::LoaderError)?;
|
||||
Ok(App {
|
||||
loader: self,
|
||||
uri,
|
||||
locked,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn load_owned_app(self, uri: String) -> Result<OwnedApp> {
|
||||
OwnedApp::try_new_async(self, |loader| Box::pin(loader.load_app(uri))).await
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AppLoader {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AppLoader").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[self_referencing]
|
||||
#[derive(Debug)]
|
||||
pub struct OwnedApp {
|
||||
loader: AppLoader,
|
||||
|
||||
#[borrows(loader)]
|
||||
#[covariant]
|
||||
app: App<'this>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for OwnedApp {
|
||||
type Target = App<'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe {
|
||||
// We know that App's lifetime param is for AppLoader, which is owned by self here.
|
||||
std::mem::transmute::<&App, &App<'static>>(self.borrow_app())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct App<'a> {
|
||||
loader: &'a AppLoader,
|
||||
uri: String,
|
||||
locked: LockedApp,
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
pub fn uri(&self) -> &str {
|
||||
&self.uri
|
||||
}
|
||||
|
||||
pub fn get_metadata<'this, T: Deserialize<'this>>(&'this self, key: &str) -> Option<Result<T>> {
|
||||
self.locked
|
||||
.metadata
|
||||
.get(key)
|
||||
.map(|value| Ok(T::deserialize(value)?))
|
||||
}
|
||||
|
||||
pub fn require_metadata<'this, T: Deserialize<'this>>(&'this self, key: &str) -> Result<T> {
|
||||
self.get_metadata(key)
|
||||
.ok_or_else(|| Error::ManifestError(format!("missing required {key:?}")))?
|
||||
}
|
||||
|
||||
pub fn variables(&self) -> impl Iterator<Item = (&String, &Variable)> {
|
||||
self.locked.variables.iter()
|
||||
}
|
||||
|
||||
pub fn components(&self) -> impl Iterator<Item = AppComponent> {
|
||||
self.locked
|
||||
.components
|
||||
.iter()
|
||||
.map(|locked| AppComponent { app: self, locked })
|
||||
}
|
||||
|
||||
pub fn get_component(&self, component_id: &str) -> Option<AppComponent> {
|
||||
self.components()
|
||||
.find(|component| component.locked.id == component_id)
|
||||
}
|
||||
|
||||
pub fn triggers(&self) -> impl Iterator<Item = AppTrigger> {
|
||||
self.locked
|
||||
.triggers
|
||||
.iter()
|
||||
.map(|locked| AppTrigger { app: self, locked })
|
||||
}
|
||||
|
||||
pub fn triggers_with_type(&'a self, trigger_type: &'a str) -> impl Iterator<Item = AppTrigger> {
|
||||
self.triggers()
|
||||
.filter(move |trigger| trigger.locked.trigger_type == trigger_type)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppComponent<'a> {
|
||||
pub app: &'a App<'a>,
|
||||
locked: &'a LockedComponent,
|
||||
}
|
||||
|
||||
impl<'a> AppComponent<'a> {
|
||||
pub fn id(&self) -> &str {
|
||||
&self.locked.id
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &LockedComponentSource {
|
||||
&self.locked.source
|
||||
}
|
||||
|
||||
pub fn files(&self) -> std::slice::Iter<ContentPath> {
|
||||
self.locked.files.iter()
|
||||
}
|
||||
|
||||
pub fn get_metadata<T: Deserialize<'a>>(&self, key: &str) -> Option<Result<T>> {
|
||||
self.locked
|
||||
.metadata
|
||||
.get(key)
|
||||
.map(|value| Ok(T::deserialize(value)?))
|
||||
}
|
||||
|
||||
pub fn config(&self) -> impl Iterator<Item = (&String, &String)> {
|
||||
self.locked.config.iter()
|
||||
}
|
||||
|
||||
pub async fn load_module<T: Send + Sync>(
|
||||
&self,
|
||||
engine: &Engine<T>,
|
||||
) -> Result<spin_core::Module> {
|
||||
self.app
|
||||
.loader
|
||||
.inner
|
||||
.load_module(engine.as_ref(), &self.locked.source)
|
||||
.await
|
||||
.map_err(Error::LoaderError)
|
||||
}
|
||||
|
||||
pub async fn apply_store_config(&self, builder: &mut StoreBuilder) -> Result<()> {
|
||||
builder.env(&self.locked.env).map_err(Error::CoreError)?;
|
||||
|
||||
let loader = self.app.loader;
|
||||
loader
|
||||
.inner
|
||||
.mount_files(builder, self)
|
||||
.await
|
||||
.map_err(Error::LoaderError)?;
|
||||
|
||||
loader
|
||||
.dynamic_host_components
|
||||
.update_data(builder.host_components_data(), self)
|
||||
.map_err(Error::HostComponentError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppTrigger<'a> {
|
||||
pub app: &'a App<'a>,
|
||||
locked: &'a LockedTrigger,
|
||||
}
|
||||
|
||||
impl<'a> AppTrigger<'a> {
|
||||
pub fn id(&self) -> &str {
|
||||
&self.locked.id
|
||||
}
|
||||
|
||||
pub fn trigger_type(&self) -> &str {
|
||||
&self.locked.trigger_type
|
||||
}
|
||||
|
||||
pub fn component(&self) -> Result<AppComponent<'a>> {
|
||||
let component_id = self.locked.trigger_config.get("component").ok_or_else(|| {
|
||||
Error::ManifestError(format!(
|
||||
"trigger {:?} missing 'component' config field",
|
||||
self.locked.id
|
||||
))
|
||||
})?;
|
||||
let component_id = component_id.as_str().ok_or_else(|| {
|
||||
Error::ManifestError(format!(
|
||||
"trigger {:?} 'component' field has unexpected value {:?}",
|
||||
self.locked.id, component_id
|
||||
))
|
||||
})?;
|
||||
self.app.get_component(component_id).ok_or_else(|| {
|
||||
Error::ManifestError(format!(
|
||||
"missing component {:?} configured for trigger {:?}",
|
||||
component_id, self.locked.id
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn typed_config<Config: Deserialize<'a>>(&self) -> Result<Config> {
|
||||
Ok(Config::deserialize(&self.locked.trigger_config)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("spin core error: {0}")]
|
||||
CoreError(anyhow::Error),
|
||||
#[error("host component error: {0}")]
|
||||
HostComponentError(anyhow::Error),
|
||||
#[error("loader error: {0}")]
|
||||
LoaderError(anyhow::Error),
|
||||
#[error("manifest error: {0}")]
|
||||
ManifestError(String),
|
||||
#[error("json error: {0}")]
|
||||
JsonError(#[from] serde_json::Error),
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::values::ValuesMap;
|
||||
|
||||
// LockedMap gives deterministic encoding, which we want.
|
||||
pub type LockedMap<T> = std::collections::BTreeMap<String, T>;
|
||||
|
||||
/// A LockedApp represents a "fully resolved" Spin application.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct LockedApp {
|
||||
/// Locked schema version
|
||||
pub spin_lock_version: FixedVersion<0>,
|
||||
/// Application metadata
|
||||
#[serde(default, skip_serializing_if = "ValuesMap::is_empty")]
|
||||
pub metadata: ValuesMap,
|
||||
/// Custom config variables
|
||||
#[serde(default, skip_serializing_if = "LockedMap::is_empty")]
|
||||
pub variables: LockedMap<Variable>,
|
||||
/// Application triggers
|
||||
pub triggers: Vec<LockedTrigger>,
|
||||
/// Application components
|
||||
pub components: Vec<LockedComponent>,
|
||||
}
|
||||
|
||||
impl LockedApp {
|
||||
pub fn from_json(contents: &[u8]) -> serde_json::Result<Self> {
|
||||
serde_json::from_slice(contents)
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> serde_json::Result<Vec<u8>> {
|
||||
serde_json::to_vec_pretty(&self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A LockedComponent represents a "fully resolved" Spin component.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct LockedComponent {
|
||||
/// Application-unique component identifier
|
||||
pub id: String,
|
||||
/// Component metadata
|
||||
#[serde(default, skip_serializing_if = "ValuesMap::is_empty")]
|
||||
pub metadata: ValuesMap,
|
||||
/// Wasm source
|
||||
pub source: LockedComponentSource,
|
||||
/// WASI environment variables
|
||||
#[serde(default, skip_serializing_if = "LockedMap::is_empty")]
|
||||
pub env: LockedMap<String>,
|
||||
/// WASI filesystem contents
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub files: Vec<ContentPath>,
|
||||
/// Custom config values
|
||||
#[serde(default, skip_serializing_if = "LockedMap::is_empty")]
|
||||
pub config: LockedMap<String>,
|
||||
}
|
||||
|
||||
/// A LockedComponentSource specifies a Wasm source.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct LockedComponentSource {
|
||||
/// Wasm source content type (e.g. "application/wasm")
|
||||
pub content_type: String,
|
||||
/// Wasm source content specification
|
||||
#[serde(flatten)]
|
||||
pub content: ContentRef,
|
||||
}
|
||||
|
||||
/// A ContentPath specifies content mapped to a WASI path.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ContentPath {
|
||||
/// Content specification
|
||||
#[serde(flatten)]
|
||||
pub content: ContentRef,
|
||||
/// WASI mount path
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
/// A ContentRef represents content used by an application.
|
||||
///
|
||||
/// At least one of `source` or `digest` must be specified. Implementations may
|
||||
/// require one or the other (or both).
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct ContentRef {
|
||||
/// A URI where the content can be accessed. Implementations may support
|
||||
/// different URI schemes.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<String>,
|
||||
/// If set, the content must have the given SHA-256 digest.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub digest: Option<String>,
|
||||
}
|
||||
|
||||
/// A LockedTrigger specifies configuration for an application trigger.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct LockedTrigger {
|
||||
/// Application-unique trigger identifier
|
||||
pub id: String,
|
||||
/// Trigger type (e.g. "http")
|
||||
pub trigger_type: String,
|
||||
/// Trigger-type-specific configuration
|
||||
pub trigger_config: Value,
|
||||
}
|
||||
|
||||
/// A Variable specifies a custom configuration variable.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Variable {
|
||||
/// The variable's default value. If unset, the variable is required.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub default: Option<String>,
|
||||
/// If set, the variable's value may be sensitive and e.g. shouldn't be logged.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub secret: bool,
|
||||
}
|
||||
|
||||
/// FixedVersion represents a schema version field with a const value.
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(into = "usize", try_from = "usize")]
|
||||
pub struct FixedVersion<const V: usize>;
|
||||
|
||||
impl<const V: usize> From<FixedVersion<V>> for usize {
|
||||
fn from(_: FixedVersion<V>) -> usize {
|
||||
V
|
||||
}
|
||||
}
|
||||
|
||||
impl<const V: usize> From<FixedVersion<V>> for String {
|
||||
fn from(_: FixedVersion<V>) -> String {
|
||||
V.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const V: usize> TryFrom<usize> for FixedVersion<V> {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
if value != V {
|
||||
return Err(format!("invalid version {} != {}", value, V));
|
||||
}
|
||||
Ok(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const V: usize> TryFrom<String> for FixedVersion<V> {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
let value: usize = value
|
||||
.parse()
|
||||
.map_err(|err| format!("invalid version: {}", err))?;
|
||||
value.try_into()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
|
||||
// ValuesMap stores dynamically-typed values.
|
||||
pub type ValuesMap = serde_json::Map<String, Value>;
|
||||
|
||||
/// ValuesMapBuilder assists in building a ValuesMap.
|
||||
#[derive(Default)]
|
||||
pub struct ValuesMapBuilder(ValuesMap);
|
||||
|
||||
impl ValuesMapBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn string(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
|
||||
self.entry(key, value.into())
|
||||
}
|
||||
|
||||
pub fn string_option(
|
||||
&mut self,
|
||||
key: impl Into<String>,
|
||||
value: Option<impl Into<String>>,
|
||||
) -> &mut Self {
|
||||
if let Some(value) = value {
|
||||
self.0.insert(key.into(), value.into().into());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn string_array<T: Into<String>>(
|
||||
&mut self,
|
||||
key: impl Into<String>,
|
||||
iter: impl IntoIterator<Item = T>,
|
||||
) -> &mut Self {
|
||||
self.entry(key, iter.into_iter().map(|s| s.into()).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
pub fn entry(&mut self, key: impl Into<String>, value: impl Into<Value>) -> &mut Self {
|
||||
self.0.insert(key.into(), value.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn serializable(
|
||||
&mut self,
|
||||
key: impl Into<String>,
|
||||
value: impl Serialize,
|
||||
) -> serde_json::Result<&mut Self> {
|
||||
let value = serde_json::to_value(value)?;
|
||||
self.0.insert(key.into(), value);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> ValuesMap {
|
||||
std::mem::take(&mut self.0)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "spin-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
tracing = "0.1"
|
||||
async-trait = "0.1"
|
||||
wasi-cap-std-sync = "0.39"
|
||||
wasi-common = "0.39"
|
||||
wasmtime = "0.39"
|
||||
wasmtime-wasi = { version = "0.39", features = ["tokio"] }
|
|
@ -0,0 +1,129 @@
|
|||
use std::{any::Any, marker::PhantomData, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use super::{Data, Linker};
|
||||
|
||||
pub trait HostComponent: Send + Sync + 'static {
|
||||
/// Host component runtime data.
|
||||
type Data: Send + Sized + 'static;
|
||||
|
||||
/// Add this component to the given Linker, using the given runtime state-getting handle.
|
||||
// This function signature mirrors those generated by wit-bindgen.
|
||||
fn add_to_linker<T: Send>(
|
||||
linker: &mut Linker<T>,
|
||||
get: impl Fn(&mut Data<T>) -> &mut Self::Data + Send + Sync + Copy + 'static,
|
||||
) -> Result<()>;
|
||||
|
||||
fn build_data(&self) -> Self::Data;
|
||||
}
|
||||
|
||||
impl<HC: HostComponent> HostComponent for Arc<HC> {
|
||||
type Data = HC::Data;
|
||||
|
||||
fn add_to_linker<T: Send>(
|
||||
linker: &mut Linker<T>,
|
||||
get: impl Fn(&mut Data<T>) -> &mut Self::Data + Send + Sync + Copy + 'static,
|
||||
) -> Result<()> {
|
||||
HC::add_to_linker(linker, get)
|
||||
}
|
||||
|
||||
fn build_data(&self) -> Self::Data {
|
||||
(**self).build_data()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HostComponentDataHandle<HC: HostComponent> {
|
||||
idx: usize,
|
||||
_phantom: PhantomData<fn() -> HC::Data>,
|
||||
}
|
||||
|
||||
impl<HC: HostComponent> Copy for HostComponentDataHandle<HC> {}
|
||||
|
||||
impl<HC: HostComponent> Clone for HostComponentDataHandle<HC> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
idx: self.idx,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DataBuilder = Box<dyn Fn() -> Box<dyn Any + Send> + Send + Sync>;
|
||||
|
||||
pub struct HostComponentsBuilder {
|
||||
data_builders: Vec<DataBuilder>,
|
||||
}
|
||||
|
||||
impl HostComponentsBuilder {
|
||||
pub fn add_host_component<T: Send, HC: HostComponent + 'static>(
|
||||
&mut self,
|
||||
linker: &mut Linker<T>,
|
||||
host_component: HC,
|
||||
) -> Result<HostComponentDataHandle<HC>> {
|
||||
let idx = self.data_builders.len();
|
||||
self.data_builders
|
||||
.push(Box::new(move || Box::new(host_component.build_data())));
|
||||
HC::add_to_linker(linker, move |data| {
|
||||
data.host_components_data
|
||||
.get_or_insert_idx(idx)
|
||||
.downcast_mut()
|
||||
.unwrap()
|
||||
})?;
|
||||
Ok(HostComponentDataHandle::<HC> {
|
||||
idx,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build(self) -> HostComponents {
|
||||
let data_builders = Arc::new(self.data_builders);
|
||||
HostComponents { data_builders }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HostComponents {
|
||||
data_builders: Arc<Vec<DataBuilder>>,
|
||||
}
|
||||
|
||||
impl HostComponents {
|
||||
pub fn builder() -> HostComponentsBuilder {
|
||||
HostComponentsBuilder {
|
||||
data_builders: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_data(&self) -> HostComponentsData {
|
||||
// Fill with `None`
|
||||
let data = std::iter::repeat_with(Default::default)
|
||||
.take(self.data_builders.len())
|
||||
.collect();
|
||||
HostComponentsData {
|
||||
data,
|
||||
data_builders: self.data_builders.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HostComponentsData {
|
||||
data: Vec<Option<Box<dyn Any + Send>>>,
|
||||
data_builders: Arc<Vec<DataBuilder>>,
|
||||
}
|
||||
|
||||
impl HostComponentsData {
|
||||
pub fn get_or_insert<HC: HostComponent>(
|
||||
&mut self,
|
||||
handle: HostComponentDataHandle<HC>,
|
||||
) -> &mut HC::Data {
|
||||
let x = self.get_or_insert_idx(handle.idx);
|
||||
x.downcast_mut().unwrap()
|
||||
}
|
||||
|
||||
fn get_or_insert_idx(&mut self, idx: usize) -> &mut Box<dyn Any + Send> {
|
||||
self.data[idx].get_or_insert_with(|| self.data_builders[idx]())
|
||||
}
|
||||
|
||||
pub fn set<HC: HostComponent>(&mut self, handle: HostComponentDataHandle<HC>, data: HC::Data) {
|
||||
self.data[handle.idx] = Some(Box::new(data));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use wasi_common::pipe::WritePipe;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OutputBuffer(Arc<RwLock<Vec<u8>>>);
|
||||
|
||||
impl OutputBuffer {
|
||||
pub fn take(&mut self) -> Vec<u8> {
|
||||
std::mem::take(&mut *self.0.write().unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn writer(&self) -> WritePipe<Vec<u8>> {
|
||||
WritePipe::from_shared(self.0.clone())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
mod host_component;
|
||||
mod io;
|
||||
mod limits;
|
||||
mod store;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::instrument;
|
||||
use wasmtime_wasi::WasiCtx;
|
||||
|
||||
pub use wasmtime::{self, Instance, Module};
|
||||
|
||||
use self::host_component::{HostComponents, HostComponentsBuilder};
|
||||
|
||||
pub use host_component::{HostComponent, HostComponentDataHandle, HostComponentsData};
|
||||
pub use store::{Store, StoreBuilder};
|
||||
|
||||
pub struct Config {
|
||||
inner: wasmtime::Config,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Borrow the inner wasmtime::Config mutably.
|
||||
/// WARNING: This is inherently unstable and may break at any time!
|
||||
#[doc(hidden)]
|
||||
pub fn wasmtime_config(&mut self) -> &mut wasmtime::Config {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
let mut inner = wasmtime::Config::new();
|
||||
inner.async_support(true);
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Data<T> {
|
||||
inner: T,
|
||||
wasi: WasiCtx,
|
||||
host_components_data: HostComponentsData,
|
||||
store_limits: limits::StoreLimitsAsync,
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Data<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for Data<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub type Linker<T> = wasmtime::Linker<Data<T>>;
|
||||
|
||||
pub struct EngineBuilder<T> {
|
||||
engine: wasmtime::Engine,
|
||||
linker: Linker<T>,
|
||||
host_components_builder: HostComponentsBuilder,
|
||||
}
|
||||
|
||||
impl<T: Send + Sync> EngineBuilder<T> {
|
||||
fn new(config: &Config) -> Result<Self> {
|
||||
let engine = wasmtime::Engine::new(&config.inner)?;
|
||||
|
||||
let mut linker: Linker<T> = Linker::new(&engine);
|
||||
wasmtime_wasi::tokio::add_to_linker(&mut linker, |data| &mut data.wasi)?;
|
||||
|
||||
Ok(Self {
|
||||
engine,
|
||||
linker,
|
||||
host_components_builder: HostComponents::builder(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn link_import(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Linker<T>, fn(&mut Data<T>) -> &mut T) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
f(&mut self.linker, Data::as_mut)
|
||||
}
|
||||
|
||||
pub fn add_host_component<HC: HostComponent + Send + Sync + 'static>(
|
||||
&mut self,
|
||||
host_component: HC,
|
||||
) -> Result<HostComponentDataHandle<HC>> {
|
||||
self.host_components_builder
|
||||
.add_host_component(&mut self.linker, host_component)
|
||||
}
|
||||
|
||||
pub fn build_with_data(self, instance_pre_data: T) -> Engine<T> {
|
||||
let host_components = self.host_components_builder.build();
|
||||
|
||||
let instance_pre_store = Arc::new(Mutex::new(
|
||||
StoreBuilder::new(self.engine.clone(), &host_components)
|
||||
.build_with_data(instance_pre_data)
|
||||
.expect("instance_pre_store build should not fail"),
|
||||
));
|
||||
|
||||
Engine {
|
||||
inner: self.engine,
|
||||
linker: self.linker,
|
||||
host_components,
|
||||
instance_pre_store,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Send + Sync> EngineBuilder<T> {
|
||||
pub fn build(self) -> Engine<T> {
|
||||
self.build_with_data(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Engine<T> {
|
||||
inner: wasmtime::Engine,
|
||||
linker: Linker<T>,
|
||||
host_components: HostComponents,
|
||||
instance_pre_store: Arc<Mutex<Store<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Send + Sync> Engine<T> {
|
||||
pub fn builder(config: &Config) -> Result<EngineBuilder<T>> {
|
||||
EngineBuilder::new(config)
|
||||
}
|
||||
|
||||
pub fn store_builder(&self) -> StoreBuilder {
|
||||
StoreBuilder::new(self.inner.clone(), &self.host_components)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub fn instantiate_pre(&self, module: &Module) -> Result<InstancePre<T>> {
|
||||
let mut store = self.instance_pre_store.lock().unwrap();
|
||||
let inner = self.linker.instantiate_pre(&mut *store, module)?;
|
||||
Ok(InstancePre { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<wasmtime::Engine> for Engine<T> {
|
||||
fn as_ref(&self) -> &wasmtime::Engine {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InstancePre<T> {
|
||||
inner: wasmtime::InstancePre<Data<T>>,
|
||||
}
|
||||
|
||||
impl<T: Send + Sync> InstancePre<T> {
|
||||
#[instrument(skip_all)]
|
||||
pub async fn instantiate_async(&self, store: &mut Store<T>) -> Result<Instance> {
|
||||
self.inner.instantiate_async(store).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for InstancePre<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<wasmtime::InstancePre<Data<T>>> for InstancePre<T> {
|
||||
fn as_ref(&self) -> &wasmtime::InstancePre<Data<T>> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
use async_trait::async_trait;
|
||||
use wasmtime::ResourceLimiterAsync;
|
||||
|
||||
/// Async implementation of wasmtime's `StoreLimits`: https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasmtime/src/limits.rs
|
||||
/// Used to limit the memory use and table size of each Instance
|
||||
#[derive(Default)]
|
||||
pub struct StoreLimitsAsync {
|
||||
max_memory_size: Option<usize>,
|
||||
max_table_elements: Option<u32>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ResourceLimiterAsync for StoreLimitsAsync {
|
||||
async fn memory_growing(
|
||||
&mut self,
|
||||
_current: usize,
|
||||
desired: usize,
|
||||
_maximum: Option<usize>,
|
||||
) -> bool {
|
||||
!matches!(self.max_memory_size, Some(limit) if desired > limit)
|
||||
}
|
||||
|
||||
async fn table_growing(&mut self, _current: u32, desired: u32, _maximum: Option<u32>) -> bool {
|
||||
!matches!(self.max_table_elements, Some(limit) if desired > limit)
|
||||
}
|
||||
}
|
||||
|
||||
impl StoreLimitsAsync {
|
||||
pub fn new(max_memory_size: Option<usize>, max_table_elements: Option<u32>) -> Self {
|
||||
Self {
|
||||
max_memory_size,
|
||||
max_table_elements,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use wasi_cap_std_sync::{ambient_authority, Dir};
|
||||
use wasi_common::{dir::DirCaps, pipe::WritePipe, WasiFile};
|
||||
use wasi_common::{file::FileCaps, pipe::ReadPipe};
|
||||
use wasmtime_wasi::tokio::WasiCtxBuilder;
|
||||
|
||||
use crate::io::OutputBuffer;
|
||||
|
||||
use super::{
|
||||
host_component::{HostComponents, HostComponentsData},
|
||||
limits::StoreLimitsAsync,
|
||||
Data,
|
||||
};
|
||||
|
||||
pub struct Store<T> {
|
||||
inner: wasmtime::Store<Data<T>>,
|
||||
}
|
||||
|
||||
impl<T> Store<T> {
|
||||
pub fn host_components_data(&mut self) -> &mut HostComponentsData {
|
||||
&mut self.inner.data_mut().host_components_data
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<wasmtime::Store<Data<T>>> for Store<T> {
|
||||
fn as_ref(&self) -> &wasmtime::Store<Data<T>> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<wasmtime::Store<Data<T>>> for Store<T> {
|
||||
fn as_mut(&mut self) -> &mut wasmtime::Store<Data<T>> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> wasmtime::AsContext for Store<T> {
|
||||
type Data = Data<T>;
|
||||
|
||||
fn as_context(&self) -> wasmtime::StoreContext<'_, Self::Data> {
|
||||
self.inner.as_context()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> wasmtime::AsContextMut for Store<T> {
|
||||
fn as_context_mut(&mut self) -> wasmtime::StoreContextMut<'_, Self::Data> {
|
||||
self.inner.as_context_mut()
|
||||
}
|
||||
}
|
||||
|
||||
// WASI expects preopened dirs in FDs starting at 3 (0-2 are stdio).
|
||||
const WASI_FIRST_PREOPENED_DIR_FD: u32 = 3;
|
||||
|
||||
const READ_ONLY_DIR_CAPS: DirCaps = DirCaps::from_bits_truncate(
|
||||
DirCaps::OPEN.bits()
|
||||
| DirCaps::READDIR.bits()
|
||||
| DirCaps::READLINK.bits()
|
||||
| DirCaps::PATH_FILESTAT_GET.bits()
|
||||
| DirCaps::FILESTAT_GET.bits(),
|
||||
);
|
||||
const READ_ONLY_FILE_CAPS: FileCaps = FileCaps::from_bits_truncate(
|
||||
FileCaps::READ.bits()
|
||||
| FileCaps::SEEK.bits()
|
||||
| FileCaps::TELL.bits()
|
||||
| FileCaps::FILESTAT_GET.bits()
|
||||
| FileCaps::POLL_READWRITE.bits(),
|
||||
);
|
||||
|
||||
pub struct StoreBuilder {
|
||||
engine: wasmtime::Engine,
|
||||
wasi: std::result::Result<Option<WasiCtxBuilder>, String>,
|
||||
read_only_preopened_dirs: Vec<(Dir, PathBuf)>,
|
||||
host_components_data: HostComponentsData,
|
||||
store_limits: StoreLimitsAsync,
|
||||
}
|
||||
|
||||
impl StoreBuilder {
|
||||
pub(crate) fn new(engine: wasmtime::Engine, host_components: &HostComponents) -> Self {
|
||||
Self {
|
||||
engine,
|
||||
wasi: Ok(Some(WasiCtxBuilder::new())),
|
||||
read_only_preopened_dirs: Vec::new(),
|
||||
host_components_data: host_components.new_data(),
|
||||
store_limits: StoreLimitsAsync::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_memory_size(&mut self, max_memory_size: usize) {
|
||||
self.store_limits = StoreLimitsAsync::new(Some(max_memory_size), None);
|
||||
}
|
||||
|
||||
pub fn inherit_stdio(&mut self) {
|
||||
self.with_wasi(|wasi| wasi.inherit_stdio());
|
||||
}
|
||||
|
||||
pub fn stdin(&mut self, file: impl WasiFile + 'static) {
|
||||
self.with_wasi(|wasi| wasi.stdin(Box::new(file)))
|
||||
}
|
||||
|
||||
pub fn stdin_pipe(&mut self, r: impl Read + Send + Sync + 'static) {
|
||||
self.stdin(ReadPipe::new(r))
|
||||
}
|
||||
|
||||
pub fn stdout(&mut self, file: impl WasiFile + 'static) {
|
||||
self.with_wasi(|wasi| wasi.stdout(Box::new(file)))
|
||||
}
|
||||
|
||||
pub fn stdout_pipe(&mut self, w: impl Write + Send + Sync + 'static) {
|
||||
self.stdout(WritePipe::new(w))
|
||||
}
|
||||
|
||||
pub fn stdout_buffered(&mut self) -> OutputBuffer {
|
||||
let buffer = OutputBuffer::default();
|
||||
self.stdout(buffer.writer());
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn stderr(&mut self, file: impl WasiFile + 'static) {
|
||||
self.with_wasi(|wasi| wasi.stderr(Box::new(file)))
|
||||
}
|
||||
|
||||
pub fn stderr_pipe(&mut self, w: impl Write + Send + Sync + 'static) {
|
||||
self.stderr(WritePipe::new(w))
|
||||
}
|
||||
|
||||
pub fn stderr_buffered(&mut self) -> OutputBuffer {
|
||||
let buffer = OutputBuffer::default();
|
||||
self.stderr(buffer.writer());
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn args<'b>(&mut self, args: impl IntoIterator<Item = &'b str>) -> Result<()> {
|
||||
self.try_with_wasi(|mut wasi| {
|
||||
for arg in args {
|
||||
wasi = wasi.arg(arg)?;
|
||||
}
|
||||
Ok(wasi)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn env(
|
||||
&mut self,
|
||||
vars: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
|
||||
) -> Result<()> {
|
||||
self.try_with_wasi(|mut wasi| {
|
||||
for (k, v) in vars {
|
||||
wasi = wasi.env(k.as_ref(), v.as_ref())?;
|
||||
}
|
||||
Ok(wasi)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_only_preopened_dir(
|
||||
&mut self,
|
||||
host_path: impl AsRef<Path>,
|
||||
guest_path: PathBuf,
|
||||
) -> Result<()> {
|
||||
// WasiCtxBuilder::preopened_dir doesn't let you set capabilities, so we need
|
||||
// to wait and call WasiCtx::insert_dir after building the WasiCtx.
|
||||
let dir = wasmtime_wasi::Dir::open_ambient_dir(host_path, ambient_authority())?;
|
||||
self.read_only_preopened_dirs.push((dir, guest_path));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_write_preopened_dir(
|
||||
&mut self,
|
||||
host_path: impl AsRef<Path>,
|
||||
guest_path: PathBuf,
|
||||
) -> Result<()> {
|
||||
let dir = wasmtime_wasi::Dir::open_ambient_dir(host_path, ambient_authority())?;
|
||||
self.try_with_wasi(|wasi| wasi.preopened_dir(dir, guest_path))
|
||||
}
|
||||
|
||||
pub fn host_components_data(&mut self) -> &mut HostComponentsData {
|
||||
&mut self.host_components_data
|
||||
}
|
||||
|
||||
pub fn build_with_data<T>(self, inner_data: T) -> Result<Store<T>> {
|
||||
let mut wasi = self.wasi.map_err(anyhow::Error::msg)?.unwrap().build();
|
||||
|
||||
// Insert any read-only preopened dirs
|
||||
for (idx, (dir, path)) in self.read_only_preopened_dirs.into_iter().enumerate() {
|
||||
let fd = WASI_FIRST_PREOPENED_DIR_FD + idx as u32;
|
||||
let dir = Box::new(wasmtime_wasi::tokio::Dir::from_cap_std(dir));
|
||||
wasi.insert_dir(fd, dir, READ_ONLY_DIR_CAPS, READ_ONLY_FILE_CAPS, path);
|
||||
}
|
||||
|
||||
let mut inner = wasmtime::Store::new(
|
||||
&self.engine,
|
||||
Data {
|
||||
inner: inner_data,
|
||||
wasi,
|
||||
host_components_data: self.host_components_data,
|
||||
store_limits: self.store_limits,
|
||||
},
|
||||
);
|
||||
inner.limiter_async(move |data| &mut data.store_limits);
|
||||
Ok(Store { inner })
|
||||
}
|
||||
|
||||
pub fn build<T: Default>(self) -> Result<Store<T>> {
|
||||
self.build_with_data(T::default())
|
||||
}
|
||||
|
||||
fn with_wasi(&mut self, f: impl FnOnce(WasiCtxBuilder) -> WasiCtxBuilder) {
|
||||
let _ = self.try_with_wasi(|wasi| Ok(f(wasi)));
|
||||
}
|
||||
|
||||
fn try_with_wasi(
|
||||
&mut self,
|
||||
f: impl FnOnce(WasiCtxBuilder) -> Result<WasiCtxBuilder>,
|
||||
) -> Result<()> {
|
||||
let wasi = self
|
||||
.wasi
|
||||
.as_mut()
|
||||
.map_err(|err| anyhow!("StoreBuilder already failed: {}", err))?
|
||||
.take()
|
||||
.unwrap();
|
||||
match f(wasi) {
|
||||
Ok(wasi) => {
|
||||
self.wasi = Ok(Some(wasi));
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
self.wasi = Err(err.to_string());
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue