| 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"])); |
| } |
| } |