use std::collections::HashMap;
use anyhow::Context;
use cargo_metadata::camino::Utf8Path;
use serde::Deserialize;
use tracing::debug;
pub fn are_lock_dependencies_updated(
local_lock: &Utf8Path,
registry_package: &Utf8Path,
) -> anyhow::Result<bool> {
let registry_lock = ®istry_package.join("Cargo.lock");
if !local_lock.exists() || !registry_lock.exists() {
return Ok(false);
}
are_dependencies_updated(local_lock, registry_lock)
}
fn are_dependencies_updated(
local_lock: &Utf8Path,
registry_lock: &Utf8Path,
) -> anyhow::Result<bool> {
let local_lock: Lockfile = read_lockfile(local_lock)
.with_context(|| format!("failed to load lockfile of local package {local_lock:?}"))?;
let registry_lock = read_lockfile(registry_lock).with_context(|| {
format!("failed to load lockfile of registry package {registry_lock:?}")
})?;
let local_lock_packages = PackagesByName::new(&local_lock.packages);
Ok(are_dependencies_of_lockfiles_updated(
®istry_lock,
&local_lock_packages,
))
}
fn read_lockfile(path: &Utf8Path) -> anyhow::Result<Lockfile> {
let content = fs_err::read_to_string(path).context("can't read lockfile")?;
let lockfile =
toml::from_str(&content).with_context(|| format!("invalid format of lockfile {path:?}"))?;
Ok(lockfile)
}
fn are_dependencies_of_lockfiles_updated(
registry_lock: &Lockfile,
local_lock: &PackagesByName,
) -> bool {
for registry_package in ®istry_lock.packages {
if let Some(local_packages) = local_lock.get(®istry_package.name) {
let is_same_version = local_packages
.iter()
.any(|p| p.version == registry_package.version);
if !is_same_version {
debug!(
"Version of package {} changed to version {:?}",
registry_package.name, registry_package.version
);
return true;
}
}
}
false
}
#[derive(Deserialize, Debug)]
struct Lockfile {
#[serde(rename = "package")]
packages: Vec<Package>,
}
#[derive(Deserialize, Debug)]
struct Package {
name: String,
version: String,
}
struct PackagesByName<'a> {
packages: HashMap<&'a str, Vec<&'a Package>>,
}
impl<'a> PackagesByName<'a> {
fn new(packages: &'a [Package]) -> Self {
let mut packages_by_name = HashMap::new();
for package in packages {
packages_by_name
.entry(package.name.as_str())
.or_insert_with(Vec::new)
.push(package);
}
Self {
packages: packages_by_name,
}
}
fn get(&self, name: &str) -> Option<&[&Package]> {
self.packages.get(name).map(|p| {
assert!(!p.is_empty());
p.as_slice()
})
}
}