blob: 9ea513946863100f641e2848b5ba5e9156f56860 [file] [log] [blame]
use std::io::prelude::*;
use toml;
use crate::core::resolver::WorkspaceResolve;
use crate::core::{resolver, Resolve, Workspace};
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::toml as cargo_toml;
use crate::util::Filesystem;
pub fn load_pkg_lockfile(ws: &Workspace<'_>) -> CargoResult<Option<Resolve>> {
if !ws.root().join("Cargo.lock").exists() {
return Ok(None);
}
let root = Filesystem::new(ws.root().to_path_buf());
let mut f = root.open_ro("Cargo.lock", ws.config(), "Cargo.lock file")?;
let mut s = String::new();
f.read_to_string(&mut s)
.chain_err(|| format!("failed to read file: {}", f.path().display()))?;
let resolve = (|| -> CargoResult<Option<Resolve>> {
let resolve: toml::Value = cargo_toml::parse(&s, f.path(), ws.config())?;
let v: resolver::EncodableResolve = resolve.try_into()?;
Ok(Some(v.into_resolve(ws)?))
})()
.chain_err(|| format!("failed to parse lock file at: {}", f.path().display()))?;
Ok(resolve)
}
pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &Resolve) -> CargoResult<()> {
// Load the original lock file if it exists.
let ws_root = Filesystem::new(ws.root().to_path_buf());
let orig = ws_root.open_ro("Cargo.lock", ws.config(), "Cargo.lock file");
let orig = orig.and_then(|mut f| {
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
});
let toml = toml::Value::try_from(WorkspaceResolve { ws, resolve }).unwrap();
let mut out = String::new();
// At the start of the file we notify the reader that the file is generated.
// Specifically Phabricator ignores files containing "@generated", so we use that.
let marker_line = "# This file is automatically @generated by Cargo.";
let extra_line = "# It is not intended for manual editing.";
out.push_str(marker_line);
out.push('\n');
out.push_str(extra_line);
out.push('\n');
// and preserve any other top comments
if let Ok(orig) = &orig {
let mut comments = orig.lines().take_while(|line| line.starts_with('#'));
if let Some(first) = comments.next() {
if first != marker_line {
out.push_str(first);
out.push('\n');
}
if let Some(second) = comments.next() {
if second != extra_line {
out.push_str(second);
out.push('\n');
}
for line in comments {
out.push_str(line);
out.push('\n');
}
}
}
}
let deps = toml["package"].as_array().unwrap();
for dep in deps.iter() {
let dep = dep.as_table().unwrap();
out.push_str("[[package]]\n");
emit_package(dep, &mut out);
}
if let Some(patch) = toml.get("patch") {
let list = patch["unused"].as_array().unwrap();
for entry in list {
out.push_str("[[patch.unused]]\n");
emit_package(entry.as_table().unwrap(), &mut out);
out.push_str("\n");
}
}
if let Some(meta) = toml.get("metadata") {
out.push_str("[metadata]\n");
out.push_str(&meta.to_string());
}
// If the lock file contents haven't changed so don't rewrite it. This is
// helpful on read-only filesystems.
if let Ok(orig) = orig {
if are_equal_lockfiles(orig, &out, ws) {
return Ok(());
}
}
if !ws.config().lock_update_allowed() {
if ws.config().cli_unstable().offline {
failure::bail!("can't update in the offline mode");
}
let flag = if ws.config().network_allowed() {
"--locked"
} else {
"--frozen"
};
failure::bail!(
"the lock file {} needs to be updated but {} was passed to \
prevent this",
ws.root().to_path_buf().join("Cargo.lock").display(),
flag
);
}
// Ok, if that didn't work just write it out
ws_root
.open_rw("Cargo.lock", ws.config(), "Cargo.lock file")
.and_then(|mut f| {
f.file().set_len(0)?;
f.write_all(out.as_bytes())?;
Ok(())
})
.chain_err(|| format!("failed to write {}", ws.root().join("Cargo.lock").display()))?;
Ok(())
}
fn are_equal_lockfiles(mut orig: String, current: &str, ws: &Workspace<'_>) -> bool {
if has_crlf_line_endings(&orig) {
orig = orig.replace("\r\n", "\n");
}
// If we want to try and avoid updating the lock file, parse both and
// compare them; since this is somewhat expensive, don't do it in the
// common case where we can update lock files.
if !ws.config().lock_update_allowed() {
let res: CargoResult<bool> = (|| {
let old: resolver::EncodableResolve = toml::from_str(&orig)?;
let new: resolver::EncodableResolve = toml::from_str(current)?;
Ok(old.into_resolve(ws)? == new.into_resolve(ws)?)
})();
if let Ok(true) = res {
return true;
}
}
current == orig
}
fn has_crlf_line_endings(s: &str) -> bool {
// Only check the first line.
if let Some(lf) = s.find('\n') {
s[..lf].ends_with('\r')
} else {
false
}
}
fn emit_package(dep: &toml::value::Table, out: &mut String) {
out.push_str(&format!("name = {}\n", &dep["name"]));
out.push_str(&format!("version = {}\n", &dep["version"]));
if dep.contains_key("source") {
out.push_str(&format!("source = {}\n", &dep["source"]));
}
if let Some(s) = dep.get("dependencies") {
let slice = s.as_array().unwrap();
if !slice.is_empty() {
out.push_str("dependencies = [\n");
for child in slice.iter() {
out.push_str(&format!(" {},\n", child));
}
out.push_str("]\n");
}
out.push_str("\n");
} else if dep.contains_key("replace") {
out.push_str(&format!("replace = {}\n\n", &dep["replace"]));
}
}