Skip to content

Commit 5b3aa0a

Browse files
authored
Merge pull request #2304 from fermyon/spin-load-wasm
Tweak spin-loader to be able to run in Wasm
2 parents 8d67954 + 1b642af commit 5b3aa0a

File tree

5 files changed

+90
-29
lines changed

5 files changed

+90
-29
lines changed

crates/loader/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ lazy_static = "1.4.0"
1717
mime_guess = { version = "2.0" }
1818
outbound-http = { path = "../outbound-http", default-features = false }
1919
spin-outbound-networking = { path = "../outbound-networking" }
20-
path-absolutize = "3.0.11"
20+
path-absolutize = { version = "3.0.11", features = ["use_unix_paths_on_wasm"] }
2121
regex = "1.5.4"
2222
reqwest = "0.11.9"
2323
semver = "1.0"
@@ -31,7 +31,7 @@ spin-manifest = { path = "../manifest" }
3131
tempfile = "3.8.0"
3232
terminal = { path = "../terminal" }
3333
thiserror = "1.0.49"
34-
tokio = { version = "1.23", features = ["full"] }
34+
tokio = "1.23"
3535
tokio-util = "0.6"
3636
toml = "0.8.2"
3737
tracing = { workspace = true }
@@ -41,6 +41,10 @@ walkdir = "2.3.2"
4141
tokio = { version = "1.23", features = ["rt", "macros"] }
4242
ui-testing = { path = "../ui-testing" }
4343

44+
[features]
45+
default = ["async-io"]
46+
async-io = ["tokio/fs"]
47+
4448
[[test]]
4549
name = "ui"
4650
path = "tests/ui.rs"

crates/loader/src/cache.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! Cache for OCI registry entities.
22
33
use anyhow::{ensure, Context, Result};
4-
use tokio::fs;
54

65
use std::path::{Path, PathBuf};
76

7+
use crate::fs::{create_dir_all, write_file};
8+
89
const CONFIG_DIR: &str = "spin";
910
const REGISTRY_CACHE_DIR: &str = "registry";
1011
const MANIFESTS_DIR: &str = "manifests";
@@ -79,13 +80,13 @@ impl Cache {
7980

8081
/// Write the contents in the cache's wasm directory.
8182
pub async fn write_wasm(&self, bytes: impl AsRef<[u8]>, digest: impl AsRef<str>) -> Result<()> {
82-
fs::write(self.wasm_path(digest), bytes.as_ref()).await?;
83+
write_file(&self.wasm_path(digest), bytes.as_ref()).await?;
8384
Ok(())
8485
}
8586

8687
/// Write the contents in the cache's data directory.
8788
pub async fn write_data(&self, bytes: impl AsRef<[u8]>, digest: impl AsRef<str>) -> Result<()> {
88-
fs::write(self.data_path(digest), bytes.as_ref()).await?;
89+
write_file(&self.data_path(digest), bytes.as_ref()).await?;
8990
Ok(())
9091
}
9192

@@ -110,21 +111,21 @@ impl Cache {
110111

111112
let p = root.join(MANIFESTS_DIR);
112113
if !p.is_dir() {
113-
fs::create_dir_all(&p).await.with_context(|| {
114+
create_dir_all(&p).await.with_context(|| {
114115
format!("failed to create manifests directory `{}`", p.display())
115116
})?;
116117
}
117118

118119
let p = root.join(WASM_DIR);
119120
if !p.is_dir() {
120-
fs::create_dir_all(&p)
121+
create_dir_all(&p)
121122
.await
122123
.with_context(|| format!("failed to create wasm directory `{}`", p.display()))?;
123124
}
124125

125126
let p = root.join(DATA_DIR);
126127
if !p.is_dir() {
127-
fs::create_dir_all(&p)
128+
create_dir_all(&p)
128129
.await
129130
.with_context(|| format!("failed to create assets directory `{}`", p.display()))?;
130131
}

crates/loader/src/fs.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use anyhow::Result;
2+
use std::path::Path;
3+
4+
#[cfg(feature = "async-io")]
5+
mod io {
6+
use super::*;
7+
8+
pub async fn write_file(path: &Path, bytes: &[u8]) -> Result<()> {
9+
tokio::fs::write(path, bytes).await?;
10+
Ok(())
11+
}
12+
13+
pub async fn create_dir_all(path: &Path) -> Result<()> {
14+
tokio::fs::create_dir_all(path).await?;
15+
Ok(())
16+
}
17+
18+
pub async fn copy(from: &Path, to: &Path) -> Result<u64> {
19+
tokio::fs::copy(from, to).await.map_err(Into::into)
20+
}
21+
22+
pub async fn metadata(path: &Path) -> Result<std::fs::Metadata> {
23+
tokio::fs::metadata(path).await.map_err(Into::into)
24+
}
25+
}
26+
27+
#[cfg(not(feature = "async-io"))]
28+
mod io {
29+
use super::*;
30+
31+
pub async fn write_file(path: &Path, bytes: &[u8]) -> Result<()> {
32+
std::fs::write(path, bytes)?;
33+
Ok(())
34+
}
35+
36+
pub async fn create_dir_all(path: &Path) -> Result<()> {
37+
std::fs::create_dir_all(path)?;
38+
Ok(())
39+
}
40+
41+
pub async fn copy(from: &Path, to: &Path) -> Result<u64> {
42+
Ok(std::fs::copy(from, to)?)
43+
}
44+
45+
pub async fn metadata(path: &Path) -> Result<std::fs::Metadata> {
46+
Ok(std::fs::metadata(path)?)
47+
}
48+
}
49+
50+
pub use io::*;

crates/loader/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212

1313
use std::path::{Path, PathBuf};
1414

15-
use anyhow::Result;
15+
use anyhow::{Context, Result};
1616
use local::LocalLoader;
1717
use spin_common::paths::parent_dir;
1818
use spin_locked_app::locked::LockedApp;
1919

2020
pub mod cache;
21+
mod fs;
22+
#[cfg(feature = "async-io")]
2123
mod http;
2224
mod local;
2325

@@ -33,7 +35,7 @@ pub async fn from_file(
3335
cache_root: Option<PathBuf>,
3436
) -> Result<LockedApp> {
3537
let path = manifest_path.as_ref();
36-
let app_root = parent_dir(path)?;
38+
let app_root = parent_dir(path).context("manifest path has no parent directory")?;
3739
let loader = LocalLoader::new(&app_root, files_mount_strategy, cache_root).await?;
3840
loader.load_file(path).await
3941
}

crates/loader/src/local.rs

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ use spin_locked_app::{
1212
values::{ValuesMap, ValuesMapBuilder},
1313
};
1414
use spin_manifest::schema::v2::{self, AppManifest, KebabId, WasiFilesMount};
15-
use tokio::{fs, sync::Semaphore};
15+
use tokio::sync::Semaphore;
1616

17-
use crate::{cache::Cache, http::verified_download, FilesMountStrategy};
17+
use crate::{cache::Cache, FilesMountStrategy};
1818

1919
#[derive(Debug)]
2020
pub struct LocalLoader {
@@ -291,7 +291,7 @@ impl LocalLoader {
291291
exclude_files: &[String],
292292
) -> Result<()> {
293293
let src_path = self.app_root.join(src);
294-
let meta = fs::metadata(&src_path)
294+
let meta = crate::fs::metadata(&src_path)
295295
.await
296296
.with_context(|| format!("invalid file mount source {}", quoted_path(src)))?;
297297
if meta.is_dir() {
@@ -366,13 +366,15 @@ impl LocalLoader {
366366

367367
let _loading_permit = self.file_loading_permits.acquire().await?;
368368
let dest_parent = parent_dir(dest)?;
369-
fs::create_dir_all(&dest_parent).await.with_context(|| {
370-
format!(
371-
"Failed to create parent directory {}",
372-
quoted_path(&dest_parent)
373-
)
374-
})?;
375-
fs::copy(src, dest).await.with_context(|| {
369+
crate::fs::create_dir_all(&dest_parent)
370+
.await
371+
.with_context(|| {
372+
format!(
373+
"Failed to create parent directory {}",
374+
quoted_path(&dest_parent)
375+
)
376+
})?;
377+
crate::fs::copy(src, dest).await.with_context(|| {
376378
format!(
377379
"Failed to copy {} to {}",
378380
quoted_path(src),
@@ -404,15 +406,18 @@ impl LocalLoader {
404406
}
405407
}
406408

407-
/// This canonicalizes the path in a way that works with globs. On non-Windows
408-
/// platforms, we can use Path::canonicalize, but on Windows platforms this
409-
/// expands to a UNC path, and the glob library does not work with UNC paths.
410-
#[cfg(not(windows))]
411-
fn safe_canonicalize(path: &Path) -> std::io::Result<PathBuf> {
412-
path.canonicalize()
409+
#[cfg(feature = "async-io")]
410+
async fn verified_download(url: &str, digest: &str, dest: &Path) -> Result<()> {
411+
crate::http::verified_download(url, digest, dest)
412+
.await
413+
.with_context(|| format!("Error fetching source URL {url:?}"))
414+
}
415+
416+
#[cfg(not(feature = "async-io"))]
417+
async fn verified_download(_url: &str, _digest: &str, _dest: &Path) -> Result<()> {
418+
panic!("async-io feature is required for downloading Wasm sources")
413419
}
414420

415-
#[cfg(windows)]
416421
fn safe_canonicalize(path: &Path) -> std::io::Result<PathBuf> {
417422
use path_absolutize::Absolutize;
418423
Ok(path.absolutize()?.into_owned())
@@ -515,8 +520,7 @@ fn file_content_ref(path: impl AsRef<Path>) -> Result<ContentRef> {
515520

516521
fn file_url(path: impl AsRef<Path>) -> Result<String> {
517522
let path = path.as_ref();
518-
let abs_path = path
519-
.canonicalize()
523+
let abs_path = safe_canonicalize(path)
520524
.with_context(|| format!("Couldn't resolve `{}`", path.display()))?;
521525
Ok(Url::from_file_path(abs_path).unwrap().to_string())
522526
}

0 commit comments

Comments
 (0)