1use std::collections::BTreeMap;
2use std::path::Path;
3use std::{
4 fs::read_to_string,
5 io::{self, BufWriter, Write},
6};
7
8use fs_err::File;
9use semver::Version;
10use serde::{Deserialize, Serialize};
11
12use crate::package_id;
13use crate::{
14 manifest::Manifest, package_id::PackageId, package_name::PackageName, resolution::Resolve,
15};
16
17pub const LOCKFILE_NAME: &str = "wally.lock";
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct Lockfile {
21 pub registry: String,
22
23 #[serde(rename = "package")]
24 pub packages: Vec<LockPackage>,
25}
26
27fn grab_dependencies(
28 package_id: &PackageId,
29 dependencies: &BTreeMap<
30 package_id::PackageId,
31 BTreeMap<std::string::String, package_id::PackageId>,
32 >,
33) -> Vec<(String, PackageId)> {
34 dependencies
35 .get(package_id)
36 .map(|dependencies| {
37 dependencies
38 .iter()
39 .map(|(key, value)| (key.clone(), value.clone()))
40 .collect()
41 })
42 .unwrap_or_else(Vec::new)
43}
44
45impl Lockfile {
46 pub fn from_manifest(manifest: &Manifest) -> Self {
47 Self {
48 registry: manifest.package.registry.clone(),
49 packages: Vec::new(),
50 }
51 }
52
53 pub fn from_resolve(resolve: &Resolve) -> Self {
54 let mut packages = Vec::new();
55
56 for package_id in &resolve.activated {
57 let dependencies = [
58 grab_dependencies(&package_id, &resolve.shared_dependencies),
59 grab_dependencies(&package_id, &resolve.server_dependencies),
60 grab_dependencies(&package_id, &resolve.dev_dependencies),
61 ]
62 .concat();
63
64 packages.push(LockPackage::Registry(RegistryLockPackage {
65 name: package_id.name().clone(),
66 version: package_id.version().clone(),
67 checksum: None,
68 dependencies,
69 }));
70 }
71
72 Self {
73 registry: "test".to_owned(),
74 packages,
75 }
76 }
77
78 pub fn load(project_path: &Path) -> anyhow::Result<Option<Self>> {
79 let lockfile_path = project_path.join(LOCKFILE_NAME);
80 let contents = match read_to_string(&lockfile_path) {
81 Ok(contents) => contents,
82 Err(err) => {
83 if err.kind() == io::ErrorKind::NotFound {
84 return Ok(None);
85 } else {
86 return Err(err.into());
87 }
88 }
89 };
90 Ok(Some(toml::from_str(&contents)?))
91 }
92
93 pub fn save(&self, project_path: &Path) -> anyhow::Result<()> {
94 let lockfile_path = project_path.join(LOCKFILE_NAME);
95 let serialized = toml::to_string(self)?;
96
97 let mut file = BufWriter::new(File::create(lockfile_path)?);
98 writeln!(file, "# This file is automatically @generated by Wally.")?;
99 writeln!(file, "# It is not intended for manual editing.")?;
100 write!(file, "{}", serialized)?;
101 file.flush()?;
102
103 Ok(())
104 }
105}
106
107#[derive(Debug, Serialize, Deserialize)]
108#[serde(untagged)]
109pub enum LockPackage {
110 Registry(RegistryLockPackage),
111 Git(GitLockPackage),
112}
113
114#[derive(Debug, Serialize, Deserialize)]
115pub struct RegistryLockPackage {
116 pub name: PackageName,
117 pub version: Version,
118 pub checksum: Option<String>,
119
120 #[serde(default)]
121 pub dependencies: Vec<(String, PackageId)>,
122}
123
124#[derive(Debug, Serialize, Deserialize)]
125pub struct GitLockPackage {
126 pub name: String,
127 pub rev: String,
128 pub commit: String,
129
130 #[serde(default)]
131 pub dependencies: Vec<PackageId>,
132}