Skip to content

Commit

Permalink
Auto merge of #12153 - weihanglo:source-doc, r=epage
Browse files Browse the repository at this point in the history
docs(source): doc comments for `Source` and friends
  • Loading branch information
bors committed May 18, 2023
2 parents 09276c7 + 4f00929 commit 5cedce9
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 20 deletions.
87 changes: 67 additions & 20 deletions src/cargo/core/source/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
//! Fundamental types and traits for sources of Cargo packages.
//!
//! A source is a provider that contains source files and metadata of packages.
//! It provides a number of methods to fetch those package informations, for
//! example, querying metadata or downloading files for a package. These
//! informations then can be used as dependencies for other Cargo packages.
//!
//! Notably, this module contains
//!
//! * [`Source`] trait as an abstraction of different sources
//! * [`SourceMap`] struct as a map of all available sources
//! * [`SourceId`] struct as an unique identifier for a certain source
//!
//! For implementations of `Source` trait, see [`crate::sources`].

use std::collections::hash_map::HashMap;
use std::fmt;
use std::task::Poll;
Expand All @@ -10,32 +25,53 @@ mod source_id;

pub use self::source_id::{GitReference, SourceId};

/// Something that finds and downloads remote packages based on names and versions.
/// 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
pub trait Source {
/// Returns the `SourceId` corresponding to this source.
/// Returns the [`SourceId`] corresponding to this source.
fn source_id(&self) -> SourceId;

/// Returns the replaced `SourceId` corresponding to this source.
/// 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 summaries with
/// Returns whether or not this source will return [`Summary`] items with
/// checksums listed.
fn supports_checksums(&self) -> bool;

/// Returns whether or not this source will return summaries with
/// the `precise` field in the source id listed.
/// Returns whether or not this source will return [`Summary`] 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 [`Summary`] becomes available.
fn query(
&mut self,
dep: &Dependency,
kind: QueryKind,
f: &mut dyn FnMut(Summary),
) -> Poll<CargoResult<()>>;

/// A helper function that collects and returns the result from
/// [`Source::query`] as a list of [`Summary`] items when available.
fn query_vec(&mut self, dep: &Dependency, kind: QueryKind) -> Poll<CargoResult<Vec<Summary>>> {
let mut ret = Vec::new();
self.query(dep, kind, &mut |s| ret.push(s)).map_ok(|_| ret)
Expand All @@ -50,6 +86,7 @@ pub trait Source {
/// Fetches the full package for each name and version specified.
fn download(&mut self, package: PackageId) -> CargoResult<MaybePackage>;

/// Fetches the full package **immediately** for each name and version specified.
fn download_now(self: Box<Self>, package: PackageId, config: &Config) -> CargoResult<Package>
where
Self: std::marker::Sized,
Expand All @@ -61,7 +98,8 @@ pub trait Source {
Ok(Package::clone(pkg))
}

fn finish_download(&mut self, package: PackageId, contents: Vec<u8>) -> CargoResult<Package>;
/// Finalizes the download contents of the given [`PackageId`] to a [`Package`].
fn finish_download(&mut self, pkg_id: PackageId, contents: Vec<u8>) -> CargoResult<Package>;

/// Generates a unique string which represents the fingerprint of the
/// current state of the source.
Expand Down Expand Up @@ -103,56 +141,67 @@ pub trait Source {
/// as yanked. This ignores the yanked whitelist.
fn is_yanked(&mut self, _pkg: PackageId) -> Poll<CargoResult<bool>>;

/// Block until all outstanding Poll::Pending requests are `Poll::Ready`.
/// Block until all outstanding [`Poll::Pending`] requests are [`Poll::Ready`].
///
/// After calling this function, the source should return `Poll::Ready` for
/// any queries that previously returned `Poll::Pending`.
///
/// If no queries previously returned `Poll::Pending`, and `invalidate_cache`
/// If no queries previously returned `Poll::Pending`, and [`Source::invalidate_cache`]
/// was not called, this function should be a no-op.
fn block_until_ready(&mut self) -> CargoResult<()>;
}

/// 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 `Index` source may return dependencies that have the same canonicalization.
/// whereas an `Registry` source may return dependencies that have the same
/// canonicalization.
Fuzzy,
}

/// 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`].
impl<'a, T: Source + ?Sized + 'a> Source for Box<T> {
/// Forwards to `Source::source_id`.
fn source_id(&self) -> SourceId {
(**self).source_id()
}

/// Forwards to `Source::replaced_source_id`.
fn replaced_source_id(&self) -> SourceId {
(**self).replaced_source_id()
}

/// Forwards to `Source::supports_checksums`.
fn supports_checksums(&self) -> bool {
(**self).supports_checksums()
}

/// Forwards to `Source::requires_precise`.
fn requires_precise(&self) -> bool {
(**self).requires_precise()
}

/// Forwards to `Source::query`.
fn query(
&mut self,
dep: &Dependency,
Expand All @@ -170,7 +219,6 @@ impl<'a, T: Source + ?Sized + 'a> Source for Box<T> {
(**self).set_quiet(quiet)
}

/// Forwards to `Source::download`.
fn download(&mut self, id: PackageId) -> CargoResult<MaybePackage> {
(**self).download(id)
}
Expand All @@ -179,12 +227,10 @@ impl<'a, T: Source + ?Sized + 'a> Source for Box<T> {
(**self).finish_download(id, data)
}

/// Forwards to `Source::fingerprint`.
fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
(**self).fingerprint(pkg)
}

/// Forwards to `Source::verify`.
fn verify(&self, pkg: PackageId) -> CargoResult<()> {
(**self).verify(pkg)
}
Expand All @@ -210,6 +256,7 @@ impl<'a, T: Source + ?Sized + 'a> Source for Box<T> {
}
}

/// A blanket implementation forwards all methods to [`Source`].
impl<'a, T: Source + ?Sized + 'a> Source for &'a mut T {
fn source_id(&self) -> SourceId {
(**self).source_id()
Expand Down Expand Up @@ -281,7 +328,7 @@ impl<'a, T: Source + ?Sized + 'a> Source for &'a mut T {
}
}

/// A `HashMap` of `SourceId` -> `Box<Source>`.
/// A [`HashMap`] of [`SourceId`] to `Box<Source>`.
#[derive(Default)]
pub struct SourceMap<'src> {
map: HashMap<SourceId, Box<dyn Source + 'src>>,
Expand Down Expand Up @@ -313,7 +360,7 @@ impl<'src> SourceMap<'src> {
self.map.get_mut(&id).map(|s| s.as_mut())
}

/// Like `HashMap::insert`, but derives the `SourceId` key from the `Source`.
/// 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);
Expand Down
47 changes: 47 additions & 0 deletions src/cargo/sources/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,64 @@ use anyhow::Context as _;
use cargo_util::{paths, Sha256};
use serde::Deserialize;

/// `DirectorySource` contains a number of crates on the file system. It was
/// designed for representing vendored dependencies for `cargo vendor`.
///
/// `DirectorySource` at this moment is just a root directory containing other
/// directories, which contain the source files of packages. Assumptions would
/// be made to determine if a directory should be included as a package of a
/// directory source's:
///
/// * Ignore directories starting with dot `.` (tend to be hidden).
/// * Only when a `Cargo.toml` exists in a directory will it be included as
/// a package. `DirectorySource` at this time only looks at one level of
/// directories and never went deeper.
/// * There must be a [`Checksum`] file `.cargo-checksum.json` file at the same
/// level of `Cargo.toml` to ensure the integrity when a directory source was
/// created (usually by `cargo vendor`). A failure to find or parse a single
/// checksum results in a denial of loading any package in this source.
/// * Otherwise, there is no other restrction of the name of directories. At
/// this moment, it is `cargo vendor` that defines the layout and the name of
/// each directory.
///
/// The file tree of a directory source may look like:
///
/// ```text
/// [source root]
/// ├── a-valid-crate/
/// │ ├── src/
/// │ ├── .cargo-checksum.json
/// │ └── Cargo.toml
/// ├── .ignored-a-dot-crate/
/// │ ├── src/
/// │ ├── .cargo-checksum.json
/// │ └── Cargo.toml
/// ├── skipped-no-manifest/
/// │ ├── src/
/// │ └── .cargo-checksum.json
/// └── no-checksum-so-fails-the-entire-source-reading/
/// └── Cargo.toml
/// ```
pub struct DirectorySource<'cfg> {
/// The unique identifier of this source.
source_id: SourceId,
/// The root path of this source.
root: PathBuf,
/// Packages that this sources has discovered.
packages: HashMap<PackageId, (Package, Checksum)>,
config: &'cfg Config,
updated: bool,
}

/// The checksum file to ensure the integrity of a package in a directory source.
///
/// The file name is simply `.cargo-checksum.json`. The checksum algorithm as
/// of now is SHA256.
#[derive(Deserialize)]
struct Checksum {
/// Checksum of the package. Normally it is computed from the `.crate` file.
package: Option<String>,
/// Checksums of each source file.
files: HashMap<String, String>,
}

Expand Down
21 changes: 21 additions & 0 deletions src/cargo/sources/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
//! Implementations of `Source` trait.
//!
//! Cargo provides several built-in implementations of [`Source`] trait. Namely,
//!
//! * [`RegistrySource`] --- A source that provides an index for people to query
//! a crate's metadata, and fetch files for a certain crate. crates.io falls
//! into this category. So do local registry and sparse registry.
//! * [`DirectorySource`] --- Files are downloaded ahead of time. Primarily
//! designed for crates generated from `cargo vendor`.
//! * [`GitSource`] --- This gets crate information from a git repository.
//! * [`PathSource`] --- This gets crate information from a local path on the
//! filesystem.
//! * [`ReplacedSource`] --- This manages the [source replacement] feature,
//! redirecting operations on the original source to the replacement.
//!
//! This module also contains [`SourceConfigMap`], which is effectively the
//! representation of the `[source.*]` value in Cargo configuration.
//!
//! [`Source`]: crate::core::Source
//! [source replacement]: https://doc.rust-lang.org/nightly/cargo/reference/source-replacement.html

pub use self::config::SourceConfigMap;
pub use self::directory::DirectorySource;
pub use self::git::GitSource;
Expand Down
11 changes: 11 additions & 0 deletions src/cargo/sources/replaced.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@ use std::task::Poll;

use anyhow::Context as _;

/// A source that replaces one source with the other. This manages the [source
/// replacement] feature.
///
/// The implementation is merely redirecting from the original to the replacement.
///
/// [source replacement]: https://doc.rust-lang.org/nightly/cargo/reference/source-replacement.html
pub struct ReplacedSource<'cfg> {
/// The identifier of the original source.
to_replace: SourceId,
/// The identifier of the new replacement source.
replace_with: SourceId,
inner: Box<dyn Source + 'cfg>,
}

impl<'cfg> ReplacedSource<'cfg> {
/// Creates a replaced source.
///
/// The `src` argument is the new replacement source.
pub fn new(
to_replace: SourceId,
replace_with: SourceId,
Expand Down

0 comments on commit 5cedce9

Please sign in to comment.