Skip to content

Commit 6d068a4

Browse files
committed
Use Till's lockfile
Signed-off-by: itowlson <[email protected]>
1 parent 2a3a213 commit 6d068a4

File tree

4 files changed

+98
-73
lines changed

4 files changed

+98
-73
lines changed

crates/build/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub async fn build(
3232
})?;
3333
let app_dir = parent_dir(manifest_file)?;
3434

35-
let build_result = build_components(component_ids, build_info.components(), app_dir);
35+
let build_result = build_components(component_ids, build_info.components(), &app_dir);
3636

3737
// Emit any required warnings now, so that they don't bury any errors.
3838
if let Some(e) = build_info.load_error() {
@@ -69,6 +69,7 @@ pub async fn build(
6969
&application,
7070
build_info.deployment_targets(),
7171
cache_root.clone(),
72+
&app_dir,
7273
)
7374
.await?;
7475

@@ -87,7 +88,7 @@ pub async fn build(
8788
fn build_components(
8889
component_ids: &[String],
8990
components: Vec<ComponentBuildInfo>,
90-
app_dir: PathBuf,
91+
app_dir: &Path,
9192
) -> Result<(), anyhow::Error> {
9293
let components_to_build = if component_ids.is_empty() {
9394
components
@@ -117,7 +118,7 @@ fn build_components(
117118

118119
components_to_build
119120
.into_iter()
120-
.map(|c| build_component(c, &app_dir))
121+
.map(|c| build_component(c, app_dir))
121122
.collect::<Result<Vec<_>, _>>()?;
122123

123124
terminal::step!("Finished", "building all Spin components");

crates/environments/src/environment_definition.rs

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,117 @@
1-
use std::path::Path;
1+
use std::{collections::HashMap, path::Path};
22

33
use anyhow::Context;
4+
use futures::future::try_join_all;
45
use spin_common::ui::quoted_path;
56
use spin_manifest::schema::v2::TargetEnvironmentRef;
67

78
const DEFAULT_REGISTRY: &str = "fermyon.com";
89

9-
/// Loads the given `TargetEnvironment` from a registry.
10-
pub async fn load_environment(
10+
/// Serialisation format for the lockfile: registry -> { name -> digest }
11+
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
12+
struct TargetEnvironmentLockfile(HashMap<String, HashMap<String, String>>);
13+
14+
impl TargetEnvironmentLockfile {
15+
fn digest(&self, registry: &str, env_id: &str) -> Option<&str> {
16+
self.0
17+
.get(registry)
18+
.and_then(|m| m.get(env_id))
19+
.map(|s| s.as_str())
20+
}
21+
22+
fn set_digest(&mut self, registry: &str, env_id: &str, digest: &str) {
23+
match self.0.get_mut(registry) {
24+
Some(map) => {
25+
map.insert(env_id.to_string(), digest.to_string());
26+
}
27+
None => {
28+
let map = vec![(env_id.to_string(), digest.to_string())]
29+
.into_iter()
30+
.collect();
31+
self.0.insert(registry.to_string(), map);
32+
}
33+
}
34+
}
35+
}
36+
37+
/// Load all the listed environments from their registries or paths.
38+
/// Registry data will be cached, with a lockfile under `.spin` mapping
39+
/// environment IDs to digests (to allow cache lookup without needing
40+
/// to fetch the digest from the registry).
41+
pub async fn load_environments(
42+
env_ids: &[TargetEnvironmentRef],
43+
cache_root: Option<std::path::PathBuf>,
44+
app_dir: &std::path::Path,
45+
) -> anyhow::Result<Vec<TargetEnvironment>> {
46+
if env_ids.is_empty() {
47+
return Ok(Default::default());
48+
}
49+
50+
let cache = spin_loader::cache::Cache::new(cache_root)
51+
.await
52+
.context("Unable to create cache")?;
53+
let lockfile_dir = app_dir.join(".spin");
54+
let lockfile_path = lockfile_dir.join("target-environments.lock");
55+
56+
let orig_lockfile: TargetEnvironmentLockfile = tokio::fs::read_to_string(&lockfile_path)
57+
.await
58+
.ok()
59+
.and_then(|s| serde_json::from_str(&s).ok())
60+
.unwrap_or_default();
61+
let lockfile = std::sync::Arc::new(tokio::sync::RwLock::new(orig_lockfile.clone()));
62+
63+
let envs = try_join_all(
64+
env_ids
65+
.iter()
66+
.map(|e| load_environment(e, &cache, &lockfile)),
67+
)
68+
.await?;
69+
70+
let final_lockfile = &*lockfile.read().await;
71+
if *final_lockfile != orig_lockfile {
72+
if let Ok(lockfile_json) = serde_json::to_string_pretty(&final_lockfile) {
73+
_ = tokio::fs::create_dir_all(lockfile_dir).await;
74+
_ = tokio::fs::write(&lockfile_path, lockfile_json).await; // failure to update lockfile is not an error
75+
}
76+
}
77+
78+
Ok(envs)
79+
}
80+
81+
/// Loads the given `TargetEnvironment` from a registry or directory.
82+
async fn load_environment(
1183
env_id: &TargetEnvironmentRef,
1284
cache: &spin_loader::cache::Cache,
85+
lockfile: &std::sync::Arc<tokio::sync::RwLock<TargetEnvironmentLockfile>>,
1386
) -> anyhow::Result<TargetEnvironment> {
1487
match env_id {
1588
TargetEnvironmentRef::DefaultRegistry(package) => {
16-
load_environment_from_registry(DEFAULT_REGISTRY, package, cache).await
89+
load_environment_from_registry(DEFAULT_REGISTRY, package, cache, lockfile).await
1790
}
1891
TargetEnvironmentRef::Registry { registry, package } => {
19-
load_environment_from_registry(registry, package, cache).await
92+
load_environment_from_registry(registry, package, cache, lockfile).await
2093
}
2194
TargetEnvironmentRef::WitDirectory { path } => load_environment_from_dir(path),
2295
}
2396
}
2497

98+
/// Loads the given `TargetEnvironment` from the given registry, or
99+
/// from cache if available. If the environment is not in cache, the
100+
/// encoded WIT will be cached, and the in-memory lockfile object
101+
/// updated.
25102
async fn load_environment_from_registry(
26103
registry: &str,
27104
env_id: &str,
28105
cache: &spin_loader::cache::Cache,
106+
lockfile: &std::sync::Arc<tokio::sync::RwLock<TargetEnvironmentLockfile>>,
29107
) -> anyhow::Result<TargetEnvironment> {
30108
use futures_util::TryStreamExt;
31109

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);
110+
if let Some(digest) = lockfile.read().await.digest(registry, env_id) {
111+
if let Ok(cache_file) = cache.wasm_file(digest) {
112+
if let Ok(bytes) = tokio::fs::read(&cache_file).await {
113+
return TargetEnvironment::from_package_bytes(env_id, bytes);
114+
}
37115
}
38116
}
39117

@@ -76,7 +154,9 @@ async fn load_environment_from_registry(
76154
.with_context(|| format!("Failed to get {env_id} package data from registry"))?
77155
.to_vec();
78156

79-
_ = cache.write_wit(&bytes, registry, env_id).await; // Failure to cache is not fatal
157+
let digest = release.content_digest.to_string();
158+
_ = cache.write_wasm(&bytes, &digest).await; // Failure to cache is not fatal
159+
lockfile.write().await.set_digest(registry, env_id, &digest);
80160

81161
TargetEnvironment::from_package_bytes(env_id, bytes)
82162
}

crates/environments/src/lib.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ use anyhow::{anyhow, Context};
33
mod environment_definition;
44
mod loader;
55

6-
use environment_definition::{load_environment, TargetEnvironment, TriggerType};
7-
use futures::future::try_join_all;
6+
use environment_definition::{load_environments, TargetEnvironment, TriggerType};
87
pub use loader::ApplicationToValidate;
98
use loader::ComponentToValidate;
109
use spin_manifest::schema::v2::TargetEnvironmentRef;
@@ -13,16 +12,13 @@ pub async fn validate_application_against_environment_ids(
1312
application: &ApplicationToValidate,
1413
env_ids: &[TargetEnvironmentRef],
1514
cache_root: Option<std::path::PathBuf>,
15+
app_dir: &std::path::Path,
1616
) -> anyhow::Result<Vec<anyhow::Error>> {
1717
if env_ids.is_empty() {
1818
return Ok(Default::default());
1919
}
2020

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?;
21+
let envs = load_environments(env_ids, cache_root, app_dir).await?;
2622
validate_application_against_environments(application, &envs).await
2723
}
2824

crates/loader/src/cache.rs

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ 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";
1817

1918
/// Cache for registry entities.
2019
#[derive(Debug)]
@@ -58,13 +57,6 @@ impl Cache {
5857
self.root.join(DATA_DIR)
5958
}
6059

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-
6860
/// Return the path to a wasm file given its digest.
6961
pub fn wasm_file(&self, digest: impl AsRef<str>) -> Result<PathBuf> {
7062
// Check the expected wasm directory first; else check the data directory as a fallback.
@@ -108,18 +100,6 @@ impl Cache {
108100
Ok(())
109101
}
110102

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-
123103
/// The path of contents in the cache's wasm directory, which may or may not exist.
124104
pub fn wasm_path(&self, digest: impl AsRef<str>) -> PathBuf {
125105
self.wasm_dir().join(safe_name(digest).as_ref())
@@ -130,24 +110,12 @@ impl Cache {
130110
self.data_dir().join(safe_name(digest).as_ref())
131111
}
132112

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-
144113
/// Ensure the expected configuration directories are found in the root.
145114
/// └── <configuration-root>
146115
/// └── registry
147116
/// └──manifests
148117
/// └──wasm
149118
/// └─-data
150-
/// └─-wit
151119
pub async fn ensure_dirs(&self) -> Result<()> {
152120
tracing::debug!("using cache root directory {}", self.root.display());
153121

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

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-
190151
self.dirs_ensured_once.store(true, Ordering::Relaxed);
191152

192153
Ok(())
193154
}
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-
}
207155
}
208156

209157
#[cfg(windows)]

0 commit comments

Comments
 (0)