1use std::io::prelude::*;
2
3use crate::core::{resolver, Resolve, ResolveVersion, Workspace};
4use crate::util::errors::{CargoResult, CargoResultExt};
5use crate::util::toml as cargo_toml;
6use crate::util::Filesystem;
7
8pub fn load_pkg_lockfile(ws: &Workspace<'_>) -> CargoResult<Option<Resolve>> {
9 if !ws.root().join("Cargo.lock").exists() {
10 return Ok(None);
11 }
12
13 let root = Filesystem::new(ws.root().to_path_buf());
14 let mut f = root.open_ro("Cargo.lock", ws.config(), "Cargo.lock file")?;
15
16 let mut s = String::new();
17 f.read_to_string(&mut s)
18 .chain_err(|| format!("failed to read file: {}", f.path().display()))?;
19
20 let resolve = (|| -> CargoResult<Option<Resolve>> {
21 let resolve: toml::Value = cargo_toml::parse(&s, f.path(), ws.config())?;
22 let v: resolver::EncodableResolve = resolve.try_into()?;
23 Ok(Some(v.into_resolve(&s, ws)?))
24 })()
25 .chain_err(|| format!("failed to parse lock file at: {}", f.path().display()))?;
26 Ok(resolve)
27}
28
29pub fn resolve_to_string(ws: &Workspace<'_>, resolve: &Resolve) -> CargoResult<String> {
31 let (_orig, out, _ws_root) = resolve_to_string_orig(ws, resolve)?;
32 Ok(out)
33}
34
35pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &Resolve) -> CargoResult<()> {
36 let (orig, out, ws_root) = resolve_to_string_orig(ws, resolve)?;
37
38 if let Some(orig) = orig {
41 if are_equal_lockfiles(orig, &out, ws) {
42 return Ok(());
43 }
44 }
45
46 if !ws.config().lock_update_allowed() {
47 if ws.config().offline() {
48 anyhow::bail!("can't update in the offline mode");
49 }
50
51 let flag = if ws.config().network_allowed() {
52 "--locked"
53 } else {
54 "--frozen"
55 };
56 anyhow::bail!(
57 "the lock file {} needs to be updated but {} was passed to prevent this\n\
58 If you want to try to generate the lock file without accessing the network, \
59 use the --offline flag.",
60 ws.root().to_path_buf().join("Cargo.lock").display(),
61 flag
62 );
63 }
64
65 ws_root
67 .open_rw("Cargo.lock", ws.config(), "Cargo.lock file")
68 .and_then(|mut f| {
69 f.file().set_len(0)?;
70 f.write_all(out.as_bytes())?;
71 Ok(())
72 })
73 .chain_err(|| format!("failed to write {}", ws.root().join("Cargo.lock").display()))?;
74 Ok(())
75}
76
77fn resolve_to_string_orig(
78 ws: &Workspace<'_>,
79 resolve: &Resolve,
80) -> CargoResult<(Option<String>, String, Filesystem)> {
81 let ws_root = Filesystem::new(ws.root().to_path_buf());
83 let orig = ws_root.open_ro("Cargo.lock", ws.config(), "Cargo.lock file");
84 let orig = orig.and_then(|mut f| {
85 let mut s = String::new();
86 f.read_to_string(&mut s)?;
87 Ok(s)
88 });
89
90 let toml = toml::Value::try_from(resolve).unwrap();
91
92 let mut out = String::new();
93
94 let marker_line = "# This file is automatically @generated by Cargo.";
97 let extra_line = "# It is not intended for manual editing.";
98 out.push_str(marker_line);
99 out.push('\n');
100 out.push_str(extra_line);
101 out.push('\n');
102 if let Ok(orig) = &orig {
104 let mut comments = orig.lines().take_while(|line| line.starts_with('#'));
105 if let Some(first) = comments.next() {
106 if first != marker_line {
107 out.push_str(first);
108 out.push('\n');
109 }
110 if let Some(second) = comments.next() {
111 if second != extra_line {
112 out.push_str(second);
113 out.push('\n');
114 }
115 for line in comments {
116 out.push_str(line);
117 out.push('\n');
118 }
119 }
120 }
121 }
122
123 let deps = toml["package"].as_array().unwrap();
124 for dep in deps {
125 let dep = dep.as_table().unwrap();
126
127 out.push_str("[[package]]\n");
128 emit_package(dep, &mut out);
129 }
130
131 if let Some(patch) = toml.get("patch") {
132 let list = patch["unused"].as_array().unwrap();
133 for entry in list {
134 out.push_str("[[patch.unused]]\n");
135 emit_package(entry.as_table().unwrap(), &mut out);
136 out.push_str("\n");
137 }
138 }
139
140 if let Some(meta) = toml.get("metadata") {
141 out.push_str("[metadata]\n");
142 out.push_str(&meta.to_string());
143 }
144
145 match resolve.version() {
151 ResolveVersion::V1 => {}
152 _ => {
153 while out.ends_with("\n\n") {
154 out.pop();
155 }
156 }
157 }
158
159 Ok((orig.ok(), out, ws_root))
160}
161
162fn are_equal_lockfiles(mut orig: String, current: &str, ws: &Workspace<'_>) -> bool {
163 if has_crlf_line_endings(&orig) {
164 orig = orig.replace("\r\n", "\n");
165 }
166
167 if !ws.config().lock_update_allowed() {
171 let res: CargoResult<bool> = (|| {
172 let old: resolver::EncodableResolve = toml::from_str(&orig)?;
173 let new: resolver::EncodableResolve = toml::from_str(current)?;
174 Ok(old.into_resolve(&orig, ws)? == new.into_resolve(current, ws)?)
175 })();
176 if let Ok(true) = res {
177 return true;
178 }
179 }
180
181 current == orig
182}
183
184fn has_crlf_line_endings(s: &str) -> bool {
185 if let Some(lf) = s.find('\n') {
187 s[..lf].ends_with('\r')
188 } else {
189 false
190 }
191}
192
193fn emit_package(dep: &toml::value::Table, out: &mut String) {
194 out.push_str(&format!("name = {}\n", &dep["name"]));
195 out.push_str(&format!("version = {}\n", &dep["version"]));
196
197 if dep.contains_key("source") {
198 out.push_str(&format!("source = {}\n", &dep["source"]));
199 }
200 if dep.contains_key("checksum") {
201 out.push_str(&format!("checksum = {}\n", &dep["checksum"]));
202 }
203
204 if let Some(s) = dep.get("dependencies") {
205 let slice = s.as_array().unwrap();
206
207 if !slice.is_empty() {
208 out.push_str("dependencies = [\n");
209
210 for child in slice.iter() {
211 out.push_str(&format!(" {},\n", child));
212 }
213
214 out.push_str("]\n");
215 }
216 out.push_str("\n");
217 } else if dep.contains_key("replace") {
218 out.push_str(&format!("replace = {}\n\n", &dep["replace"]));
219 }
220}