Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(source): doc comments for Source and friends #12153

Merged
merged 3 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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