]> git.proxmox.com Git - cargo.git/blob - src/cargo/ops/lockfile.rs
Print environment note for json format, too.
[cargo.git] / src / cargo / ops / lockfile.rs
1 use std::io::prelude::*;
2
3 use crate::core::{resolver, Resolve, ResolveVersion, Workspace};
4 use crate::util::errors::{CargoResult, CargoResultExt};
5 use crate::util::toml as cargo_toml;
6 use crate::util::Filesystem;
7
8 pub 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
29 /// Generate a toml String of Cargo.lock from a Resolve.
30 pub fn resolve_to_string(ws: &Workspace<'_>, resolve: &mut Resolve) -> CargoResult<String> {
31 let (_orig, out, _ws_root) = resolve_to_string_orig(ws, resolve);
32 Ok(out)
33 }
34
35 pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &mut Resolve) -> CargoResult<()> {
36 let (orig, mut out, ws_root) = resolve_to_string_orig(ws, resolve);
37
38 // If the lock file contents haven't changed so don't rewrite it. This is
39 // helpful on read-only filesystems.
40 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 // While we're updating the lock file anyway go ahead and update its
66 // encoding to whatever the latest default is. That way we can slowly roll
67 // out lock file updates as they're otherwise already updated, and changes
68 // which don't touch dependencies won't seemingly spuriously update the lock
69 // file.
70 if resolve.version() < ResolveVersion::default() {
71 resolve.set_version(ResolveVersion::default());
72 out = serialize_resolve(resolve, orig.as_deref());
73 }
74
75 // Ok, if that didn't work just write it out
76 ws_root
77 .open_rw("Cargo.lock", ws.config(), "Cargo.lock file")
78 .and_then(|mut f| {
79 f.file().set_len(0)?;
80 f.write_all(out.as_bytes())?;
81 Ok(())
82 })
83 .chain_err(|| format!("failed to write {}", ws.root().join("Cargo.lock").display()))?;
84 Ok(())
85 }
86
87 fn resolve_to_string_orig(
88 ws: &Workspace<'_>,
89 resolve: &mut Resolve,
90 ) -> (Option<String>, String, Filesystem) {
91 // Load the original lock file if it exists.
92 let ws_root = Filesystem::new(ws.root().to_path_buf());
93 let orig = ws_root.open_ro("Cargo.lock", ws.config(), "Cargo.lock file");
94 let orig = orig.and_then(|mut f| {
95 let mut s = String::new();
96 f.read_to_string(&mut s)?;
97 Ok(s)
98 });
99 let out = serialize_resolve(resolve, orig.as_deref().ok());
100 (orig.ok(), out, ws_root)
101 }
102
103 fn serialize_resolve(resolve: &Resolve, orig: Option<&str>) -> String {
104 let toml = toml::Value::try_from(resolve).unwrap();
105
106 let mut out = String::new();
107
108 // At the start of the file we notify the reader that the file is generated.
109 // Specifically Phabricator ignores files containing "@generated", so we use that.
110 let marker_line = "# This file is automatically @generated by Cargo.";
111 let extra_line = "# It is not intended for manual editing.";
112 out.push_str(marker_line);
113 out.push('\n');
114 out.push_str(extra_line);
115 out.push('\n');
116 // and preserve any other top comments
117 if let Some(orig) = orig {
118 let mut comments = orig.lines().take_while(|line| line.starts_with('#'));
119 if let Some(first) = comments.next() {
120 if first != marker_line {
121 out.push_str(first);
122 out.push('\n');
123 }
124 if let Some(second) = comments.next() {
125 if second != extra_line {
126 out.push_str(second);
127 out.push('\n');
128 }
129 for line in comments {
130 out.push_str(line);
131 out.push('\n');
132 }
133 }
134 }
135 }
136
137 if let Some(version) = toml.get("version") {
138 out.push_str(&format!("version = {}\n\n", version));
139 }
140
141 let deps = toml["package"].as_array().unwrap();
142 for dep in deps {
143 let dep = dep.as_table().unwrap();
144
145 out.push_str("[[package]]\n");
146 emit_package(dep, &mut out);
147 }
148
149 if let Some(patch) = toml.get("patch") {
150 let list = patch["unused"].as_array().unwrap();
151 for entry in list {
152 out.push_str("[[patch.unused]]\n");
153 emit_package(entry.as_table().unwrap(), &mut out);
154 out.push('\n');
155 }
156 }
157
158 if let Some(meta) = toml.get("metadata") {
159 out.push_str("[metadata]\n");
160 out.push_str(&meta.to_string());
161 }
162
163 // Historical versions of Cargo in the old format accidentally left trailing
164 // blank newlines at the end of files, so we just leave that as-is. For all
165 // encodings going forward, though, we want to be sure that our encoded lock
166 // file doesn't contain any trailing newlines so trim out the extra if
167 // necessary.
168 if resolve.version() >= ResolveVersion::V2 {
169 while out.ends_with("\n\n") {
170 out.pop();
171 }
172 }
173 out
174 }
175
176 fn are_equal_lockfiles(orig: &str, current: &str, ws: &Workspace<'_>) -> bool {
177 // If we want to try and avoid updating the lock file, parse both and
178 // compare them; since this is somewhat expensive, don't do it in the
179 // common case where we can update lock files.
180 if !ws.config().lock_update_allowed() {
181 let res: CargoResult<bool> = (|| {
182 let old: resolver::EncodableResolve = toml::from_str(orig)?;
183 let new: resolver::EncodableResolve = toml::from_str(current)?;
184 Ok(old.into_resolve(orig, ws)? == new.into_resolve(current, ws)?)
185 })();
186 if let Ok(true) = res {
187 return true;
188 }
189 }
190
191 orig.lines().eq(current.lines())
192 }
193
194 fn emit_package(dep: &toml::value::Table, out: &mut String) {
195 out.push_str(&format!("name = {}\n", &dep["name"]));
196 out.push_str(&format!("version = {}\n", &dep["version"]));
197
198 if dep.contains_key("source") {
199 out.push_str(&format!("source = {}\n", &dep["source"]));
200 }
201 if dep.contains_key("checksum") {
202 out.push_str(&format!("checksum = {}\n", &dep["checksum"]));
203 }
204
205 if let Some(s) = dep.get("dependencies") {
206 let slice = s.as_array().unwrap();
207
208 if !slice.is_empty() {
209 out.push_str("dependencies = [\n");
210
211 for child in slice.iter() {
212 out.push_str(&format!(" {},\n", child));
213 }
214
215 out.push_str("]\n");
216 }
217 out.push('\n');
218 } else if dep.contains_key("replace") {
219 out.push_str(&format!("replace = {}\n\n", &dep["replace"]));
220 }
221 }