use std::io::prelude::*;
use crate::core::{resolver, Resolve, ResolveVersion, Workspace};
use crate::util::errors::CargoResult;
use crate::util::Filesystem;
use anyhow::Context as _;
pub fn load_pkg_lockfile(ws: &Workspace<'_>) -> CargoResult<Option<Resolve>> {
let lock_root = lock_root(ws);
if !lock_root.as_path_unlocked().join("Cargo.lock").exists() {
return Ok(None);
}
let mut f = lock_root.open_ro("Cargo.lock", ws.config(), "Cargo.lock file")?;
let mut s = String::new();
f.read_to_string(&mut s)
.with_context(|| format!("failed to read file: {}", f.path().display()))?;
let resolve = (|| -> CargoResult<Option<Resolve>> {
let v: resolver::EncodableResolve = toml::from_str(&s)?;
Ok(Some(v.into_resolve(&s, ws)?))
})()
.with_context(|| format!("failed to parse lock file at: {}", f.path().display()))?;
Ok(resolve)
}
pub fn resolve_to_string(ws: &Workspace<'_>, resolve: &mut Resolve) -> CargoResult<String> {
let (_orig, out, _lock_root) = resolve_to_string_orig(ws, resolve);
Ok(out)
}
pub fn write_pkg_lockfile(ws: &Workspace<'_>, resolve: &mut Resolve) -> CargoResult<()> {
let (orig, mut out, lock_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() {
let flag = if ws.config().locked() {
"--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, \
remove the {} flag and use --offline instead.",
lock_root.as_path_unlocked().join("Cargo.lock").display(),
flag,
flag
);
}
if resolve.version() < ResolveVersion::default() {
resolve.set_version(ResolveVersion::default());
out = serialize_resolve(resolve, orig.as_deref());
} else if resolve.version() > ResolveVersion::default()
&& !ws.config().cli_unstable().next_lockfile_bump
{
anyhow::bail!(
"lock file version `{:?}` requires `-Znext-lockfile-bump`",
resolve.version()
)
}
lock_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(())
})
.with_context(|| {
format!(
"failed to write {}",
lock_root.as_path_unlocked().join("Cargo.lock").display()
)
})?;
Ok(())
}
fn resolve_to_string_orig(
ws: &Workspace<'_>,
resolve: &mut Resolve,
) -> (Option<String>, String, Filesystem) {
let lock_root = lock_root(ws);
let orig = lock_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 out = serialize_resolve(resolve, orig.as_deref().ok());
(orig.ok(), out, lock_root)
}
fn serialize_resolve(resolve: &Resolve, orig: Option<&str>) -> String {
let toml = toml::Table::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 Some(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');
}
}
}
}
if let Some(version) = toml.get("version") {
out.push_str(&format!("version = {}\n\n", version));
}
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('\n');
}
}
if let Some(meta) = toml.get("metadata") {
let meta_table = meta
.as_table()
.expect("validation ensures this is a table")
.clone();
let mut meta_doc = toml::Table::new();
meta_doc.insert("metadata".to_owned(), toml::Value::Table(meta_table));
out.push_str(&meta_doc.to_string());
}
if resolve.version() >= ResolveVersion::V2 {
while out.ends_with("\n\n") {
out.pop();
}
}
out
}
fn are_equal_lockfiles(orig: &str, current: &str, ws: &Workspace<'_>) -> bool {
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;
}
}
orig.lines().eq(current.lines())
}
fn emit_package(dep: &toml::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('\n');
} else if dep.contains_key("replace") {
out.push_str(&format!("replace = {}\n\n", &dep["replace"]));
}
}
fn lock_root(ws: &Workspace<'_>) -> Filesystem {
if ws.root_maybe().is_embedded() {
ws.target_dir()
} else {
Filesystem::new(ws.root().to_owned())
}
}