1 use std
::io
::prelude
::*;
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
;
8 pub fn load_pkg_lockfile(ws
: &Workspace
<'_
>) -> CargoResult
<Option
<Resolve
>> {
9 if !ws
.root().join("Cargo.lock").exists() {
13 let root
= Filesystem
::new(ws
.root().to_path_buf());
14 let mut f
= root
.open_ro("Cargo.lock", ws
.config(), "Cargo.lock file")?
;
16 let mut s
= String
::new();
17 f
.read_to_string(&mut s
)
18 .chain_err(|| format
!("failed to read file: {}", f
.path().display()))?
;
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
)?
))
25 .chain_err(|| format
!("failed to parse lock file at: {}", f
.path().display()))?
;
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
);
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
);
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
) {
46 if !ws
.config().lock_update_allowed() {
47 if ws
.config().offline() {
48 anyhow
::bail
!("can't update in the offline mode");
51 let flag
= if ws
.config().network_allowed() {
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(),
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
70 if resolve
.version() < ResolveVersion
::default() {
71 resolve
.set_version(ResolveVersion
::default());
72 out
= serialize_resolve(resolve
, orig
.as_deref());
75 // Ok, if that didn't work just write it out
77 .open_rw("Cargo.lock", ws
.config(), "Cargo.lock file")
80 f
.write_all(out
.as_bytes())?
;
83 .chain_err(|| format
!("failed to write {}", ws
.root().join("Cargo.lock").display()))?
;
87 fn resolve_to_string_orig(
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
)?
;
99 let out
= serialize_resolve(resolve
, orig
.as_deref().ok());
100 (orig
.ok(), out
, ws_root
)
103 fn serialize_resolve(resolve
: &Resolve
, orig
: Option
<&str>) -> String
{
104 let toml
= toml
::Value
::try_from(resolve
).unwrap();
106 let mut out
= String
::new();
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
);
114 out
.push_str(extra_line
);
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
{
124 if let Some(second
) = comments
.next() {
125 if second
!= extra_line
{
126 out
.push_str(second
);
129 for line
in comments
{
137 if let Some(version
) = toml
.get("version") {
138 out
.push_str(&format
!("version = {}\n\n", version
));
141 let deps
= toml
["package"].as_array().unwrap();
143 let dep
= dep
.as_table().unwrap();
145 out
.push_str("[[package]]\n");
146 emit_package(dep
, &mut out
);
149 if let Some(patch
) = toml
.get("patch") {
150 let list
= patch
["unused"].as_array().unwrap();
152 out
.push_str("[[patch.unused]]\n");
153 emit_package(entry
.as_table().unwrap(), &mut out
);
158 if let Some(meta
) = toml
.get("metadata") {
159 out
.push_str("[metadata]\n");
160 out
.push_str(&meta
.to_string());
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
168 if resolve
.version() >= ResolveVersion
::V2
{
169 while out
.ends_with("\n\n") {
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
)?
)
186 if let Ok(true) = res
{
191 orig
.lines().eq(current
.lines())
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"]));
198 if dep
.contains_key("source") {
199 out
.push_str(&format
!("source = {}\n", &dep
["source"]));
201 if dep
.contains_key("checksum") {
202 out
.push_str(&format
!("checksum = {}\n", &dep
["checksum"]));
205 if let Some(s
) = dep
.get("dependencies") {
206 let slice
= s
.as_array().unwrap();
208 if !slice
.is_empty() {
209 out
.push_str("dependencies = [\n");
211 for child
in slice
.iter() {
212 out
.push_str(&format
!(" {},\n", child
));
218 } else if dep
.contains_key("replace") {
219 out
.push_str(&format
!("replace = {}\n\n", &dep
["replace"]));