Skip to content

Commit 2a3a213

Browse files
committed
Cache environment definitions
Signed-off-by: itowlson <[email protected]>
1 parent 76cb8af commit 2a3a213

File tree

8 files changed

+94
-15
lines changed

8 files changed

+94
-15
lines changed

crates/build/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub async fn build(
2020
manifest_file: &Path,
2121
component_ids: &[String],
2222
skip_target_checks: bool,
23+
cache_root: Option<PathBuf>,
2324
) -> Result<()> {
2425
let build_info = component_build_configs(manifest_file)
2526
.await
@@ -67,6 +68,7 @@ pub async fn build(
6768
let errors = spin_environments::validate_application_against_environment_ids(
6869
&application,
6970
build_info.deployment_targets(),
71+
cache_root.clone(),
7072
)
7173
.await?;
7274

@@ -193,6 +195,6 @@ mod tests {
193195
#[tokio::test]
194196
async fn can_load_even_if_trigger_invalid() {
195197
let bad_trigger_file = test_data_root().join("bad_trigger.toml");
196-
build(&bad_trigger_file, &[], true).await.unwrap();
198+
build(&bad_trigger_file, &[], true, None).await.unwrap();
197199
}
198200
}

crates/environments/src/environment_definition.rs

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ use spin_manifest::schema::v2::TargetEnvironmentRef;
77
const DEFAULT_REGISTRY: &str = "fermyon.com";
88

99
/// Loads the given `TargetEnvironment` from a registry.
10-
pub async fn load_environment(env_id: &TargetEnvironmentRef) -> anyhow::Result<TargetEnvironment> {
10+
pub async fn load_environment(
11+
env_id: &TargetEnvironmentRef,
12+
cache: &spin_loader::cache::Cache,
13+
) -> anyhow::Result<TargetEnvironment> {
1114
match env_id {
1215
TargetEnvironmentRef::DefaultRegistry(package) => {
13-
load_environment_from_registry(DEFAULT_REGISTRY, package).await
16+
load_environment_from_registry(DEFAULT_REGISTRY, package, cache).await
1417
}
1518
TargetEnvironmentRef::Registry { registry, package } => {
16-
load_environment_from_registry(registry, package).await
19+
load_environment_from_registry(registry, package, cache).await
1720
}
1821
TargetEnvironmentRef::WitDirectory { path } => load_environment_from_dir(path),
1922
}
@@ -22,23 +25,32 @@ pub async fn load_environment(env_id: &TargetEnvironmentRef) -> anyhow::Result<T
2225
async fn load_environment_from_registry(
2326
registry: &str,
2427
env_id: &str,
28+
cache: &spin_loader::cache::Cache,
2529
) -> anyhow::Result<TargetEnvironment> {
2630
use futures_util::TryStreamExt;
2731

32+
let cache_file = cache.wit_path(registry, env_id);
33+
if cache.wit_path(registry, env_id).exists() {
34+
// Failure to read from cache is not fatal - fall through to origin.
35+
if let Ok(bytes) = tokio::fs::read(cache_file).await {
36+
return TargetEnvironment::from_package_bytes(env_id, bytes);
37+
}
38+
}
39+
2840
let (pkg_name, pkg_ver) = env_id.split_once('@').with_context(|| format!("Failed to parse target environment {env_id} as package reference - is the target correct?"))?;
2941
let env_pkg_ref: wasm_pkg_loader::PackageRef = pkg_name
3042
.parse()
3143
.with_context(|| format!("Environment {pkg_name} is not a valid package name"))?;
3244

33-
let registry: wasm_pkg_loader::Registry = registry
45+
let wkg_registry: wasm_pkg_loader::Registry = registry
3446
.parse()
3547
.with_context(|| format!("Registry {registry} is not a valid registry name"))?;
3648

3749
// TODO: this requires wkg configuration which shouldn't be on users:
3850
// is there a better way to handle it?
3951
let mut wkg_config = wasm_pkg_loader::Config::global_defaults()
4052
.unwrap_or_else(|_| wasm_pkg_loader::Config::empty());
41-
wkg_config.set_package_registry_override(env_pkg_ref, registry);
53+
wkg_config.set_package_registry_override(env_pkg_ref, wkg_registry);
4254

4355
let mut client = wasm_pkg_loader::Client::new(wkg_config);
4456

@@ -64,7 +76,9 @@ async fn load_environment_from_registry(
6476
.with_context(|| format!("Failed to get {env_id} package data from registry"))?
6577
.to_vec();
6678

67-
TargetEnvironment::from_package_bytes(env_id.to_owned(), bytes)
79+
_ = cache.write_wit(&bytes, registry, env_id).await; // Failure to cache is not fatal
80+
81+
TargetEnvironment::from_package_bytes(env_id, bytes)
6882
}
6983

7084
fn load_environment_from_dir(path: &Path) -> anyhow::Result<TargetEnvironment> {
@@ -93,7 +107,7 @@ pub struct TargetEnvironment {
93107
}
94108

95109
impl TargetEnvironment {
96-
fn from_package_bytes(name: String, bytes: Vec<u8>) -> anyhow::Result<Self> {
110+
fn from_package_bytes(name: &str, bytes: Vec<u8>) -> anyhow::Result<Self> {
97111
let decoded = wit_component::decode(&bytes)
98112
.with_context(|| format!("Failed to decode package for environment {name}"))?;
99113
let package_id = decoded.package();
@@ -107,7 +121,7 @@ impl TargetEnvironment {
107121
.clone();
108122

109123
Ok(Self {
110-
name,
124+
name: name.to_owned(),
111125
decoded,
112126
package,
113127
package_id,

crates/environments/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@ use spin_manifest::schema::v2::TargetEnvironmentRef;
1212
pub async fn validate_application_against_environment_ids(
1313
application: &ApplicationToValidate,
1414
env_ids: &[TargetEnvironmentRef],
15+
cache_root: Option<std::path::PathBuf>,
1516
) -> anyhow::Result<Vec<anyhow::Error>> {
1617
if env_ids.is_empty() {
1718
return Ok(Default::default());
1819
}
1920

20-
let envs = try_join_all(env_ids.iter().map(load_environment)).await?;
21+
let cache = spin_loader::cache::Cache::new(cache_root)
22+
.await
23+
.context("Unable to create cache")?;
24+
25+
let envs = try_join_all(env_ids.iter().map(|e| load_environment(e, &cache))).await?;
2126
validate_application_against_environments(application, &envs).await
2227
}
2328

crates/loader/src/cache.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const REGISTRY_CACHE_DIR: &str = "registry";
1414
const MANIFESTS_DIR: &str = "manifests";
1515
const WASM_DIR: &str = "wasm";
1616
const DATA_DIR: &str = "data";
17+
const WIT_DIR: &str = "wit";
1718

1819
/// Cache for registry entities.
1920
#[derive(Debug)]
@@ -57,6 +58,13 @@ impl Cache {
5758
self.root.join(DATA_DIR)
5859
}
5960

61+
/// The (Wasm-encoded) WIT directory for the current cache.
62+
/// This does not reuse `wasm_dir` because it cannot be addressed
63+
/// by digest.
64+
fn wit_dir(&self) -> PathBuf {
65+
self.root.join(WIT_DIR)
66+
}
67+
6068
/// Return the path to a wasm file given its digest.
6169
pub fn wasm_file(&self, digest: impl AsRef<str>) -> Result<PathBuf> {
6270
// Check the expected wasm directory first; else check the data directory as a fallback.
@@ -100,6 +108,18 @@ impl Cache {
100108
Ok(())
101109
}
102110

111+
/// Write the contents in the cache's wit directory.
112+
pub async fn write_wit(
113+
&self,
114+
bytes: impl AsRef<[u8]>,
115+
registry: impl AsRef<str>,
116+
name: impl AsRef<str>,
117+
) -> Result<()> {
118+
self.ensure_wit_dir(registry.as_ref()).await?;
119+
write_file(&self.wit_path(registry, name), bytes.as_ref()).await?;
120+
Ok(())
121+
}
122+
103123
/// The path of contents in the cache's wasm directory, which may or may not exist.
104124
pub fn wasm_path(&self, digest: impl AsRef<str>) -> PathBuf {
105125
self.wasm_dir().join(safe_name(digest).as_ref())
@@ -110,12 +130,24 @@ impl Cache {
110130
self.data_dir().join(safe_name(digest).as_ref())
111131
}
112132

133+
/// The path of contents in the cache's wit directory, which may or may not exist.
134+
/// This is keyed by registry and package name, not by digest, because digest
135+
/// lookups slow down the `build` command and obviate the value of the cache:
136+
/// therefore, it assumes that WITs are immutable once published, and that users
137+
/// will manage the situation when this assumption is invalid.
138+
pub fn wit_path(&self, registry: impl AsRef<str>, name: impl AsRef<str>) -> PathBuf {
139+
self.wit_dir()
140+
.join(safe_name(registry).as_ref())
141+
.join(safe_name(name).as_ref())
142+
}
143+
113144
/// Ensure the expected configuration directories are found in the root.
114145
/// └── <configuration-root>
115146
/// └── registry
116147
/// └──manifests
117148
/// └──wasm
118149
/// └─-data
150+
/// └─-wit
119151
pub async fn ensure_dirs(&self) -> Result<()> {
120152
tracing::debug!("using cache root directory {}", self.root.display());
121153

@@ -148,10 +180,30 @@ impl Cache {
148180
.with_context(|| format!("failed to create assets directory `{}`", p.display()))?;
149181
}
150182

183+
let p = root.join(WIT_DIR);
184+
if !p.is_dir() {
185+
create_dir_all(&p)
186+
.await
187+
.with_context(|| format!("failed to create wit directory `{}`", p.display()))?;
188+
}
189+
151190
self.dirs_ensured_once.store(true, Ordering::Relaxed);
152191

153192
Ok(())
154193
}
194+
195+
async fn ensure_wit_dir(&self, registry: impl AsRef<str>) -> Result<()> {
196+
self.ensure_dirs().await?;
197+
198+
let p = self.wit_dir().join(safe_name(registry).as_ref());
199+
if !p.is_dir() {
200+
create_dir_all(&p)
201+
.await
202+
.with_context(|| format!("failed to create wit directory `{}`", p.display()))?;
203+
}
204+
205+
Ok(())
206+
}
155207
}
156208

157209
#[cfg(windows)]

src/commands/build.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ impl BuildCommand {
5353
spin_common::paths::find_manifest_file_path(self.app_source.as_ref())?;
5454
notify_if_nondefault_rel(&manifest_file, distance);
5555

56-
spin_build::build(&manifest_file, &self.component_id, self.skip_target_checks).await?;
56+
spin_build::build(
57+
&manifest_file,
58+
&self.component_id,
59+
self.skip_target_checks,
60+
None,
61+
)
62+
.await?;
5763

5864
if self.up {
5965
let mut cmd = UpCommand::parse_from(

src/commands/registry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl Push {
7575
notify_if_nondefault_rel(&app_file, distance);
7676

7777
if self.build {
78-
spin_build::build(&app_file, &[], false).await?;
78+
spin_build::build(&app_file, &[], false, self.cache_dir.clone()).await?;
7979
}
8080

8181
let annotations = if self.annotations.is_empty() {

src/commands/up.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ impl UpCommand {
191191
}
192192

193193
if self.build {
194-
app_source.build().await?;
194+
app_source.build(&self.cache_dir).await?;
195195
}
196196
let mut locked_app = self
197197
.load_resolved_app_source(resolved_app_source, &working_dir)

src/commands/up/app_source.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ impl AppSource {
5656
}
5757
}
5858

59-
pub async fn build(&self) -> anyhow::Result<()> {
59+
pub async fn build(&self, cache_root: &Option<PathBuf>) -> anyhow::Result<()> {
6060
match self {
61-
Self::File(path) => spin_build::build(path, &[], false).await,
61+
Self::File(path) => spin_build::build(path, &[], false, cache_root.clone()).await,
6262
_ => Ok(()),
6363
}
6464
}

0 commit comments

Comments
 (0)