blob: 071c3b95c4b623be4ba4198e0ed38ab5152dc88f [file] [edit]
//! [`Source`] trait for sources of Cargo packages.
use crate::util::data_structures::HashMap;
use std::fmt;
use std::rc::Rc;
use crate::sources::IndexSummary;
use crate::util::CargoResult;
use crate::workspace::SourceId;
use crate::workspace::{Dependency, Package, PackageId};
/// An abstraction of different sources of Cargo packages.
///
/// The [`Source`] trait generalizes the API to interact with these providers.
/// For example,
///
/// * [`Source::query`] is for querying package metadata on a given
/// [`Dependency`] requested by a Cargo manifest.
/// * [`Source::download`] is for fetching the full package information on
/// given names and versions.
/// * [`Source::source_id`] is for defining an unique identifier of a source to
/// distinguish one source from another, keeping Cargo safe from [dependency
/// confusion attack].
///
/// Normally, developers don't need to implement their own [`Source`]s. Cargo
/// provides several kinds of sources implementations that should cover almost
/// all use cases. See [`crate::sources`] for implementations provided by Cargo.
///
/// [dependency confusion attack]: https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
#[async_trait::async_trait(?Send)]
pub trait Source {
/// Returns the [`SourceId`] corresponding to this source.
fn source_id(&self) -> SourceId;
/// Returns the replaced [`SourceId`] corresponding to this source.
fn replaced_source_id(&self) -> SourceId {
self.source_id()
}
/// Returns whether or not this source will return [`IndexSummary`] items with
/// checksums listed.
fn supports_checksums(&self) -> bool;
/// Returns whether or not this source will return [`IndexSummary`] items with
/// the `precise` field in the [`SourceId`] listed.
fn requires_precise(&self) -> bool;
/// Attempts to find the packages that match a dependency request.
///
/// The `f` argument is expected to get called when any [`IndexSummary`] becomes available.
async fn query(
&self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> CargoResult<()>;
/// Gathers the result from [`Source::query`] as a list of [`IndexSummary`] items
/// when they become available.
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)
}
/// Ensure that the source is fully up-to-date for the current session on the next query.
fn invalidate_cache(&self);
/// If quiet, the source should not display any progress or status messages.
fn set_quiet(&mut self, quiet: bool);
/// Starts the process to fetch a [`Package`] for the given [`PackageId`].
///
/// If the source already has the package available on disk, then it
/// should return immediately with [`MaybePackage::Ready`] with the
/// [`Package`]. Otherwise it should return a [`MaybePackage::Download`]
/// to indicate the URL to download the package (this is for remote
/// registry sources only).
///
/// In the case where [`MaybePackage::Download`] is returned, then the
/// package downloader will call [`Source::finish_download`] after the
/// download has finished.
async fn download(&self, package: PackageId) -> CargoResult<MaybePackage>;
/// Gives the source the downloaded `.crate` file.
///
/// When a source has returned [`MaybePackage::Download`] in the
/// [`Source::download`] method, then this function will be called with
/// the results of the download of the given URL. The source is
/// responsible for saving to disk, and returning the appropriate
/// [`Package`].
async fn finish_download(&self, pkg_id: PackageId, contents: Vec<u8>) -> CargoResult<Package>;
/// Generates a unique string which represents the fingerprint of the
/// current state of the source.
///
/// This fingerprint is used to determine the "freshness" of the source
/// later on. It must be guaranteed that the fingerprint of a source is
/// constant if and only if the output product will remain constant.
///
/// The `pkg` argument is the package which this fingerprint should only be
/// interested in for when this source may contain multiple packages.
fn fingerprint(&self, pkg: &Package) -> CargoResult<String>;
/// If this source supports it, verifies the source of the package
/// specified.
///
/// Note that the source may also have performed other checksum-based
/// verification during the `download` step, but this is intended to be run
/// just before a crate is compiled so it may perform more expensive checks
/// which may not be cacheable.
fn verify(&self, _pkg: PackageId) -> CargoResult<()> {
Ok(())
}
/// Describes this source in a human readable fashion, used for display in
/// resolver error messages currently.
fn describe(&self) -> String;
/// Returns whether a source is being replaced by another here.
///
/// Builtin replacement of `crates.io` doesn't count as replacement here.
fn is_replaced(&self) -> bool {
false
}
}
/// Defines how a dependency query will be performed for a [`Source`].
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum QueryKind {
/// A query for packages exactly matching the given dependency requirement.
///
/// Each source gets to define what `exact` means for it.
Exact,
/// A query for packages close to the given dependency requirement.
///
/// Each source gets to define what `close` means for it.
///
/// Path/Git sources may return all dependencies that are at that URI,
/// whereas an `Registry` source may return dependencies that are yanked or invalid.
RejectedVersions,
/// A query for packages close to the given dependency requirement.
///
/// Each source gets to define what `close` means for it.
///
/// Path/Git sources may return all dependencies that are at that URI,
/// whereas an `Registry` source may return dependencies that have the same
/// canonicalization.
AlternativeNames,
/// Match a dependency in all ways and will normalize the package name.
/// Each source defines what normalizing means.
Normalized,
}
/// A download status that represents if a [`Package`] has already been
/// downloaded, or if not then a location to download.
pub enum MaybePackage {
/// The [`Package`] is already downloaded.
Ready(Package),
/// Not yet downloaded. Here is the URL to download the [`Package`] from.
Download {
/// URL to download the content.
url: String,
/// Text to display to the user of what is being downloaded.
descriptor: String,
/// Authorization data that may be required to attach when downloading.
authorization: Option<String>,
},
}
/// A blanket implementation forwards all methods to [`Source`].
#[async_trait::async_trait(?Send)]
impl<'a, T: Source + ?Sized + 'a> Source for &'a mut T {
fn source_id(&self) -> SourceId {
(**self).source_id()
}
fn replaced_source_id(&self) -> SourceId {
(**self).replaced_source_id()
}
fn supports_checksums(&self) -> bool {
(**self).supports_checksums()
}
fn requires_precise(&self) -> bool {
(**self).requires_precise()
}
async fn query(
&self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(IndexSummary),
) -> CargoResult<()> {
(**self).query(dep, kind, f).await
}
fn invalidate_cache(&self) {
(**self).invalidate_cache()
}
fn set_quiet(&mut self, quiet: bool) {
(**self).set_quiet(quiet)
}
async fn download(&self, id: PackageId) -> CargoResult<MaybePackage> {
(**self).download(id).await
}
async fn finish_download(&self, id: PackageId, data: Vec<u8>) -> CargoResult<Package> {
(**self).finish_download(id, data).await
}
fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
(**self).fingerprint(pkg)
}
fn verify(&self, pkg: PackageId) -> CargoResult<()> {
(**self).verify(pkg)
}
fn describe(&self) -> String {
(**self).describe()
}
fn is_replaced(&self) -> bool {
(**self).is_replaced()
}
}
/// A [`HashMap`] of [`SourceId`] to `Box<Source>`.
#[derive(Default)]
pub struct SourceMap<'src> {
map: HashMap<SourceId, Rc<dyn Source + 'src>>,
}
// `impl Debug` on source requires specialization, if even desirable at all.
impl<'src> fmt::Debug for SourceMap<'src> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SourceMap ")?;
f.debug_set().entries(self.map.keys()).finish()
}
}
impl<'src> SourceMap<'src> {
/// Creates an empty map.
pub fn new() -> SourceMap<'src> {
SourceMap {
map: HashMap::default(),
}
}
/// Like `HashMap::get`.
pub fn get(&self, id: SourceId) -> Option<&Rc<dyn Source + 'src>> {
self.map.get(&id)
}
/// Like `HashMap::insert`, but derives the [`SourceId`] key from the [`Source`].
pub fn insert(&mut self, source: Box<dyn Source + 'src>) {
let id = source.source_id();
self.map.insert(id, source.into());
}
/// Like `HashMap::len`.
pub fn len(&self) -> usize {
self.map.len()
}
/// Like `HashMap::iter`.
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a SourceId, &'a (dyn Source + 'src))> {
self.map.iter().map(|(a, b)| (a, &**b))
}
/// Merge the given map into self.
pub fn add_source_map(&mut self, other: SourceMap<'src>) {
for (key, value) in other.map {
self.map.entry(key).or_insert(value);
}
}
}