use std::io::prelude::*;
use crate::core::{resolver, Resolve, ResolveVersion, 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(&s, ws)?))
})()
.chain_err(|| format!("failed to parse lock file at: {}", f.path().display()))?;
Ok(resolve)
}
pub fn resolve_to_string(ws: &Workspace<'_>, resolve: &Resolve) -> CargoResult<String> {
let (_orig, out, _ws_root) = resolve_to_string_orig(ws, resolve)?;
Ok(out)
}
pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &Resolve) -> CargoResult<()> {
let (orig, out, ws_root) = resolve_to_string_orig(ws, resolve)?;
if let Some(orig) = orig {
if are_equal_lockfiles(orig, &out, ws) {
return Ok(());
}
}
if !ws.config().lock_update_allowed() {
if ws.config().offline() {
anyhow::bail!("can't update in the offline mode");
}
let flag = if ws.config().network_allowed() {
"--locked"
} else {
"--frozen"
};
anyhow::bail!(
"the lock file {} needs to be updated but {} was passed to prevent this\n\
If you want to try to generate the lock file without accessing the network, \
use the --offline flag.",
ws.root().to_path_buf().join("Cargo.lock").display(),
flag
);
}
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 resolve_to_string_orig(
ws: &Workspace<'_>,
resolve: &Resolve,
) -> CargoResult<(Option<String>, String, Filesystem)> {
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(resolve).unwrap();
let mut out = String::new();
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');
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 {
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());
}
match resolve.version() {
ResolveVersion::V1 => {}
_ => {
while out.ends_with("\n\n") {
out.pop();
}
}
}
Ok((orig.ok(), out, ws_root))
}
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 !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(&orig, ws)? == new.into_resolve(current, ws)?)
})();
if let Ok(true) = res {
return true;
}
}
current == orig
}
fn has_crlf_line_endings(s: &str) -> bool {
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 dep.contains_key("checksum") {
out.push_str(&format!("checksum = {}\n", &dep["checksum"]));
}
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"]));
}
}