1use std::{
4 collections::{BTreeMap, HashMap},
5 num::ParseIntError,
6 path::{Path, PathBuf},
7};
8
9use gix::ObjectId;
10use serde::{Deserialize, Serialize};
11use serde_with::FromInto;
12
13use crate::{
14 Dependency, GitDependency, ManifestFile, PreciseGitPkg, PreciseIndexPkg, PrecisePkg,
15 error::{Error, IoResultExt},
16 index::{self},
17 resolve::Resolution,
18 version::SemVer,
19};
20
21#[derive(
27 Clone,
28 Debug,
29 Default,
30 serde_with::SerializeDisplay,
31 serde_with::DeserializeFromStr,
32 PartialEq,
33 Eq,
34 PartialOrd,
35 Ord,
36 Hash,
37)]
38pub struct EntryName {
39 pub name: String,
40 pub id: u32,
41}
42
43impl std::fmt::Display for EntryName {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 if self.id == 0 {
46 write!(f, "{}", self.name)
47 } else {
48 write!(f, "{} {}", self.name, self.id)
51 }
52 }
53}
54
55impl std::str::FromStr for EntryName {
56 type Err = ParseIntError;
57
58 fn from_str(s: &str) -> Result<EntryName, ParseIntError> {
59 if let Some((name, num)) = s.split_once(' ') {
60 Ok(EntryName {
61 name: name.to_owned(),
62 id: num.parse()?,
63 })
64 } else {
65 Ok(EntryName {
66 name: s.to_owned(),
67 id: 0,
68 })
69 }
70 }
71}
72
73#[derive(Default)]
74struct LockFileNamer {
75 counts: HashMap<String, u32>,
76 assigned: HashMap<PrecisePkg, (String, u32)>,
77}
78
79impl LockFileNamer {
80 fn name(&mut self, name: &str, pkg: &PrecisePkg) -> EntryName {
81 if let Some((old_name, old_num)) = self.assigned.get(pkg) {
82 EntryName {
83 name: old_name.to_owned(),
84 id: *old_num,
85 }
86 } else {
87 let count = *self
88 .counts
89 .entry(name.to_owned())
90 .and_modify(|i| *i += 1)
91 .or_insert(0);
92 self.assigned.insert(pkg.clone(), (name.to_owned(), count));
93 EntryName {
94 name: name.to_owned(),
95 id: count,
96 }
97 }
98 }
99}
100
101#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
123pub struct LockFile {
124 pub dependencies: BTreeMap<String, LockFileDep>,
128 pub packages: BTreeMap<EntryName, LockFileEntry>,
135}
136
137impl LockFile {
138 pub fn empty() -> Self {
139 Self {
140 dependencies: BTreeMap::new(),
141 packages: BTreeMap::new(),
142 }
143 }
144
145 pub fn new(manifest: &ManifestFile, resolution: &Resolution) -> Result<Self, Error> {
146 fn collect_packages(
147 resolution: &Resolution,
148 id: &str,
149 pkg: &PrecisePkg,
150 acc: &mut BTreeMap<EntryName, LockFileEntry>,
151 namer: &mut LockFileNamer,
152 ) -> Result<EntryName, Error> {
153 let name = namer.name(id, pkg);
154 let entry = LockFileEntry {
155 precise: pkg.clone().into(),
156 dependencies: resolution
157 .sorted_dependencies(pkg)?
158 .into_iter()
159 .map(|(id, dep, precise)| {
160 let spec = match dep {
161 Dependency::Git(g) => Some(g.clone()),
162 Dependency::Path(_) => None,
163 Dependency::Index(_) => None,
164 };
165 let entry = LockFileDep {
166 name: namer.name(id.label(), &precise),
167 spec,
168 };
169 (id.label().to_owned(), entry)
170 })
171 .collect(),
172 };
173
174 if acc.insert(name.clone(), entry).is_none() {
176 for (id, _dep, precise) in resolution.sorted_dependencies(pkg)? {
177 collect_packages(resolution, id.label(), &precise, acc, namer)?;
178 }
179 }
180 Ok(name)
181 }
182
183 let mut acc = BTreeMap::new();
184
185 let mut dependencies = BTreeMap::new();
186 let mut namer = LockFileNamer::default();
187 for (id, dep) in manifest.sorted_dependencies() {
188 let pkg = resolution.precise(dep);
189 let name = collect_packages(resolution, id, &pkg, &mut acc, &mut namer)?;
190 let spec = match dep {
191 Dependency::Git(g) => Some(g.clone()),
192 Dependency::Path(_) => None,
193 Dependency::Index(_) => None,
194 };
195 let entry = LockFileDep { name, spec };
196 dependencies.insert(id.to_owned(), entry);
197 }
198
199 Ok(LockFile {
200 dependencies,
201 packages: acc,
202 })
203 }
204
205 pub fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
207 let path = path.as_ref();
208 let contents = std::fs::read_to_string(path).with_path(path)?;
209 serde_json::from_str(&contents).map_err(|error| Error::LockFileDeserialization {
210 path: path.to_owned(),
211 error,
212 })
213 }
214
215 pub fn write(&self, path: &Path) -> Result<(), Error> {
217 let serialized_lock = serde_json::to_string_pretty(self).unwrap();
221 std::fs::write(path, serialized_lock).with_path(path)?;
222 Ok(())
223 }
224
225 pub fn dependency<'a>(
226 &'a self,
227 entry: Option<&'a LockFileEntry>,
228 name: &str,
229 ) -> Option<&'a LockFileDep> {
230 match entry {
231 None => self.dependencies.get(name),
232 Some(entry) => entry.dependencies.get(name),
233 }
234 }
235}
236
237#[serde_with::serde_as]
242#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
243pub enum LockPrecisePkg {
244 Git {
245 #[serde(with = "crate::serde_url")]
251 url: gix::Url,
252 #[serde_as(as = "serde_with::DisplayFromStr")]
254 id: ObjectId,
255 path: PathBuf,
256 },
257 Path,
258 Index {
259 #[serde_as(as = "FromInto<crate::index::serialize::IdFormat>")]
260 id: index::Id,
261 version: SemVer,
262 },
263}
264
265impl From<PrecisePkg> for LockPrecisePkg {
266 fn from(p: PrecisePkg) -> Self {
267 match p {
268 PrecisePkg::Git(PreciseGitPkg { url, id, path }) => {
270 LockPrecisePkg::Git { url, id, path }
271 }
272 PrecisePkg::Path { .. } => LockPrecisePkg::Path,
273 PrecisePkg::Index(PreciseIndexPkg { id, version }) => {
274 LockPrecisePkg::Index { id, version }
275 }
276 }
277 }
278}
279
280#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
281pub struct LockFileDep {
282 pub name: EntryName,
283 #[serde(skip_serializing_if = "Option::is_none")]
291 #[serde(default)]
292 pub spec: Option<GitDependency>,
293}
294
295#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
297pub struct LockFileEntry {
298 pub precise: LockPrecisePkg,
299 pub dependencies: BTreeMap<String, LockFileDep>,
300}