Skip to main content

zoi_project/
lockfile.rs

1use anyhow::{Result, anyhow};
2use std::fs;
3use zoi_core::types;
4
5fn get_lockfile_path() -> Result<std::path::PathBuf> {
6    Ok(std::env::current_dir()?.join("zoi.lock"))
7}
8
9fn read_lockfile_from(path: &std::path::Path) -> Result<Option<types::ZoiLockV2>> {
10    if !path.exists() {
11        return Ok(None);
12    }
13    let content = fs::read_to_string(path)?;
14    if content.trim().is_empty() {
15        return Ok(None);
16    }
17    serde_json::from_str(&content).map(Some).map_err(|e| {
18        anyhow!(
19            "Failed to parse {}. It might be corrupted or in an old format. Error: {}",
20            path.display(),
21            e
22        )
23    })
24}
25
26fn is_lockfile_compatible(lockfile: &types::ZoiLockV2) -> bool {
27    let current_platform = zoi_core::utils::get_platform().unwrap_or_default();
28    if lockfile.installed_packages.is_empty() {
29        return true;
30    }
31    lockfile.installed_packages.values().all(|pkg| {
32        pkg.platform.is_empty()
33            || pkg.platform == current_platform
34            || zoi_core::utils::is_platform_compatible(
35                &current_platform,
36                std::slice::from_ref(&pkg.platform),
37            )
38    })
39}
40
41pub fn read_zoi_lock() -> Result<types::ZoiLockV2> {
42    let path = get_lockfile_path()?;
43
44    if let Some(lockfile) = read_lockfile_from(&path)? {
45        if is_lockfile_compatible(&lockfile) {
46            return Ok(lockfile);
47        }
48
49        let platform = zoi_core::utils::get_platform().unwrap_or_default();
50        let platform_path = path.with_file_name(format!("zoi.{}.lock", platform));
51        if let Some(platform_lock) = read_lockfile_from(&platform_path)? {
52            return Ok(platform_lock);
53        }
54
55        eprintln!(
56            "Warning: zoi.lock has packages targeting a different platform \
57             and no zoi.{}.lock was found, falling back to unconstrained resolution",
58            platform
59        );
60    }
61
62    Ok(types::ZoiLockV2 {
63        version: "2".to_string(),
64        ..Default::default()
65    })
66}
67
68pub fn write_zoi_lock(lockfile: &mut types::ZoiLockV2) -> Result<()> {
69    if zoi_core::frozen::is_frozen() {
70        return Ok(());
71    }
72    let path = get_lockfile_path()?;
73
74    if let Ok(store_dir) = zoi_core::utils::get_store_base_dir(types::Scope::Project) {
75        lockfile.packages_hash = Some(format!(
76            "sha512-{}",
77            zoi_core::hash::calculate_dir_hash(&store_dir).unwrap_or_default()
78        ));
79    }
80
81    let db_dir = std::env::current_dir()?
82        .join(".zoi")
83        .join("pkgs")
84        .join("db");
85
86    if db_dir.exists() {
87        lockfile.registries_hash = Some(format!(
88            "sha512-{}",
89            zoi_core::hash::calculate_dir_hash(&db_dir).unwrap_or_default()
90        ));
91    }
92
93    let content = serde_json::to_string_pretty(lockfile)?;
94    fs::write(path, content)?;
95    Ok(())
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct FrozenLockPackage {
100    pub source: String,
101    pub revision: String,
102    pub direct: bool,
103    pub chosen_options: Vec<String>,
104    pub chosen_optionals: Vec<String>,
105    pub dependencies: Option<types::DependenciesV2>,
106    pub git_sha: Option<String>,
107}
108
109pub fn locked_packages(lockfile: &types::ZoiLockV2) -> Vec<FrozenLockPackage> {
110    let mut packages = Vec::new();
111
112    for (key, detail) in &lockfile.installed_packages {
113        packages.push(FrozenLockPackage {
114            source: format!("{}@{}", key.trim(), detail.version),
115            revision: detail.revision.clone(),
116            direct: detail.why == "direct",
117            chosen_options: Vec::new(),
118            chosen_optionals: Vec::new(),
119            dependencies: detail.dependencies.clone(),
120            git_sha: None,
121        });
122    }
123
124    packages.sort_by(|a, b| a.source.cmp(&b.source));
125    packages
126}
127
128pub fn sources_from_lock(lockfile: &types::ZoiLockV2) -> Vec<String> {
129    locked_packages(lockfile)
130        .into_iter()
131        .map(|entry| entry.source)
132        .collect()
133}