1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use std::path::Path;
use std::{
    fs::read_to_string,
    io::{self, BufWriter, Write},
};

use fs_err::File;
use semver::Version;
use serde::{Deserialize, Serialize};

use crate::{
    manifest::Manifest, package_id::PackageId, package_name::PackageName, resolution::Resolve,
};

pub const LOCKFILE_NAME: &str = "wally.lock";

#[derive(Debug, Serialize, Deserialize)]
pub struct Lockfile {
    pub registry: String,

    #[serde(rename = "package")]
    pub packages: Vec<LockPackage>,
}

impl Lockfile {
    pub fn from_manifest(manifest: &Manifest) -> Self {
        Self {
            registry: manifest.package.registry.clone(),
            packages: Vec::new(),
        }
    }

    pub fn from_resolve(resolve: &Resolve) -> Self {
        let mut packages = Vec::new();

        for package_id in &resolve.activated {
            let dependencies = resolve
                .shared_dependencies
                .get(package_id)
                .map(|dependencies| {
                    dependencies
                        .iter()
                        .map(|(key, value)| (key.clone(), value.clone()))
                        .collect()
                })
                .unwrap_or_else(Vec::new);

            packages.push(LockPackage::Registry(RegistryLockPackage {
                name: package_id.name().clone(),
                version: package_id.version().clone(),
                checksum: None,
                dependencies,
            }));
        }

        Self {
            registry: "test".to_owned(),
            packages,
        }
    }

    pub fn load(project_path: &Path) -> anyhow::Result<Option<Self>> {
        let lockfile_path = project_path.join(LOCKFILE_NAME);
        let contents = match read_to_string(&lockfile_path) {
            Ok(contents) => contents,
            Err(err) => {
                if err.kind() == io::ErrorKind::NotFound {
                    return Ok(None);
                } else {
                    return Err(err.into());
                }
            }
        };
        Ok(Some(toml::from_str(&contents)?))
    }

    pub fn save(&self, project_path: &Path) -> anyhow::Result<()> {
        let lockfile_path = project_path.join(LOCKFILE_NAME);
        let serialized = toml::to_string(self)?;

        let mut file = BufWriter::new(File::create(lockfile_path)?);
        writeln!(file, "# This file is automatically @generated by Wally.")?;
        writeln!(file, "# It is not intended for manual editing.")?;
        write!(file, "{}", serialized)?;
        file.flush()?;

        Ok(())
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LockPackage {
    Registry(RegistryLockPackage),
    Git(GitLockPackage),
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RegistryLockPackage {
    pub name: PackageName,
    pub version: Version,
    pub checksum: Option<String>,

    #[serde(default)]
    pub dependencies: Vec<(String, PackageId)>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct GitLockPackage {
    pub name: String,
    pub rev: String,
    pub commit: String,

    #[serde(default)]
    pub dependencies: Vec<PackageId>,
}