1use std::{collections::HashMap, path::Path};
3
4use anyhow::Result;
5use cargo_component_core::{
6 lock::{FileLock, LockFile, LockedPackage, LockedPackageVersion},
7 registry::{DependencyResolution, DependencyResolutionMap},
8 terminal::{Colors, Terminal},
9};
10use semver::Version;
11use wasm_pkg_client::{ContentDigest, PackageRef};
12
13pub const LOCK_FILE_NAME: &str = "wit.lock";
15
16pub(crate) fn acquire_lock_file_ro(
17 terminal: &Terminal,
18 config_path: &Path,
19) -> Result<Option<FileLock>> {
20 let path = config_path.with_file_name(LOCK_FILE_NAME);
21 if !path.exists() {
22 return Ok(None);
23 }
24
25 log::info!("opening lock file `{path}`", path = path.display());
26 match FileLock::try_open_ro(&path)? {
27 Some(lock) => Ok(Some(lock)),
28 None => {
29 terminal.status_with_color(
30 "Blocking",
31 format!("on access to lock file `{path}`", path = path.display()),
32 Colors::Cyan,
33 )?;
34
35 FileLock::open_ro(&path).map(Some)
36 }
37 }
38}
39
40pub(crate) fn acquire_lock_file_rw(terminal: &Terminal, config_path: &Path) -> Result<FileLock> {
41 let path = config_path.with_file_name(LOCK_FILE_NAME);
42 log::info!("creating lock file `{path}`", path = path.display());
43 match FileLock::try_open_rw(&path)? {
44 Some(lock) => Ok(lock),
45 None => {
46 terminal.status_with_color(
47 "Blocking",
48 format!("on access to lock file `{path}`", path = path.display()),
49 Colors::Cyan,
50 )?;
51
52 FileLock::open_rw(&path)
53 }
54 }
55}
56
57pub fn to_lock_file(map: &DependencyResolutionMap) -> LockFile {
59 type PackageKey = (PackageRef, Option<String>);
60 type VersionsMap = HashMap<String, (Version, ContentDigest)>;
61 let mut packages: HashMap<PackageKey, VersionsMap> = HashMap::new();
62
63 for resolution in map.values() {
64 match resolution.key() {
65 Some((id, registry)) => {
66 let pkg = match resolution {
67 DependencyResolution::Registry(pkg) => pkg,
68 DependencyResolution::Local(_) => unreachable!(),
69 };
70
71 let prev = packages
72 .entry((id.clone(), registry.map(str::to_string)))
73 .or_default()
74 .insert(
75 pkg.requirement.to_string(),
76 (pkg.version.clone(), pkg.digest.clone()),
77 );
78
79 if let Some((prev, _)) = prev {
80 assert!(prev == pkg.version)
82 }
83 }
84 None => continue,
85 }
86 }
87
88 let mut packages: Vec<_> = packages
89 .into_iter()
90 .map(|((name, registry), versions)| {
91 let mut versions: Vec<LockedPackageVersion> = versions
92 .into_iter()
93 .map(|(requirement, (version, digest))| LockedPackageVersion {
94 requirement,
95 version,
96 digest,
97 })
98 .collect();
99
100 versions.sort_by(|a, b| a.key().cmp(b.key()));
101
102 LockedPackage {
103 name,
104 registry,
105 versions,
106 }
107 })
108 .collect();
109
110 packages.sort_by(|a, b| a.key().cmp(&b.key()));
111
112 LockFile::new(packages)
113}