harn_cli/package/lockfile/
materialize.rs1use crate::package::*;
6
7pub(crate) fn materialize_dependencies_from_lock(
8 workspace: &PackageWorkspace,
9 ctx: &ManifestContext,
10 lock: &LockFile,
11 refetch: Option<&str>,
12 offline: bool,
13) -> Result<usize, PackageError> {
14 publish_package_generation(ctx, lock, refetch.is_some(), |packages_dir| {
15 let mut installed = 0usize;
16 for entry in &lock.packages {
17 let alias = &entry.name;
18 validate_package_alias(alias)?;
19 if entry.source.starts_with("path+") {
20 let source = path_from_source_uri(&entry.source)?;
21 materialize_path_dependency(&source, packages_dir, alias)?;
22 installed += 1;
23 continue;
24 }
25
26 let expected_hash = entry
27 .content_hash
28 .as_deref()
29 .ok_or_else(|| format!("missing content hash for {alias}"))?;
30 let source = entry.source.clone();
31 let refetch_this = refetch == Some("all") || refetch == Some(alias.as_str());
32 let cache_dir = if source.starts_with("git+") {
33 let commit = entry
34 .commit
35 .as_deref()
36 .ok_or_else(|| format!("missing locked commit for {alias}"))?;
37 let url = source.trim_start_matches("git+");
38 ensure_git_cache_populated_in(
39 workspace,
40 url,
41 &source,
42 commit,
43 Some(expected_hash),
44 refetch_this,
45 offline,
46 )?;
47 git_cache_dir_in(workspace, &source, commit)?
48 } else if source.starts_with("archive+") {
49 let url = archive_url_from_source_uri(&source)?;
50 ensure_archive_cache_populated_in(
51 workspace,
52 url,
53 &source,
54 expected_hash,
55 refetch_this,
56 offline,
57 )?;
58 archive_cache_dir_in(workspace, &source, expected_hash)?
59 } else {
60 return Err(
61 format!("unsupported locked package source for {alias}: {source}").into(),
62 );
63 };
64 let dest_dir = packages_dir.join(alias);
65 copy_dir_recursive(&cache_dir, &dest_dir)?;
66 write_cached_content_hash(&dest_dir, expected_hash)?;
67 installed += 1;
68 }
69 Ok(installed)
70 })
71}
72
73pub(crate) fn validate_lock_matches_manifest(
74 workspace: &PackageWorkspace,
75 ctx: &ManifestContext,
76 lock: &LockFile,
77) -> Result<(), PackageError> {
78 for (alias, dependency) in &ctx.manifest.dependencies {
79 validate_package_alias(alias)?;
80 let entry = lock.find(alias).ok_or_else(|| {
81 format!(
82 "{} is missing an entry for {alias}",
83 ctx.lock_path().display()
84 )
85 })?;
86 if !compatible_locked_entry(workspace, alias, dependency, entry, &ctx.dir)? {
87 return Err(format!(
88 "{} is out of date for {alias}; run `harn install`",
89 ctx.lock_path().display()
90 )
91 .into());
92 }
93 }
94 Ok(())
95}
96
97pub fn ensure_dependencies_materialized(anchor: &Path) -> Result<(), PackageError> {
98 let Some((manifest, dir)) = load_nearest_manifest(anchor).into_result()? else {
99 return Ok(());
100 };
101 let ctx = ManifestContext { manifest, dir };
102 if ctx.manifest.dependencies.is_empty() {
103 return dependency_package_snapshot(&ctx.manifest, &ctx.dir).map(|_| ());
104 }
105 let lock = LockFile::load(&ctx.lock_path())?.ok_or_else(|| {
106 format!(
107 "{} is missing; run `harn install`",
108 ctx.lock_path().display()
109 )
110 })?;
111 let workspace = PackageWorkspace::from_current_dir()?;
112 validate_lock_matches_manifest(&workspace, &ctx, &lock)?;
113 materialize_dependencies_from_lock(&workspace, &ctx, &lock, None, false)?;
114 Ok(())
115}
116
117pub(super) fn dependency_manifest_item(
118 alias: &str,
119 dependency: &Dependency,
120) -> Result<toml_edit::Item, PackageError> {
121 validate_package_alias(alias)?;
122 let mut fields = toml_edit::InlineTable::new();
123 let table = match dependency {
124 Dependency::Path(path) => {
125 fields.insert("path", path.clone().into());
126 return Ok(toml_edit::Item::Value(fields.into()));
127 }
128 Dependency::Table(table) => table,
129 };
130 for (name, value) in [
131 ("path", table.path.as_deref()),
132 ("git", table.git.as_deref()),
133 ("archive", table.archive.as_deref()),
134 ] {
135 if let Some(value) = value {
136 fields.insert(name, value.into());
137 }
138 }
139 if let Some(branch) = table.branch.as_deref() {
140 fields.insert("branch", branch.into());
141 } else if let Some(tag) = table.tag.as_deref() {
142 fields.insert("tag", tag.into());
143 } else if let Some(rev) = table.rev.as_deref() {
144 fields.insert("rev", rev.into());
145 }
146 for (name, value) in [
147 ("version", table.version.as_deref()),
148 ("package", table.package.as_deref()),
149 ("checksum", table.checksum.as_deref()),
150 ("registry", table.registry.as_deref()),
151 ("registry_name", table.registry_name.as_deref()),
152 ("registry_version", table.registry_version.as_deref()),
153 ] {
154 if let Some(value) = value {
155 fields.insert(name, value.into());
156 }
157 }
158 Ok(toml_edit::Item::Value(fields.into()))
159}