use std::collections::{HashMap, HashSet};
use std::task::{Poll, ready};
use crate::core::PackageSet;
use crate::core::{Dependency, PackageId, SourceId, Summary};
use crate::sources::IndexSummary;
use crate::sources::config::SourceConfigMap;
use crate::sources::source::QueryKind;
use crate::sources::source::Source;
use crate::sources::source::SourceMap;
use crate::util::errors::CargoResult;
use crate::util::interning::InternedString;
use crate::util::{CanonicalUrl, GlobalContext};
use annotate_snippets::Level;
use anyhow::{Context as _, bail};
use tracing::{debug, trace};
use url::Url;
pub trait Registry {
fn query(
&mut self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> Poll<CargoResult<()>>;
fn query_vec(
&mut self,
dep: &Dependency,
kind: QueryKind,
) -> Poll<CargoResult<Vec<IndexSummary>>> {
let mut ret = Vec::new();
self.query(dep, kind, &mut |s| ret.push(s)).map_ok(|()| ret)
}
fn describe_source(&self, source: SourceId) -> String;
fn is_replaced(&self, source: SourceId) -> bool;
fn block_until_ready(&mut self) -> CargoResult<()>;
}
pub struct PackageRegistry<'gctx> {
gctx: &'gctx GlobalContext,
sources: SourceMap<'gctx>,
overrides: Vec<SourceId>,
source_ids: HashMap<SourceId, (SourceId, Kind)>,
locked: LockedMap,
yanked_whitelist: HashSet<PackageId>,
source_config: SourceConfigMap<'gctx>,
patches: HashMap<CanonicalUrl, Vec<Summary>>,
patches_locked: bool,
patches_available: HashMap<CanonicalUrl, Vec<PackageId>>,
}
type LockedMap = HashMap<
(SourceId, InternedString),
Vec<(PackageId, Vec<PackageId>)>,
>;
#[derive(PartialEq, Eq, Clone, Copy)]
enum Kind {
Override,
Locked,
Normal,
}
pub type PatchDependency<'a> = (&'a Dependency, Option<LockedPatchDependency>);
pub struct LockedPatchDependency {
pub dependency: Dependency,
pub package_id: PackageId,
pub alt_package_id: Option<PackageId>,
}
impl<'gctx> PackageRegistry<'gctx> {
pub fn new_with_source_config(
gctx: &'gctx GlobalContext,
source_config: SourceConfigMap<'gctx>,
) -> CargoResult<PackageRegistry<'gctx>> {
Ok(PackageRegistry {
gctx,
sources: SourceMap::new(),
source_ids: HashMap::new(),
overrides: Vec::new(),
source_config,
locked: HashMap::new(),
yanked_whitelist: HashSet::new(),
patches: HashMap::new(),
patches_locked: false,
patches_available: HashMap::new(),
})
}
pub fn get(self, package_ids: &[PackageId]) -> CargoResult<PackageSet<'gctx>> {
trace!("getting packages; sources={}", self.sources.len());
PackageSet::new(package_ids, self.sources, self.gctx)
}
fn ensure_loaded(&mut self, namespace: SourceId, kind: Kind) -> CargoResult<()> {
match self.source_ids.get(&namespace) {
Some((_, Kind::Locked)) => {
debug!("load/locked {}", namespace);
return Ok(());
}
Some((previous, _)) if !previous.has_precise() => {
debug!("load/precise {}", namespace);
return Ok(());
}
Some((previous, _)) => {
if previous.has_same_precise_as(namespace) {
debug!("load/match {}", namespace);
return Ok(());
}
debug!("load/mismatch {}", namespace);
}
None => {
debug!("load/missing {}", namespace);
}
}
self.load(namespace, kind)?;
self.block_until_ready()?;
Ok(())
}
pub fn add_sources(&mut self, ids: impl IntoIterator<Item = SourceId>) -> CargoResult<()> {
for id in ids {
self.ensure_loaded(id, Kind::Locked)?;
}
Ok(())
}
pub fn add_preloaded(&mut self, source: Box<dyn Source + 'gctx>) {
self.add_source(source, Kind::Locked);
}
fn add_source(&mut self, source: Box<dyn Source + 'gctx>, kind: Kind) {
let id = source.source_id();
self.sources.insert(source);
self.source_ids.insert(id, (id, kind));
}
pub fn add_override(&mut self, source: Box<dyn Source + 'gctx>) {
self.overrides.push(source.source_id());
self.add_source(source, Kind::Override);
}
pub fn add_to_yanked_whitelist(&mut self, iter: impl Iterator<Item = PackageId>) {
let pkgs = iter.collect::<Vec<_>>();
for (_, source) in self.sources.sources_mut() {
source.add_to_yanked_whitelist(&pkgs);
}
self.yanked_whitelist.extend(pkgs);
}
pub fn clear_lock(&mut self) {
trace!("clear_lock");
self.locked = HashMap::new();
}
pub fn register_lock(&mut self, id: PackageId, deps: Vec<PackageId>) {
trace!("register_lock: {}", id);
for dep in deps.iter() {
trace!("\t-> {}", dep);
}
let sub_vec = self
.locked
.entry((id.source_id(), id.name()))
.or_insert_with(Vec::new);
sub_vec.push((id, deps));
}
#[tracing::instrument(skip(self, patch_deps))]
pub fn patch(
&mut self,
url: &Url,
patch_deps: &[PatchDependency<'_>],
) -> CargoResult<Vec<(Dependency, PackageId)>> {
let canonical = CanonicalUrl::new(url)?;
let mut unlock_patches = Vec::new();
let mut patch_deps_remaining: Vec<_> = patch_deps.iter().collect();
let mut unlocked_summaries = Vec::new();
while !patch_deps_remaining.is_empty() {
let mut patch_deps_pending = Vec::new();
for patch_dep_remaining in patch_deps_remaining {
let (orig_patch, locked) = patch_dep_remaining;
let dep = match locked {
Some(lock) => &lock.dependency,
None => *orig_patch,
};
debug!(
"registering a patch for `{}` with `{}`",
url,
dep.package_name()
);
let mut unused_fields = Vec::new();
if dep.features().len() != 0 {
unused_fields.push("`features`");
}
if !dep.uses_default_features() {
unused_fields.push("`default-features`")
}
if !unused_fields.is_empty() {
self.source_config.gctx().shell().print_report(
&[Level::WARNING
.secondary_title(format!(
"unused field in patch for `{}`: {}",
dep.package_name(),
unused_fields.join(", ")
))
.element(Level::HELP.message(format!(
"configure {} in the `dependencies` entry",
unused_fields.join(", ")
)))],
false,
)?;
}
self.ensure_loaded(dep.source_id(), Kind::Normal)
.with_context(|| {
format!(
"failed to load source for dependency `{}`",
dep.package_name()
)
})?;
let source = self
.sources
.get_mut(dep.source_id())
.expect("loaded source not present");
let summaries = match source.query_vec(dep, QueryKind::Exact)? {
Poll::Ready(deps) => deps,
Poll::Pending => {
patch_deps_pending.push(patch_dep_remaining);
continue;
}
};
let summaries = summaries.into_iter().map(|s| s.into_summary()).collect();
let (summary, should_unlock) =
match summary_for_patch(orig_patch, &locked, summaries, source) {
Poll::Ready(x) => x,
Poll::Pending => {
patch_deps_pending.push(patch_dep_remaining);
continue;
}
}
.with_context(|| {
format!(
"patch for `{}` in `{}` failed to resolve",
orig_patch.package_name(),
url,
)
})
.with_context(|| format!("failed to resolve patches for `{}`", url))?;
debug!(
"patch summary is {:?} should_unlock={:?}",
summary, should_unlock
);
if let Some(unlock_id) = should_unlock {
unlock_patches.push(((*orig_patch).clone(), unlock_id));
}
if *summary.package_id().source_id().canonical_url() == canonical {
return Err(anyhow::anyhow!(
"patch for `{}` in `{}` points to the same source, but \
patches must point to different sources",
dep.package_name(),
url
)
.context(format!("failed to resolve patches for `{}`", url)));
}
unlocked_summaries.push(summary);
}
patch_deps_remaining = patch_deps_pending;
self.block_until_ready()?;
}
let mut name_and_version = HashSet::new();
for summary in unlocked_summaries.iter() {
let name = summary.package_id().name();
let version = summary.package_id().version();
if !name_and_version.insert((name, version)) {
bail!(
"cannot have two `[patch]` entries which both resolve \
to `{} v{}`",
name,
version
);
}
}
let mut ids = Vec::new();
for (summary, (_, lock)) in unlocked_summaries.iter().zip(patch_deps) {
ids.push(summary.package_id());
if let Some(lock) = lock {
ids.extend(lock.alt_package_id);
}
}
self.patches_available.insert(canonical.clone(), ids);
self.patches.insert(canonical, unlocked_summaries);
Ok(unlock_patches)
}
pub fn lock_patches(&mut self) {
assert!(!self.patches_locked);
for summaries in self.patches.values_mut() {
for summary in summaries {
debug!("locking patch {:?}", summary);
*summary = lock(&self.locked, &self.patches_available, summary.clone());
}
}
self.patches_locked = true;
}
pub fn patches(&self) -> &HashMap<CanonicalUrl, Vec<Summary>> {
&self.patches
}
fn load(&mut self, source_id: SourceId, kind: Kind) -> CargoResult<()> {
debug!("loading source {}", source_id);
let source = self
.source_config
.load(source_id, &self.yanked_whitelist)
.with_context(|| format!("Unable to update {}", source_id))?;
assert_eq!(source.source_id(), source_id);
if kind == Kind::Override {
self.overrides.push(source_id);
}
self.add_source(source, kind);
if !source_id.has_locked_precise() {
self.sources.get_mut(source_id).unwrap().invalidate_cache();
} else {
debug!("skipping update due to locked registry");
}
Ok(())
}
fn query_overrides(&mut self, dep: &Dependency) -> Poll<CargoResult<Option<IndexSummary>>> {
for &s in self.overrides.iter() {
let src = self.sources.get_mut(s).unwrap();
let dep = Dependency::new_override(dep.package_name(), s);
let mut results = None;
ready!(src.query(&dep, QueryKind::Exact, &mut |s| results = Some(s)))?;
if results.is_some() {
return Poll::Ready(Ok(results));
}
}
Poll::Ready(Ok(None))
}
pub fn lock(&self, summary: Summary) -> Summary {
assert!(self.patches_locked);
lock(&self.locked, &self.patches_available, summary)
}
fn warn_bad_override(
&self,
override_summary: &Summary,
real_summary: &Summary,
) -> CargoResult<()> {
let mut real_deps = real_summary.dependencies().iter().collect::<Vec<_>>();
let boilerplate = "\
This is currently allowed but is known to produce buggy behavior with spurious
recompiles and changes to the crate graph. Path overrides unfortunately were
never intended to support this feature, so for now this message is just a
warning. In the future, however, this message will become a hard error.
To change the dependency graph via an override it's recommended to use the
`[patch]` feature of Cargo instead of the path override feature. This is
documented online at the url below for more information.
https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html
";
for dep in override_summary.dependencies() {
if let Some(i) = real_deps.iter().position(|d| dep == *d) {
real_deps.remove(i);
continue;
}
let msg = format!(
"path override for crate `{}` has altered the original list of\n\
dependencies; the dependency on `{}` was either added or\n\
modified to not match the previously resolved version\n\n\
{}",
override_summary.package_id().name(),
dep.package_name(),
boilerplate
);
self.source_config.gctx().shell().warn(&msg)?;
return Ok(());
}
if let Some(dep) = real_deps.get(0) {
let msg = format!(
"path override for crate `{}` has altered the original list of\n\
dependencies; the dependency on `{}` was removed\n\n\
{}",
override_summary.package_id().name(),
dep.package_name(),
boilerplate
);
self.source_config.gctx().shell().warn(&msg)?;
return Ok(());
}
Ok(())
}
}
impl<'gctx> Registry for PackageRegistry<'gctx> {
fn query(
&mut self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> Poll<CargoResult<()>> {
assert!(self.patches_locked);
let override_summary = ready!(self.query_overrides(dep))?;
let mut patches = Vec::<Summary>::new();
if let Some(extra) = self.patches.get(dep.source_id().canonical_url()) {
patches.extend(
extra
.iter()
.filter(|s| dep.matches_ignoring_source(s.package_id()))
.cloned(),
);
}
if patches.len() == 1 && dep.is_locked() {
let patch = patches.remove(0);
match override_summary {
Some(override_summary) => {
self.warn_bad_override(override_summary.as_summary(), &patch)?;
let override_summary =
override_summary.map_summary(|summary| self.lock(summary));
f(override_summary);
}
None => f(IndexSummary::Candidate(patch)),
}
return Poll::Ready(Ok(()));
}
if !patches.is_empty() {
debug!(
"found {} patches with an unlocked dep on `{}` at {} \
with `{}`, \
looking at sources",
patches.len(),
dep.package_name(),
dep.source_id(),
dep.version_req()
);
}
self.ensure_loaded(dep.source_id(), Kind::Normal)
.with_context(|| {
format!(
"failed to load source for dependency `{}`",
dep.package_name()
)
})?;
let source = self.sources.get_mut(dep.source_id());
match (override_summary, source) {
(Some(_), None) => {
return Poll::Ready(Err(anyhow::anyhow!("override found but no real ones")));
}
(None, None) => return Poll::Ready(Ok(())),
(None, Some(source)) => {
for patch in patches.iter() {
f(IndexSummary::Candidate(patch.clone()));
}
let locked = &self.locked;
let all_patches = &self.patches_available;
let callback = &mut |summary: IndexSummary| {
for patch in patches.iter() {
let patch = patch.package_id().version();
if summary.package_id().version() == patch {
return;
}
}
let summary = summary.map_summary(|summary| lock(locked, all_patches, summary));
f(summary)
};
return source.query(dep, kind, callback);
}
(Some(override_summary), Some(source)) => {
if !patches.is_empty() {
return Poll::Ready(Err(anyhow::anyhow!("found patches and a path override")));
}
let mut n = 0;
let mut to_warn = None;
let callback = &mut |summary| {
n += 1;
to_warn = Some(summary);
};
let pend = source.query(dep, kind, callback);
if pend.is_pending() {
return Poll::Pending;
}
if n > 1 {
return Poll::Ready(Err(anyhow::anyhow!(
"found an override with a non-locked list"
)));
}
if let Some(to_warn) = to_warn {
self.warn_bad_override(override_summary.as_summary(), to_warn.as_summary())?;
}
let override_summary = override_summary.map_summary(|summary| self.lock(summary));
f(override_summary);
}
}
Poll::Ready(Ok(()))
}
fn describe_source(&self, id: SourceId) -> String {
match self.sources.get(id) {
Some(src) => src.describe(),
None => id.to_string(),
}
}
fn is_replaced(&self, id: SourceId) -> bool {
match self.sources.get(id) {
Some(src) => src.is_replaced(),
None => false,
}
}
#[tracing::instrument(skip_all)]
fn block_until_ready(&mut self) -> CargoResult<()> {
self.gctx.debug_assert_shell_not_borrowed();
for (source_id, source) in self.sources.sources_mut() {
source
.block_until_ready()
.with_context(|| format!("Unable to update {}", source_id))?;
}
Ok(())
}
}
fn lock(
locked: &LockedMap,
patches: &HashMap<CanonicalUrl, Vec<PackageId>>,
summary: Summary,
) -> Summary {
let pair = locked
.get(&(summary.source_id(), summary.name()))
.and_then(|vec| vec.iter().find(|&&(id, _)| id == summary.package_id()));
trace!("locking summary of {}", summary.package_id());
let summary = match pair {
Some((precise, _)) => summary.override_id(*precise),
None => summary,
};
summary.map_dependencies(|dep| {
trace!(
"\t{}/{}/{}",
dep.package_name(),
dep.version_req(),
dep.source_id()
);
if let Some((_, locked_deps)) = pair {
let locked = locked_deps.iter().find(|&&id| {
if dep.matches_id(id) {
return true;
}
if !dep.matches_ignoring_source(id) {
return false;
}
match patches.get(dep.source_id().canonical_url()) {
Some(list) => list.contains(&id),
None => false,
}
});
if let Some(&locked) = locked {
trace!("\tfirst hit on {}", locked);
let mut dep = dep;
if locked.source_id() == dep.source_id() {
dep.lock_to(locked);
} else {
dep.lock_version(locked.version());
}
return dep;
}
}
let v = locked
.get(&(dep.source_id(), dep.package_name()))
.and_then(|vec| vec.iter().find(|&&(id, _)| dep.matches_id(id)));
if let Some(&(id, _)) = v {
trace!("\tsecond hit on {}", id);
let mut dep = dep;
dep.lock_to(id);
return dep;
}
trace!("\tnope, unlocked");
dep
})
}
fn summary_for_patch(
orig_patch: &Dependency,
locked: &Option<LockedPatchDependency>,
mut summaries: Vec<Summary>,
source: &mut dyn Source,
) -> Poll<CargoResult<(Summary, Option<PackageId>)>> {
if summaries.len() == 1 {
return Poll::Ready(Ok((summaries.pop().unwrap(), None)));
}
if summaries.len() > 1 {
let mut vers: Vec<_> = summaries.iter().map(|summary| summary.version()).collect();
vers.sort();
let versions: Vec<_> = vers.into_iter().map(|v| v.to_string()).collect();
return Poll::Ready(Err(anyhow::anyhow!(
"patch for `{}` in `{}` resolved to more than one candidate\n\
Found versions: {}\n\
Update the patch definition to select only one package.\n\
For example, add an `=` version requirement to the patch definition, \
such as `version = \"={}\"`.",
orig_patch.package_name(),
orig_patch.source_id(),
versions.join(", "),
versions.last().unwrap()
)));
}
assert!(summaries.is_empty());
if let Some(locked) = locked {
let orig_matches =
ready!(source.query_vec(orig_patch, QueryKind::Exact)).unwrap_or_else(|e| {
tracing::warn!(
"could not determine unlocked summaries for dep {:?}: {:?}",
orig_patch,
e
);
Vec::new()
});
let orig_matches = orig_matches.into_iter().map(|s| s.into_summary()).collect();
let summary = ready!(summary_for_patch(orig_patch, &None, orig_matches, source))?;
return Poll::Ready(Ok((summary.0, Some(locked.package_id))));
}
let name_only_dep = Dependency::new_override(orig_patch.package_name(), orig_patch.source_id());
let name_summaries =
ready!(source.query_vec(&name_only_dep, QueryKind::Exact)).unwrap_or_else(|e| {
tracing::warn!(
"failed to do name-only summary query for {:?}: {:?}",
name_only_dep,
e
);
Vec::new()
});
let mut vers = name_summaries
.iter()
.map(|summary| summary.as_summary().version())
.collect::<Vec<_>>();
let found = match vers.len() {
0 => format!(""),
1 => format!("version `{}`", vers[0]),
_ => {
vers.sort();
let strs: Vec<_> = vers.into_iter().map(|v| v.to_string()).collect();
format!("versions `{}`", strs.join(", "))
}
};
Poll::Ready(Err(if found.is_empty() {
anyhow::anyhow!(
"The patch location `{}` does not appear to contain any packages \
matching the name `{}`.",
orig_patch.source_id(),
orig_patch.package_name()
)
} else {
anyhow::anyhow!(
"The patch location `{}` contains a `{}` package with {}, but the patch \
definition requires `{}`.\n\
Check that the version in the patch location is what you expect, \
and update the patch definition to match.",
orig_patch.source_id(),
orig_patch.package_name(),
found,
orig_patch.version_req()
)
}))
}