cargo/sources/source.rs
1//! [`Source`] trait for sources of Cargo packages.
2
3use std::collections::hash_map::HashMap;
4use std::fmt;
5use std::task::Poll;
6
7use crate::core::SourceId;
8use crate::core::{Dependency, Package, PackageId};
9use crate::sources::IndexSummary;
10use crate::util::CargoResult;
11
12/// An abstraction of different sources of Cargo packages.
13///
14/// The [`Source`] trait generalizes the API to interact with these providers.
15/// For example,
16///
17/// * [`Source::query`] is for querying package metadata on a given
18/// [`Dependency`] requested by a Cargo manifest.
19/// * [`Source::download`] is for fetching the full package information on
20/// given names and versions.
21/// * [`Source::source_id`] is for defining an unique identifier of a source to
22/// distinguish one source from another, keeping Cargo safe from [dependency
23/// confusion attack].
24///
25/// Normally, developers don't need to implement their own [`Source`]s. Cargo
26/// provides several kinds of sources implementations that should cover almost
27/// all use cases. See [`crate::sources`] for implementations provided by Cargo.
28///
29/// [dependency confusion attack]: https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
30pub trait Source {
31 /// Returns the [`SourceId`] corresponding to this source.
32 fn source_id(&self) -> SourceId;
33
34 /// Returns the replaced [`SourceId`] corresponding to this source.
35 fn replaced_source_id(&self) -> SourceId {
36 self.source_id()
37 }
38
39 /// Returns whether or not this source will return [`IndexSummary`] items with
40 /// checksums listed.
41 fn supports_checksums(&self) -> bool;
42
43 /// Returns whether or not this source will return [`IndexSummary`] items with
44 /// the `precise` field in the [`SourceId`] listed.
45 fn requires_precise(&self) -> bool;
46
47 /// Attempts to find the packages that match a dependency request.
48 ///
49 /// Usually you should call [`Source::block_until_ready`] somewhere and
50 /// wait until package information become available. Otherwise any query
51 /// may return a [`Poll::Pending`].
52 ///
53 /// The `f` argument is expected to get called when any [`IndexSummary`] becomes available.
54 fn query(
55 &mut self,
56 dep: &Dependency,
57 kind: QueryKind,
58 f: &mut dyn FnMut(IndexSummary),
59 ) -> Poll<CargoResult<()>>;
60
61 /// Gathers the result from [`Source::query`] as a list of [`IndexSummary`] items
62 /// when they become available.
63 fn query_vec(
64 &mut self,
65 dep: &Dependency,
66 kind: QueryKind,
67 ) -> Poll<CargoResult<Vec<IndexSummary>>> {
68 let mut ret = Vec::new();
69 self.query(dep, kind, &mut |s| ret.push(s)).map_ok(|_| ret)
70 }
71
72 /// Ensure that the source is fully up-to-date for the current session on the next query.
73 fn invalidate_cache(&mut self);
74
75 /// If quiet, the source should not display any progress or status messages.
76 fn set_quiet(&mut self, quiet: bool);
77
78 /// Starts the process to fetch a [`Package`] for the given [`PackageId`].
79 ///
80 /// If the source already has the package available on disk, then it
81 /// should return immediately with [`MaybePackage::Ready`] with the
82 /// [`Package`]. Otherwise it should return a [`MaybePackage::Download`]
83 /// to indicate the URL to download the package (this is for remote
84 /// registry sources only).
85 ///
86 /// In the case where [`MaybePackage::Download`] is returned, then the
87 /// package downloader will call [`Source::finish_download`] after the
88 /// download has finished.
89 fn download(&mut self, package: PackageId) -> CargoResult<MaybePackage>;
90
91 /// Gives the source the downloaded `.crate` file.
92 ///
93 /// When a source has returned [`MaybePackage::Download`] in the
94 /// [`Source::download`] method, then this function will be called with
95 /// the results of the download of the given URL. The source is
96 /// responsible for saving to disk, and returning the appropriate
97 /// [`Package`].
98 fn finish_download(&mut self, pkg_id: PackageId, contents: Vec<u8>) -> CargoResult<Package>;
99
100 /// Generates a unique string which represents the fingerprint of the
101 /// current state of the source.
102 ///
103 /// This fingerprint is used to determine the "freshness" of the source
104 /// later on. It must be guaranteed that the fingerprint of a source is
105 /// constant if and only if the output product will remain constant.
106 ///
107 /// The `pkg` argument is the package which this fingerprint should only be
108 /// interested in for when this source may contain multiple packages.
109 fn fingerprint(&self, pkg: &Package) -> CargoResult<String>;
110
111 /// If this source supports it, verifies the source of the package
112 /// specified.
113 ///
114 /// Note that the source may also have performed other checksum-based
115 /// verification during the `download` step, but this is intended to be run
116 /// just before a crate is compiled so it may perform more expensive checks
117 /// which may not be cacheable.
118 fn verify(&self, _pkg: PackageId) -> CargoResult<()> {
119 Ok(())
120 }
121
122 /// Describes this source in a human readable fashion, used for display in
123 /// resolver error messages currently.
124 fn describe(&self) -> String;
125
126 /// Returns whether a source is being replaced by another here.
127 ///
128 /// Builtin replacement of `crates.io` doesn't count as replacement here.
129 fn is_replaced(&self) -> bool {
130 false
131 }
132
133 /// Add a number of crates that should be whitelisted for showing up during
134 /// queries, even if they are yanked. Currently only applies to registry
135 /// sources.
136 fn add_to_yanked_whitelist(&mut self, pkgs: &[PackageId]);
137
138 /// Query if a package is yanked. Only registry sources can mark packages
139 /// as yanked. This ignores the yanked whitelist.
140 fn is_yanked(&mut self, _pkg: PackageId) -> Poll<CargoResult<bool>>;
141
142 /// Block until all outstanding [`Poll::Pending`] requests are [`Poll::Ready`].
143 ///
144 /// After calling this function, the source should return `Poll::Ready` for
145 /// any queries that previously returned `Poll::Pending`.
146 ///
147 /// If no queries previously returned `Poll::Pending`, and [`Source::invalidate_cache`]
148 /// was not called, this function should be a no-op.
149 fn block_until_ready(&mut self) -> CargoResult<()>;
150}
151
152/// Defines how a dependency query will be performed for a [`Source`].
153#[derive(Copy, Clone, PartialEq, Eq)]
154pub enum QueryKind {
155 /// A query for packages exactly matching the given dependency requirement.
156 ///
157 /// Each source gets to define what `exact` means for it.
158 Exact,
159 /// A query for packages close to the given dependency requirement.
160 ///
161 /// Each source gets to define what `close` means for it.
162 ///
163 /// Path/Git sources may return all dependencies that are at that URI,
164 /// whereas an `Registry` source may return dependencies that are yanked or invalid.
165 RejectedVersions,
166 /// A query for packages close to the given dependency requirement.
167 ///
168 /// Each source gets to define what `close` means for it.
169 ///
170 /// Path/Git sources may return all dependencies that are at that URI,
171 /// whereas an `Registry` source may return dependencies that have the same
172 /// canonicalization.
173 AlternativeNames,
174 /// Match a dependency in all ways and will normalize the package name.
175 /// Each source defines what normalizing means.
176 Normalized,
177}
178
179/// A download status that represents if a [`Package`] has already been
180/// downloaded, or if not then a location to download.
181pub enum MaybePackage {
182 /// The [`Package`] is already downloaded.
183 Ready(Package),
184 /// Not yet downloaded. Here is the URL to download the [`Package`] from.
185 Download {
186 /// URL to download the content.
187 url: String,
188 /// Text to display to the user of what is being downloaded.
189 descriptor: String,
190 /// Authorization data that may be required to attach when downloading.
191 authorization: Option<String>,
192 },
193}
194
195/// A blanket implementation forwards all methods to [`Source`].
196impl<'a, T: Source + ?Sized + 'a> Source for &'a mut T {
197 fn source_id(&self) -> SourceId {
198 (**self).source_id()
199 }
200
201 fn replaced_source_id(&self) -> SourceId {
202 (**self).replaced_source_id()
203 }
204
205 fn supports_checksums(&self) -> bool {
206 (**self).supports_checksums()
207 }
208
209 fn requires_precise(&self) -> bool {
210 (**self).requires_precise()
211 }
212
213 fn query(
214 &mut self,
215 dep: &Dependency,
216 kind: QueryKind,
217 f: &mut dyn FnMut(IndexSummary),
218 ) -> Poll<CargoResult<()>> {
219 (**self).query(dep, kind, f)
220 }
221
222 fn invalidate_cache(&mut self) {
223 (**self).invalidate_cache()
224 }
225
226 fn set_quiet(&mut self, quiet: bool) {
227 (**self).set_quiet(quiet)
228 }
229
230 fn download(&mut self, id: PackageId) -> CargoResult<MaybePackage> {
231 (**self).download(id)
232 }
233
234 fn finish_download(&mut self, id: PackageId, data: Vec<u8>) -> CargoResult<Package> {
235 (**self).finish_download(id, data)
236 }
237
238 fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
239 (**self).fingerprint(pkg)
240 }
241
242 fn verify(&self, pkg: PackageId) -> CargoResult<()> {
243 (**self).verify(pkg)
244 }
245
246 fn describe(&self) -> String {
247 (**self).describe()
248 }
249
250 fn is_replaced(&self) -> bool {
251 (**self).is_replaced()
252 }
253
254 fn add_to_yanked_whitelist(&mut self, pkgs: &[PackageId]) {
255 (**self).add_to_yanked_whitelist(pkgs);
256 }
257
258 fn is_yanked(&mut self, pkg: PackageId) -> Poll<CargoResult<bool>> {
259 (**self).is_yanked(pkg)
260 }
261
262 fn block_until_ready(&mut self) -> CargoResult<()> {
263 (**self).block_until_ready()
264 }
265}
266
267/// A [`HashMap`] of [`SourceId`] to `Box<Source>`.
268#[derive(Default)]
269pub struct SourceMap<'src> {
270 map: HashMap<SourceId, Box<dyn Source + 'src>>,
271}
272
273// `impl Debug` on source requires specialization, if even desirable at all.
274impl<'src> fmt::Debug for SourceMap<'src> {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 write!(f, "SourceMap ")?;
277 f.debug_set().entries(self.map.keys()).finish()
278 }
279}
280
281impl<'src> SourceMap<'src> {
282 /// Creates an empty map.
283 pub fn new() -> SourceMap<'src> {
284 SourceMap {
285 map: HashMap::new(),
286 }
287 }
288
289 /// Like `HashMap::get`.
290 pub fn get(&self, id: SourceId) -> Option<&(dyn Source + 'src)> {
291 self.map.get(&id).map(|s| s.as_ref())
292 }
293
294 /// Like `HashMap::get_mut`.
295 pub fn get_mut(&mut self, id: SourceId) -> Option<&mut (dyn Source + 'src)> {
296 self.map.get_mut(&id).map(|s| s.as_mut())
297 }
298
299 /// Like `HashMap::insert`, but derives the [`SourceId`] key from the [`Source`].
300 pub fn insert(&mut self, source: Box<dyn Source + 'src>) {
301 let id = source.source_id();
302 self.map.insert(id, source);
303 }
304
305 /// Like `HashMap::len`.
306 pub fn len(&self) -> usize {
307 self.map.len()
308 }
309
310 /// Like `HashMap::iter_mut`.
311 pub fn sources_mut<'a>(
312 &'a mut self,
313 ) -> impl Iterator<Item = (&'a SourceId, &'a mut (dyn Source + 'src))> {
314 self.map.iter_mut().map(|(a, b)| (a, &mut **b))
315 }
316
317 /// Merge the given map into self.
318 pub fn add_source_map(&mut self, other: SourceMap<'src>) {
319 for (key, value) in other.map {
320 self.map.entry(key).or_insert(value);
321 }
322 }
323}