blob: 0786043579e4dccc8748295a619253933f0a2ab5 [file] [log] [blame]
use crate::{Krate, Krates, Source};
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use std::collections::BTreeMap;
use tame_index::{index::ComboIndexCache, Error, IndexLocation, IndexUrl};
type YankMap = Vec<(semver::Version, bool)>;
#[derive(Clone)]
pub enum Entry {
Map(YankMap),
Error(String),
}
pub struct Indices<'k> {
pub indices: Vec<(&'k Source, Result<ComboIndexCache, Error>)>,
pub cache: BTreeMap<(&'k str, &'k Source), Entry>,
}
impl<'k> Indices<'k> {
pub fn load(krates: &'k Krates, cargo_home: crate::PathBuf) -> Self {
let mut indices = Vec::<(&Source, Result<ComboIndexCache, Error>)>::new();
for source in krates
.krates()
.filter_map(|k| k.source.as_ref().filter(|s| s.is_registry()))
{
if indices.iter().any(|(src, _)| *src == source) {
continue;
}
let index_url = match source {
Source::CratesIo(_is_sparse) => IndexUrl::crates_io(
Some(krates.workspace_root().to_owned()),
Some(&cargo_home),
None,
),
Source::Sparse(url) | Source::Registry(url) => Ok(url.as_str().into()),
Source::Git { .. } => unreachable!(),
};
let index = index_url.and_then(|iu| {
ComboIndexCache::new(IndexLocation::new(iu).with_root(Some(cargo_home.clone())))
});
indices.push((source, index));
}
#[allow(clippy::blocks_in_conditions)]
let cargo_package_lock =
match tame_index::utils::flock::LockOptions::cargo_package_lock(Some(cargo_home))
.expect("unreachable")
.shared()
.lock(|path| {
log::info!("waiting for {path}...");
Some(std::time::Duration::from_secs(60))
}) {
Ok(fl) => fl,
Err(err) => {
log::error!("unable to acquire cargo global package lock: {err:#}");
tame_index::utils::flock::FileLock::unlocked()
}
};
// Load the current entries into an in-memory cache so we can hopefully
// remove any I/O in the rest of the check
let set: std::collections::BTreeSet<_> = krates
.krates()
.filter_map(|k| {
k.source
.as_ref()
.filter(|s| s.is_registry())
.map(|s| (k.name.as_str(), s))
})
.collect();
let cache = set
.into_par_iter()
.map(|(name, src)| {
let read_entry = || -> Result<YankMap, String> {
match indices
.iter()
.find_map(|(url, index)| (src == *url).then_some(index))
.ok_or_else(|| "unable to locate index".to_owned())?
{
Ok(index) => {
match index.cached_krate(
name.try_into()
.map_err(|e: tame_index::Error| e.to_string())?,
&cargo_package_lock,
) {
Ok(Some(ik)) => {
let yank_map = Self::load_index_krate(ik);
Ok(yank_map)
}
Ok(None) => {
Err("unable to locate index entry for crate".to_owned())
}
Err(err) => Err(format!("{err:#}")),
}
}
Err(err) => Err(format!("{err:#}")),
}
};
(
(name, src),
match read_entry() {
Ok(ym) => Entry::Map(ym),
Err(err) => Entry::Error(err),
},
)
})
.collect();
Self { indices, cache }
}
#[inline]
fn load_index_krate(ik: tame_index::IndexKrate) -> YankMap {
ik.versions
.into_iter()
.filter_map(|iv| Some((iv.version.parse().ok()?, iv.yanked)))
.collect()
}
#[inline]
pub fn is_yanked(&self, krate: &'k Krate) -> Result<bool, String> {
// Ignore non-registry crates when checking, as a crate sourced
// locally or via git can have the same name as a registry package
let Some(src) = krate.source.as_ref().filter(|s| s.is_registry()) else {
return Ok(false);
};
let Some(entry) = self.cache.get(&(krate.name.as_str(), src)) else {
panic!("we should have a cache entry for {krate} by now");
};
match entry {
Entry::Map(cache_entry) => {
let is_yanked = cache_entry
.iter()
.find_map(|kv| (kv.0 == krate.version).then_some(kv.1));
is_yanked.ok_or_else(|| format!("unable to locate version '{}'", krate.version))
}
Entry::Error(err) => Err(err.clone()),
}
}
}