Skip to content

Commit 24cb7c8

Browse files
authored
[packages] Make manifest and dependency digests mandatory in lock files (#13487)
## Description This is a follow-up to MystenLabs/sui#12575 where making manifest and dependency digests mandatory was left for future work (MystenLabs/sui#12575 (comment)) One issue here is that now external resolvers must also output content for both of these fields (though digests can actually be empty). ## Test Plan All existing tests must pass after the mandatory field adjustment. --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [ ] protocol change - [x] user-visible impact - [ ] breaking change for a client SDKs - [ ] breaking change for FNs (FN binary must upgrade) - [ ] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes Manifest file digest and dependency digest fields in Move.lock files are are now mandatory.
1 parent 9fda3e3 commit 24cb7c8

File tree

81 files changed

+329
-349
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+329
-349
lines changed

Cargo.lock

Lines changed: 24 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/move-package/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,17 +163,21 @@ impl BuildConfig {
163163
let lock_string = std::fs::read_to_string(path.join(SourcePackageLayout::Lock.path())).ok();
164164
let _mutx = PackageLock::lock(); // held until function returns
165165

166-
let mut dep_graph_builder =
167-
DependencyGraphBuilder::new(self.skip_fetch_latest_git_deps, writer);
166+
let install_dir = self.install_dir.as_ref().unwrap_or(&path).to_owned();
167+
168+
let mut dep_graph_builder = DependencyGraphBuilder::new(
169+
self.skip_fetch_latest_git_deps,
170+
writer,
171+
install_dir.clone(),
172+
);
168173
let (dependency_graph, modified) = dep_graph_builder.get_graph(
169174
&DependencyKind::default(),
170-
path.clone(),
175+
path,
171176
manifest_string,
172177
lock_string,
173178
)?;
174179

175180
if modified {
176-
let install_dir = self.install_dir.as_ref().unwrap_or(&path).to_owned();
177181
let lock = dependency_graph.write_to_lock(install_dir)?;
178182
if let Some(lock_path) = &self.lock_file {
179183
lock.commit(lock_path)?;

tools/move-package/src/lock_file/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ impl LockFile {
3838
/// of a move package).
3939
pub fn new(
4040
install_dir: PathBuf,
41-
manifest_digest: Option<String>,
42-
deps_digest: Option<String>,
41+
manifest_digest: String,
42+
deps_digest: String,
4343
) -> Result<LockFile> {
4444
let mut locks_dir = install_dir;
4545
locks_dir.extend([

tools/move-package/src/lock_file/schema.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,14 @@ pub struct Dependency {
6363
#[derive(Serialize, Deserialize)]
6464
pub struct Header {
6565
pub version: u64,
66-
/// A hash of the manifest file content this lock file was generated from (if any) computed
67-
/// using SHA-256 hashing algorithm.
68-
pub manifest_digest: Option<String>,
69-
/// A hash of all the dependencies (their lock file content) this lock file depends on (if any),
70-
/// computed by first hashing all lock files using SHA-256 hashing algorithm and then combining
71-
/// them into a single digest using SHA-256 hasher (similarly to the package digest is computed)
72-
pub deps_digest: Option<String>,
66+
/// A hash of the manifest file content this lock file was generated from computed using SHA-256
67+
/// hashing algorithm.
68+
pub manifest_digest: String,
69+
/// A hash of all the dependencies (their lock file content) this lock file depends on, computed
70+
/// by first hashing all lock files using SHA-256 hashing algorithm and then combining them into
71+
/// a single digest using SHA-256 hasher (similarly to the package digest is computed). If there
72+
/// are no dependencies, it's an empty string.
73+
pub deps_digest: String,
7374
}
7475

7576
#[derive(Serialize, Deserialize)]
@@ -115,8 +116,8 @@ pub fn read_header(contents: &String) -> Result<Header> {
115116
/// Write the initial part of the lock file.
116117
pub(crate) fn write_prologue(
117118
file: &mut NamedTempFile,
118-
manifest_digest: Option<String>,
119-
deps_digest: Option<String>,
119+
manifest_digest: String,
120+
deps_digest: String,
120121
) -> Result<()> {
121122
writeln!(
122123
file,

tools/move-package/src/resolution/dependency_graph.rs

Lines changed: 77 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ pub struct DependencyGraph {
7979
/// `DependencyMode::Always` edges in `package_graph`).
8080
pub always_deps: BTreeSet<PM::PackageName>,
8181

82-
/// A hash of the manifest file content this lock file was generated from, if any.
83-
pub manifest_digest: Option<String>,
84-
/// A hash of all the dependencies (their lock file content) this lock file depends on, if any.
85-
pub deps_digest: Option<String>,
82+
/// A hash of the manifest file content this lock file was generated from.
83+
pub manifest_digest: String,
84+
/// A hash of all the dependencies (their lock file content) this lock file depends on.
85+
pub deps_digest: String,
8686
}
8787

8888
/// A helper to store additional information about a dependency graph
@@ -163,88 +163,52 @@ pub struct DependencyGraphBuilder<Progress: Write> {
163163
pub progress_output: Progress,
164164
/// A chain of visited dependencies used for cycle detection
165165
visited_dependencies: VecDeque<(PM::PackageName, PM::InternalDependency)>,
166+
/// Installation directory for compiled artifacts (from BuildConfig).
167+
install_dir: PathBuf,
166168
}
167169

168170
impl<Progress: Write> DependencyGraphBuilder<Progress> {
169-
pub fn new(skip_fetch_latest_git_deps: bool, progress_output: Progress) -> Self {
171+
pub fn new(
172+
skip_fetch_latest_git_deps: bool,
173+
progress_output: Progress,
174+
install_dir: PathBuf,
175+
) -> Self {
170176
DependencyGraphBuilder {
171177
dependency_cache: DependencyCache::new(skip_fetch_latest_git_deps),
172178
progress_output,
173179
visited_dependencies: VecDeque::new(),
180+
install_dir,
174181
}
175182
}
176183

177-
/// Get a graph from the Move.lock file, if Move.lock file is present and up-to-date
178-
/// (additionally returning false), otherwise compute a new graph based on the content of the
179-
/// Move.toml (manifest) file (additionally returning true).
184+
/// Get a new graph by either reading it from Move.lock file (if this file is up-to-date, in
185+
/// which case also return false) or by computing a new graph based on the content of the
186+
/// Move.toml (manifest) file (in which case also return true).
180187
pub fn get_graph(
181188
&mut self,
182189
parent: &PM::DependencyKind,
183190
root_path: PathBuf,
184191
manifest_string: String,
185-
lock_string: Option<String>,
192+
lock_string_opt: Option<String>,
186193
) -> Result<(DependencyGraph, bool)> {
187194
let toml_manifest = parse_move_manifest_string(manifest_string.clone())?;
188-
let manifest = parse_source_manifest(toml_manifest)?;
195+
let root_manifest = parse_source_manifest(toml_manifest)?;
189196

190197
// compute digests eagerly as even if we can't reuse existing lock file, they need to become
191198
// part of the newly computed dependency graph
192-
let new_manifest_digest_opt = Some(digest_str(manifest_string.into_bytes().as_slice()));
193-
194-
let new_deps_digest_opt = self.dependency_digest(root_path.clone(), &manifest)?;
195-
if let Some(lock_contents) = lock_string {
196-
let schema::Header {
197-
version: _,
198-
manifest_digest: manifest_digest_opt,
199-
deps_digest: deps_digest_opt,
200-
} = schema::read_header(&lock_contents)?;
201-
202-
// check if manifest file and dependencies haven't changed and we can use existing lock
203-
// file to create the dependency graph
204-
if new_manifest_digest_opt == manifest_digest_opt {
205-
// manifest file hasn't changed
206-
if let Some(deps_digest) = deps_digest_opt {
207-
// dependencies digest exists in the lock file
208-
if Some(deps_digest) == new_deps_digest_opt {
209-
// dependencies have not changed
210-
return Ok((
211-
DependencyGraph::read_from_lock(
212-
root_path,
213-
manifest.package.name,
214-
&mut lock_contents.as_bytes(),
215-
None,
216-
)?,
217-
false,
218-
));
219-
}
220-
}
221-
}
222-
}
223-
224-
Ok((
225-
self.new_graph(
226-
parent,
227-
&manifest,
228-
root_path,
229-
new_manifest_digest_opt,
230-
new_deps_digest_opt,
231-
)?,
232-
true,
233-
))
234-
}
199+
let new_manifest_digest = digest_str(manifest_string.into_bytes().as_slice());
200+
let (old_manifest_digest_opt, old_deps_digest_opt, lock_string) = match lock_string_opt {
201+
Some(lock_string) => match schema::read_header(&lock_string) {
202+
Ok(header) => (
203+
Some(header.manifest_digest),
204+
Some(header.deps_digest),
205+
Some(lock_string),
206+
),
207+
Err(_) => (None, None, None), // malformed header - regenerate lock file
208+
},
209+
None => (None, None, None),
210+
};
235211

236-
/// Build a graph from the transitive dependencies and dev-dependencies of `root_package`.
237-
///
238-
/// `progress_output` is an output stream that is written to while generating the graph, to
239-
/// provide human-readable progress updates.
240-
pub fn new_graph(
241-
&mut self,
242-
parent: &PM::DependencyKind,
243-
root_manifest: &PM::SourceManifest,
244-
root_path: PathBuf,
245-
manifest_digest: Option<String>,
246-
deps_digest: Option<String>,
247-
) -> Result<DependencyGraph> {
248212
// collect sub-graphs for "regular" and "dev" dependencies
249213
let mut dep_graphs = self.collect_graphs(
250214
parent,
@@ -253,6 +217,10 @@ impl<Progress: Write> DependencyGraphBuilder<Progress> {
253217
DependencyMode::Always,
254218
&root_manifest.dependencies,
255219
)?;
220+
let dep_lock_files = dep_graphs
221+
.values()
222+
.map(|graph_info| graph_info.g.write_to_lock(self.install_dir.clone()))
223+
.collect::<Result<Vec<LockFile>>>()?;
256224
let dev_dep_graphs = self.collect_graphs(
257225
parent,
258226
root_manifest.package.name,
@@ -261,6 +229,30 @@ impl<Progress: Write> DependencyGraphBuilder<Progress> {
261229
&root_manifest.dev_dependencies,
262230
)?;
263231

232+
let dev_dep_lock_files = dev_dep_graphs
233+
.values()
234+
.map(|graph_info| graph_info.g.write_to_lock(self.install_dir.clone()))
235+
.collect::<Result<Vec<LockFile>>>()?;
236+
let new_deps_digest = self.dependency_digest(dep_lock_files, dev_dep_lock_files)?;
237+
let (manifest_digest, deps_digest) =
238+
match (old_manifest_digest_opt, old_deps_digest_opt, lock_string) {
239+
(Some(old_manifest_digest), Some(old_deps_digest), Some(lock_string))
240+
if old_manifest_digest == new_manifest_digest
241+
&& old_deps_digest == new_deps_digest =>
242+
{
243+
return Ok((
244+
DependencyGraph::read_from_lock(
245+
root_path,
246+
root_manifest.package.name,
247+
&mut lock_string.as_bytes(), // safe since old_deps_digest exists
248+
None,
249+
)?,
250+
false,
251+
));
252+
}
253+
_ => (new_manifest_digest, new_deps_digest),
254+
};
255+
264256
dep_graphs.extend(dev_dep_graphs);
265257

266258
let mut combined_graph = DependencyGraph {
@@ -309,7 +301,7 @@ impl<Progress: Write> DependencyGraphBuilder<Progress> {
309301
combined_graph.check_acyclic()?;
310302
combined_graph.discover_always_deps();
311303

312-
Ok(combined_graph)
304+
Ok((combined_graph, true))
313305
}
314306

315307
/// Given all dependencies from the parent manifest file, collects all the sub-graphs
@@ -405,66 +397,33 @@ impl<Progress: Write> DependencyGraphBuilder<Progress> {
405397
Ok((pkg_graph, is_override, is_external))
406398
}
407399

408-
/// Computes dependency hashes but may return None if information about some dependencies is not
409-
/// available or if there are no dependencies.
410-
fn dependency_hashes(
411-
&mut self,
412-
root_path: PathBuf,
413-
dependencies: &PM::Dependencies,
414-
) -> Result<Option<Vec<String>>> {
400+
/// Computes dependency hashes.
401+
fn dependency_hashes(&mut self, lock_files: Vec<LockFile>) -> Result<Vec<String>> {
415402
let mut hashed_lock_files = Vec::new();
416-
417-
for (pkg_name, dep) in dependencies {
418-
let internal_dep = match dep {
419-
// bail if encountering external dependency that would require running the external
420-
// resolver
421-
// TODO: should we consider handling this here?
422-
PM::Dependency::External(_) => return Ok(None),
423-
PM::Dependency::Internal(d) => d,
424-
};
425-
426-
self.dependency_cache
427-
.download_and_update_if_remote(
428-
*pkg_name,
429-
&internal_dep.kind,
430-
&mut self.progress_output,
431-
)
432-
.with_context(|| format!("Fetching '{}'", *pkg_name))?;
433-
let pkg_path = root_path.join(local_path(&internal_dep.kind));
434-
435-
let Ok(lock_contents) = std::fs::read_to_string(pkg_path.join(SourcePackageLayout::Lock.path())) else {
436-
return Ok(None);
437-
};
438-
hashed_lock_files.push(digest_str(lock_contents.as_bytes()));
403+
for mut lock_file in lock_files {
404+
let mut lock_string: String = "".to_string();
405+
lock_file.read_to_string(&mut lock_string)?;
406+
hashed_lock_files.push(digest_str(lock_string.as_bytes()));
439407
}
440-
441-
Ok(if hashed_lock_files.is_empty() {
442-
None
443-
} else {
444-
Some(hashed_lock_files)
445-
})
408+
Ok(hashed_lock_files)
446409
}
447410

448-
/// Computes a digest of all dependencies in a manifest file but may return None if information
449-
/// about some dependencies is not available.
411+
/// Computes a digest of all dependencies in a manifest file (or digest of empty list if there
412+
/// are no dependencies).
450413
fn dependency_digest(
451414
&mut self,
452-
root_path: PathBuf,
453-
manifest: &PM::SourceManifest,
454-
) -> Result<Option<String>> {
455-
let mut dep_hashes = self
456-
.dependency_hashes(root_path.clone(), &manifest.dependencies)?
457-
.unwrap_or_default();
458-
459-
let dev_dep_hashes = self
460-
.dependency_hashes(root_path, &manifest.dev_dependencies)?
461-
.unwrap_or_default();
462-
463-
if dep_hashes.is_empty() {
464-
Ok(None)
415+
dep_lock_files: Vec<LockFile>,
416+
dev_dep_lock_files: Vec<LockFile>,
417+
) -> Result<String> {
418+
let mut dep_hashes = self.dependency_hashes(dep_lock_files)?;
419+
420+
let dev_dep_hashes = self.dependency_hashes(dev_dep_lock_files)?;
421+
422+
if dep_hashes.is_empty() && dev_dep_hashes.is_empty() {
423+
Ok(digest_str(&[]))
465424
} else {
466425
dep_hashes.extend(dev_dep_hashes);
467-
Ok(Some(hashed_files_digest(dep_hashes)))
426+
Ok(hashed_files_digest(dep_hashes))
468427
}
469428
}
470429
}

0 commit comments

Comments
 (0)