wit/
lock.rs

1//! Module for the lock file implementation.
2use 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
13/// The name of the lock file.
14pub 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
57/// Constructs a `LockFile` from a `DependencyResolutionMap`.
58pub 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                    // The same requirements should resolve to the same version
81                    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}