use crate::sources::registry::{LoadResponse, RegistryData};
use crate::util::IntoUrl;
use crate::util::data_structures::HashMap;
use crate::util::interning::InternedString;
use crate::util::{CargoResult, Filesystem, GlobalContext, OptVersionReq, internal};
use crate::workspace::dependency::{Artifact, DepKind};
use crate::workspace::{CliUnstable, Dependency};
use crate::workspace::{PackageId, SourceId, Summary};
use cargo_util::registry::make_dep_path;
use cargo_util_schemas::index::{IndexPackage, RegistryDependency};
use cargo_util_schemas::manifest::RustVersion;
use futures::channel::oneshot;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::path::Path;
use std::rc::Rc;
use std::str;
use tracing::info;
mod cache;
use self::cache::CacheManager;
use self::cache::SummariesCache;
const INDEX_V_MAX: u32 = 2;
pub struct RegistryIndex<'gctx> {
source_id: SourceId,
path: Filesystem,
summaries_cache: RefCell<HashMap<InternedString, Rc<Summaries>>>,
summaries_inflight: RefCell<HashMap<InternedString, Vec<oneshot::Sender<Rc<Summaries>>>>>,
gctx: &'gctx GlobalContext,
cache_manager: CacheManager<'gctx>,
}
#[derive(Default)]
struct Summaries {
raw_data: Vec<u8>,
versions: Vec<(Version, RefCell<MaybeIndexSummary>)>,
}
enum MaybeIndexSummary {
Unparsed { start: usize, end: usize },
Parsed(IndexSummary),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum IndexSummary {
Candidate(Summary),
Yanked(Summary),
Offline(Summary),
Unsupported(Summary, u32),
Invalid(Summary),
}
impl IndexSummary {
fn as_summary_unchecked(&self) -> &Summary {
match self {
IndexSummary::Candidate(sum)
| IndexSummary::Yanked(sum)
| IndexSummary::Offline(sum)
| IndexSummary::Unsupported(sum, _)
| IndexSummary::Invalid(sum) => sum,
}
}
pub fn map_summary(self, f: impl Fn(Summary) -> Summary) -> Self {
match self {
IndexSummary::Candidate(s) => IndexSummary::Candidate(f(s)),
IndexSummary::Yanked(s) => IndexSummary::Yanked(f(s)),
IndexSummary::Offline(s) => IndexSummary::Offline(f(s)),
IndexSummary::Unsupported(s, v) => IndexSummary::Unsupported(f(s), v.clone()),
IndexSummary::Invalid(s) => IndexSummary::Invalid(f(s)),
}
}
pub fn package_id(&self) -> PackageId {
self.as_summary_unchecked().package_id()
}
#[must_use]
pub fn is_yanked(&self) -> bool {
matches!(self, Self::Yanked(..))
}
#[must_use]
pub fn is_offline(&self) -> bool {
matches!(self, Self::Offline(..))
}
}
fn index_package_to_summary(
pkg: IndexPackage<'_>,
source_id: SourceId,
cli_unstable: &CliUnstable,
) -> CargoResult<Summary> {
let pkgid = PackageId::new(pkg.name.as_ref().into(), pkg.vers, source_id);
let deps = pkg
.deps
.into_iter()
.map(|dep| registry_dependency_into_dep(dep, source_id, cli_unstable))
.collect::<CargoResult<Vec<_>>>()?;
let mut features = pkg.features;
if let Some(features2) = pkg.features2 {
for (name, values) in features2 {
features.entry(name).or_default().extend(values);
}
}
let features = features
.into_iter()
.map(|(name, values)| (name.into(), values.into_iter().map(|v| v.into()).collect()))
.collect::<BTreeMap<_, _>>();
let links: Option<InternedString> = pkg.links.as_ref().map(|l| l.as_ref().into());
let mut summary = Summary::new(pkgid, deps, &features, links, pkg.rust_version)?;
summary.set_checksum(pkg.cksum);
if let Some(pubtime) = pkg.pubtime {
summary.set_pubtime(pubtime);
}
Ok(summary)
}
#[derive(Deserialize, Serialize)]
struct IndexPackageMinimum<'a> {
name: Cow<'a, str>,
vers: Version,
}
#[derive(Deserialize, Serialize, Default)]
struct IndexPackageRustVersion {
rust_version: Option<RustVersion>,
}
#[derive(Deserialize, Serialize, Default)]
struct IndexPackageV {
v: Option<u32>,
}
impl<'gctx> RegistryIndex<'gctx> {
pub fn new(
source_id: SourceId,
path: &Filesystem,
gctx: &'gctx GlobalContext,
) -> RegistryIndex<'gctx> {
RegistryIndex {
source_id,
path: path.clone(),
summaries_cache: RefCell::new(HashMap::default()),
summaries_inflight: RefCell::new(HashMap::default()),
gctx,
cache_manager: CacheManager::new(path.join(".cache"), gctx),
}
}
pub async fn hash(&self, pkg: PackageId, load: &dyn RegistryData) -> CargoResult<String> {
let req = OptVersionReq::lock_to_exact(pkg.version());
let mut summary = self.summaries(pkg.name(), &req, load).await?;
Ok(summary
.next()
.ok_or_else(|| internal(format!("no hash listed for {}", pkg)))?
.as_summary_unchecked()
.checksum()
.map(|checksum| checksum.to_string())
.ok_or_else(|| internal(format!("no hash listed for {}", pkg)))?)
}
async fn summaries<'a, 'b>(
&'a self,
name: InternedString,
req: &'b OptVersionReq,
load: &dyn RegistryData,
) -> CargoResult<impl Iterator<Item = IndexSummary> + 'b>
where
'a: 'b,
{
let summaries = self.load_summaries(name, load).await?;
struct I<'a> {
name: InternedString,
index: &'a RegistryIndex<'a>,
req: &'a OptVersionReq,
summaries: Rc<Summaries>,
i: usize,
}
impl<'a> Iterator for I<'a> {
type Item = IndexSummary;
fn next(&mut self) -> Option<Self::Item> {
while let Some((v, summary)) = self.summaries.versions.get(self.i) {
self.i += 1;
if self.req.matches(v) {
match summary.borrow_mut().parse(
&self.summaries.raw_data,
self.index.source_id,
self.index.gctx.cli_unstable(),
) {
Ok(summary) => return Some(summary.clone()),
Err(e) => {
info!("failed to parse `{}` registry package: {}", self.name, e);
}
}
}
}
None
}
}
Ok(I {
name,
index: self,
req,
summaries,
i: 0,
})
}
async fn load_summaries(
&self,
name: InternedString,
load: &dyn RegistryData,
) -> CargoResult<Rc<Summaries>> {
if let Some(summaries) = self.summaries_cache.borrow().get(&name) {
return Ok(summaries.clone());
}
let rx = {
let mut pending = self.summaries_inflight.borrow_mut();
if let Some(waiters) = pending.get_mut(&name) {
let (tx, rx) = oneshot::channel();
waiters.push(tx);
Some(rx)
} else {
pending.insert(name, Vec::new());
None
}
};
if let Some(rx) = rx {
return Ok(rx.await?);
}
let summaries = self.load_summaries_uncached(name, load).await;
let pending = self.summaries_inflight.borrow_mut().remove(&name).unwrap();
if let Ok(summaries) = &summaries {
self.summaries_cache
.borrow_mut()
.insert(name, summaries.clone());
for entry in pending {
let _ = entry.send(summaries.clone());
}
};
summaries
}
async fn load_summaries_uncached(
&self,
name: InternedString,
load: &dyn RegistryData,
) -> CargoResult<Rc<Summaries>> {
load.prepare()?;
let root = load.assert_index_locked(&self.path);
let summaries = Summaries::parse(
root,
&name,
self.source_id,
load,
self.gctx.cli_unstable(),
&self.cache_manager,
)
.await?
.unwrap_or_default();
Ok(Rc::new(summaries))
}
pub fn clear_summaries_cache(&self) {
self.summaries_cache.borrow_mut().clear();
}
pub async fn query_inner(
&self,
name: InternedString,
req: &OptVersionReq,
load: &dyn RegistryData,
f: &mut dyn FnMut(IndexSummary),
) -> CargoResult<()> {
if !self.gctx.network_allowed() {
let mut called = false;
let callback = &mut |s: IndexSummary| {
if !s.is_offline() {
called = true;
f(s);
}
};
self.query_inner_with_online(name, req, load, callback, false)
.await?;
if called {
return Ok(());
}
}
self.query_inner_with_online(name, req, load, f, true).await
}
async fn query_inner_with_online(
&self,
name: InternedString,
req: &OptVersionReq,
load: &dyn RegistryData,
f: &mut dyn FnMut(IndexSummary),
online: bool,
) -> CargoResult<()> {
self.summaries(name, &req, load)
.await?
.map(|s| {
if online || load.is_crate_downloaded(s.package_id()) {
s.clone()
} else {
IndexSummary::Offline(s.as_summary_unchecked().clone())
}
})
.for_each(f);
Ok(())
}
}
impl Summaries {
pub async fn parse(
root: &Path,
name: &str,
source_id: SourceId,
load: &dyn RegistryData,
cli_unstable: &CliUnstable,
cache_manager: &CacheManager<'_>,
) -> CargoResult<Option<Summaries>> {
let lowered_name = &name.to_lowercase();
let relative = make_dep_path(&lowered_name, false);
let mut cached_summaries = None;
let mut index_version = None;
if let Some(contents) = cache_manager.get(lowered_name) {
match Summaries::parse_cache(contents) {
Ok((s, v)) => {
cached_summaries = Some(s);
index_version = Some(v);
}
Err(e) => {
tracing::debug!("failed to parse {lowered_name:?} cache: {e}");
}
}
}
let response = load
.load(root, relative.as_ref(), index_version.as_deref())
.await?;
match response {
LoadResponse::CacheValid => {
tracing::debug!("fast path for registry cache of {:?}", relative);
if cached_summaries.is_none() {
return Err(anyhow::anyhow!(
"registry said cache valid when no cache exists"
));
}
return Ok(cached_summaries);
}
LoadResponse::NotFound => {
cache_manager.invalidate(lowered_name);
return Ok(None);
}
LoadResponse::Data {
raw_data,
index_version,
} => {
tracing::debug!("slow path for {:?}", relative);
let mut cache = SummariesCache::default();
let mut ret = Summaries::default();
ret.raw_data = raw_data;
for line in split(&ret.raw_data, b'\n') {
let summary = match IndexSummary::parse(line, source_id, cli_unstable) {
Ok(summary) => summary,
Err(e) => {
tracing::info!(
"failed to parse {:?} registry package: {}",
relative,
e
);
continue;
}
};
let version = summary.package_id().version().clone();
cache.versions.push((version.clone(), line));
ret.versions.push((version, RefCell::new(summary.into())));
}
if let Some(index_version) = index_version {
tracing::trace!("caching index_version {}", index_version);
let cache_bytes = cache.serialize(index_version.as_str());
cache_manager.put(lowered_name, &cache_bytes);
#[cfg(debug_assertions)]
{
let readback = SummariesCache::parse(&cache_bytes)
.expect("failed to parse cache we just wrote");
assert_eq!(
readback.index_version, index_version,
"index_version mismatch"
);
assert_eq!(readback.versions, cache.versions, "versions mismatch");
}
}
Ok(Some(ret))
}
}
}
pub fn parse_cache(contents: Vec<u8>) -> CargoResult<(Summaries, InternedString)> {
let cache = SummariesCache::parse(&contents)?;
let index_version = cache.index_version.into();
let mut versions = Vec::with_capacity(cache.versions.len());
for (version, summary) in cache.versions {
let (start, end) = subslice_bounds(&contents, summary);
versions.push((
version,
RefCell::new(MaybeIndexSummary::Unparsed { start, end }),
));
}
let ret = Summaries {
raw_data: contents,
versions,
};
return Ok((ret, index_version));
fn subslice_bounds(outer: &[u8], inner: &[u8]) -> (usize, usize) {
let outer_start = outer.as_ptr() as usize;
let outer_end = outer_start + outer.len();
let inner_start = inner.as_ptr() as usize;
let inner_end = inner_start + inner.len();
assert!(inner_start >= outer_start);
assert!(inner_end <= outer_end);
(inner_start - outer_start, inner_end - outer_start)
}
}
}
impl MaybeIndexSummary {
fn parse(
&mut self,
raw_data: &[u8],
source_id: SourceId,
cli_unstable: &CliUnstable,
) -> CargoResult<&IndexSummary> {
let (start, end) = match self {
MaybeIndexSummary::Unparsed { start, end } => (*start, *end),
MaybeIndexSummary::Parsed(summary) => return Ok(summary),
};
let summary = IndexSummary::parse(&raw_data[start..end], source_id, cli_unstable)?;
*self = MaybeIndexSummary::Parsed(summary);
match self {
MaybeIndexSummary::Unparsed { .. } => unreachable!(),
MaybeIndexSummary::Parsed(summary) => Ok(summary),
}
}
}
impl From<IndexSummary> for MaybeIndexSummary {
fn from(summary: IndexSummary) -> MaybeIndexSummary {
MaybeIndexSummary::Parsed(summary)
}
}
impl IndexSummary {
fn parse(
line: &[u8],
source_id: SourceId,
cli_unstable: &CliUnstable,
) -> CargoResult<IndexSummary> {
struct IndexSubset {
v: Option<u32>,
yanked: Option<bool>,
}
let index_summary = (|| {
let index = serde_json::from_slice::<IndexPackage<'_>>(line)?;
let subset = IndexSubset {
v: index.v,
yanked: index.yanked,
};
tracing::trace!("json parsed registry {}/{}", index.name, index.vers);
let summary = index_package_to_summary(index, source_id, cli_unstable)?;
Ok((subset, summary))
})();
let (subset, summary, valid) = match index_summary {
Ok((subset, summary)) => (subset, summary, true),
Err(err) => {
let Ok(IndexPackageMinimum { name, vers }) =
serde_json::from_slice::<IndexPackageMinimum<'_>>(line)
else {
return Err(err);
};
tracing::info!(
"recoverying from failed parse of registry package {name}@{vers}: {err}"
);
let IndexPackageRustVersion { rust_version } =
serde_json::from_slice::<IndexPackageRustVersion>(line).unwrap_or_default();
let IndexPackageV { v } =
serde_json::from_slice::<IndexPackageV>(line).unwrap_or_default();
let subset = IndexSubset {
v,
yanked: Default::default(),
};
let index = IndexPackage {
name,
vers,
rust_version,
v,
deps: Default::default(),
features: Default::default(),
features2: Default::default(),
cksum: Default::default(),
yanked: Default::default(),
links: Default::default(),
pubtime: Default::default(),
};
tracing::trace!("json parsed registry {}/{}", index.name, index.vers);
let summary = index_package_to_summary(index, source_id, cli_unstable)?;
(subset, summary, false)
}
};
let v = subset.v.unwrap_or(1);
let v_max = if cli_unstable.bindeps {
INDEX_V_MAX + 1
} else {
INDEX_V_MAX
};
if v_max < v {
Ok(IndexSummary::Unsupported(summary, v))
} else if !valid {
Ok(IndexSummary::Invalid(summary))
} else if subset.yanked.unwrap_or(false) {
Ok(IndexSummary::Yanked(summary))
} else {
Ok(IndexSummary::Candidate(summary))
}
}
}
fn registry_dependency_into_dep(
dep: RegistryDependency<'_>,
default: SourceId,
cli_unstable: &CliUnstable,
) -> CargoResult<Dependency> {
let RegistryDependency {
name,
req,
mut features,
optional,
default_features,
target,
kind,
registry,
package,
public,
artifact,
bindep_target,
lib,
} = dep;
let id = if let Some(registry) = ®istry {
SourceId::for_registry(®istry.into_url()?)?
} else {
default
};
let interned_name = InternedString::new(package.as_ref().unwrap_or(&name));
let mut dep = Dependency::parse(interned_name, Some(&req), id)?;
if package.is_some() {
dep.set_explicit_name_in_toml(name);
}
let kind = match kind.as_deref().unwrap_or("") {
"dev" => DepKind::Development,
"build" => DepKind::Build,
_ => DepKind::Normal,
};
let platform = match target {
Some(target) => Some(target.parse()?),
None => None,
};
let public = public.unwrap_or(false);
features.retain(|s| !s.is_empty());
if !id.is_crates_io() {
dep.set_registry_id(id);
}
if let Some(artifacts) = artifact {
let artifact = Artifact::parse(
&artifacts,
lib,
bindep_target.as_deref(),
cli_unstable.json_target_spec,
)?;
dep.set_artifact(artifact);
}
dep.set_optional(optional)
.set_default_features(default_features)
.set_features(features)
.set_platform(platform)
.set_kind(kind)
.set_public(public);
Ok(dep)
}
fn split(haystack: &[u8], needle: u8) -> impl Iterator<Item = &[u8]> {
struct Split<'a> {
haystack: &'a [u8],
needle: u8,
}
impl<'a> Iterator for Split<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
if self.haystack.is_empty() {
return None;
}
let (ret, remaining) = match memchr::memchr(self.needle, self.haystack) {
Some(pos) => (&self.haystack[..pos], &self.haystack[pos + 1..]),
None => (self.haystack, &[][..]),
};
self.haystack = remaining;
Some(ret)
}
}
Split { haystack, needle }
}