use crate::util::data_structures::{HashMap, HashSet};
use std::cell::RefCell;
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 crate::workspace::{Dependency, PackageId, PackageSet, Patch, SourceId, Summary};
use anyhow::Context as _;
use cargo_util_terminal::report::Level;
use futures::stream::FuturesUnordered;
use itertools::Itertools;
use tracing::{debug, trace};
use url::Url;
#[allow(async_fn_in_trait)]
pub trait Registry {
async fn query(
&self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> CargoResult<()>;
async fn query_vec(&self, dep: &Dependency, kind: QueryKind) -> CargoResult<Vec<IndexSummary>> {
let mut ret = Vec::new();
self.query(dep, kind, &mut |s| ret.push(s))
.await
.map(|()| ret)
}
fn describe_source(&self, source: SourceId) -> String;
fn is_replaced(&self, source: SourceId) -> bool;
}
pub struct PackageRegistry<'gctx> {
gctx: &'gctx GlobalContext,
sources: RefCell<SourceMap<'gctx>>,
overrides: RefCell<Vec<SourceId>>,
source_ids: RefCell<HashMap<SourceId, (SourceId, Kind)>>,
locked: LockedMap,
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 Patch, 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: RefCell::new(SourceMap::new()),
source_ids: RefCell::new(HashMap::default()),
overrides: RefCell::new(Vec::new()),
source_config,
locked: HashMap::default(),
patches: HashMap::default(),
patches_locked: false,
patches_available: HashMap::default(),
})
}
pub fn get(self, package_ids: &[PackageId]) -> CargoResult<PackageSet<'gctx>> {
trace!("getting packages; sources={}", self.sources.borrow().len());
PackageSet::new(package_ids, self.sources.into_inner(), self.gctx)
}
fn ensure_loaded(&self, namespace: SourceId, kind: Kind) -> CargoResult<()> {
match self.source_ids.borrow().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.gctx.debug_assert_shell_not_borrowed();
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(&self, source: Box<dyn Source + 'gctx>, kind: Kind) {
let id = source.source_id();
self.sources.borrow_mut().insert(source);
self.source_ids.borrow_mut().insert(id, (id, kind));
}
pub fn add_override(&mut self, source: Box<dyn Source + 'gctx>) {
self.overrides.borrow_mut().push(source.source_id());
self.add_source(source, Kind::Override);
}
pub fn clear_lock(&mut self) {
trace!("clear_lock");
self.locked = HashMap::default();
}
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<(Patch, PackageId)>> {
let canonical = CanonicalUrl::new(url)?;
let mut unlock_patches = Vec::new();
let pending = FuturesUnordered::new();
for (orig_patch, locked) in patch_deps {
let dep = match locked {
Some(lock) => &lock.dependency,
None => &orig_patch.dep,
};
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
.borrow()
.get(dep.source_id())
.expect("loaded source not present")
.clone();
pending.push(async move {
let mut summaries = Vec::new();
source
.query(&dep, QueryKind::Exact, &mut |s| {
if let IndexSummary::Candidate(summary) = s {
summaries.push(summary)
}
})
.await
.with_context(|| format!("unable to update {}", source.source_id()))
.with_context(|| {
format!(
"failed to load source for dependency `{}`",
dep.package_name()
)
})?;
let (summary, should_unlock) =
summary_for_patch(&orig_patch, url, &locked, summaries, source.as_ref())
.await?;
Ok::<_, anyhow::Error>((orig_patch, dep, summary, should_unlock))
});
}
let unlocked_summaries = crate::util::block_on_stream(pending).map(|next| {
let (orig_patch, dep, summary, should_unlock) = next?;
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 `{}` points to the same source, but patches must point to different sources\n\
help: check `{}` patch definition for `{}` in `{}`",
dep.package_name(),
dep.package_name(),
url,
orig_patch.loc
));
}
Ok(summary)
}).collect::<CargoResult<Vec<_>>>()?;
let mut name_and_version = HashSet::default();
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)) {
let duplicate_locations = patch_deps
.iter()
.filter(|&p| p.0.dep.package_name() == name)
.map(|p| format!("`{}`", p.0.loc))
.unique()
.join(", ");
return Err(anyhow::anyhow!(
"several `[patch]` entries resolving to same version `{} v{}`\n\
help: check `{}` patch definitions for `{}` in {}",
name,
version,
name,
url,
duplicate_locations
));
}
}
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(&self, source_id: SourceId, kind: Kind) -> CargoResult<()> {
debug!("loading source {}", source_id);
let source = self
.source_config
.load(source_id)
.with_context(|| format!("unable to update {}", source_id))?;
assert_eq!(source.source_id(), source_id);
if kind == Kind::Override {
self.overrides.borrow_mut().push(source_id);
}
self.add_source(source, kind);
if !source_id.has_locked_precise() {
self.sources
.borrow()
.get(source_id)
.unwrap()
.invalidate_cache();
} else {
debug!("skipping update due to locked registry");
}
Ok(())
}
async fn query_overrides(&self, dep: &Dependency) -> CargoResult<Option<Summary>> {
let overrides = self.overrides.borrow();
for &s in overrides.iter() {
let dep = Dependency::new_override(dep.package_name(), s);
let mut results = None;
self.sources
.borrow()
.get(s)
.unwrap()
.query(&dep, QueryKind::Exact, &mut |s| {
if let IndexSummary::Candidate(s) = s {
results = Some(s);
}
})
.await?;
if results.is_some() {
return Ok(results);
}
}
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> {
async fn query(
&self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> CargoResult<()> {
assert!(self.patches_locked);
let override_summary = self.query_overrides(dep).await?;
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, &patch)?;
let override_summary = self.lock(override_summary);
f(IndexSummary::Candidate(override_summary));
}
None => f(IndexSummary::Candidate(patch)),
}
return 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()
)
})?;
async fn query_with_context(
source: &dyn Source,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> CargoResult<()> {
source
.query(dep, kind, f)
.await
.with_context(|| format!("unable to update {}", source.source_id()))
.with_context(|| {
format!(
"failed to load source for dependency `{}`",
dep.package_name()
)
})
}
let source = self.sources.borrow().get(dep.source_id()).cloned();
match (override_summary, source) {
(Some(_), None) => {
return Err(anyhow::anyhow!("override found but no real ones"));
}
(None, None) => return 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 query_with_context(&*source, dep, kind, callback).await;
}
(Some(override_summary), Some(source)) => {
if !patches.is_empty() {
return Err(anyhow::anyhow!("found patches and a path override"));
}
let mut n = 0;
let mut to_warn = None;
let callback = &mut |summary| {
n += 1;
match summary {
IndexSummary::Candidate(summary)
| IndexSummary::Yanked(summary)
| IndexSummary::Offline(summary)
| IndexSummary::Unsupported(summary, _)
| IndexSummary::Invalid(summary) => {
to_warn = Some(summary);
}
}
};
query_with_context(&*source, dep, kind, callback).await?;
if n > 1 {
return Err(anyhow::anyhow!("found an override with a non-locked list"));
}
if let Some(to_warn) = to_warn {
self.warn_bad_override(&override_summary, &to_warn)?;
}
let override_summary = self.lock(override_summary);
f(IndexSummary::Candidate(override_summary));
}
}
Ok(())
}
fn describe_source(&self, id: SourceId) -> String {
match self.sources.borrow().get(id) {
Some(src) => src.describe(),
None => id.to_string(),
}
}
fn is_replaced(&self, id: SourceId) -> bool {
match self.sources.borrow().get(id) {
Some(src) => src.is_replaced(),
None => false,
}
}
}
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
})
}
async fn summary_for_patch(
original_patch: &Patch,
orig_patch_url: &Url,
locked: &Option<LockedPatchDependency>,
mut summaries: Vec<Summary>,
source: &dyn Source,
) -> CargoResult<(Summary, Option<PackageId>)> {
if summaries.len() == 1 {
return 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 Err(anyhow::anyhow!(
"patch for `{}` in `{}` resolved to more than one candidate\n\
note: found versions: {}\n\
help: check `{}` patch definition for `{}` in `{}`\n\
help: select only one package using `version = \"={}\"`",
&original_patch.dep.package_name(),
&original_patch.dep.source_id(),
versions.join(", "),
&original_patch.dep.package_name(),
orig_patch_url,
original_patch.loc,
versions.last().unwrap()
));
}
assert!(summaries.is_empty());
if let Some(locked) = locked {
let orig_matches = source
.query_vec(&original_patch.dep, QueryKind::Exact)
.await
.unwrap_or_else(|e| {
tracing::warn!(
"could not determine unlocked summaries for dep {:?}: {:?}",
&original_patch.dep,
e
);
Vec::new()
});
let orig_matches = orig_matches
.into_iter()
.filter_map(|s| match s {
IndexSummary::Candidate(s) => Some(s),
_ => None,
})
.collect();
let summary = Box::pin(summary_for_patch(
original_patch,
orig_patch_url,
&None,
orig_matches,
source,
))
.await?;
return Ok((summary.0, Some(locked.package_id)));
}
let name_only_dep = Dependency::new_override(
original_patch.dep.package_name(),
original_patch.dep.source_id(),
);
let name_summaries = source
.query_vec(&name_only_dep, QueryKind::Exact)
.await
.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()
.filter_map(|s| match s {
IndexSummary::Candidate(s) => Some(s.version()),
_ => None,
})
.collect::<Vec<_>>();
let found = match vers.len() {
0 => "".to_string(),
1 => format!("version `{}`", vers[0]),
_ => {
vers.sort();
let strs: Vec<_> = vers.into_iter().map(|v| v.to_string()).collect();
format!("versions `{}`", strs.join(", "))
}
};
Err(if found.is_empty() {
anyhow::anyhow!(
"patch location `{}` does not contain packages matching `{}`\n\
help: check `{}` patch definition for `{}` in `{}`",
&original_patch.dep.source_id(),
&original_patch.dep.package_name(),
&original_patch.dep.package_name(),
orig_patch_url,
original_patch.loc
)
} else {
anyhow::anyhow!(
"patch `{}` version mismatch\n\
note: patch location contains {}, but patch definition requires `{}`\n\
help: check patch location `{}`\n\
help: check `{}` patch definition for `{}` in `{}`",
&original_patch.dep.package_name(),
found,
&original_patch.dep.version_req(),
&original_patch.dep.source_id(),
&original_patch.dep.package_name(),
orig_patch_url,
original_patch.loc
)
})
}