]> git.proxmox.com Git - cargo.git/blame - src/cargo/core/package.rs
Stabilize namespaced and weak dependency features.
[cargo.git] / src / cargo / core / package.rs
CommitLineData
5f616eb1 1use std::cell::{Cell, Ref, RefCell, RefMut};
fa0787aa 2use std::cmp::Ordering;
bcfdf9fb 3use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
e723d41c 4use std::fmt;
c84bc16b 5use std::hash;
468f243e 6use std::mem;
a6dad622 7use std::path::{Path, PathBuf};
98b6f816 8use std::rc::Rc;
e5a11190 9use std::time::{Duration, Instant};
b56b61c5 10
3a18c89a 11use anyhow::Context;
a46df8fe 12use bytesize::ByteSize;
e5a11190
E
13use curl::easy::{Easy, HttpVersion};
14use curl::multi::{EasyHandle, Multi};
a46df8fe 15use lazycell::LazyCell;
9ed82b57 16use log::{debug, warn};
41b6f526 17use semver::Version;
9ed82b57 18use serde::Serialize;
e05b4dc8 19
944f5049
EH
20use crate::core::compiler::{CompileKind, RustcTargetData};
21use crate::core::dependency::DepKind;
3d5a9083 22use crate::core::resolver::features::ForceAllTargets;
944f5049 23use crate::core::resolver::{HasDevUnits, Resolve};
04ddd4d0
DW
24use crate::core::source::MaybePackage;
25use crate::core::{Dependency, Manifest, PackageId, SourceId, Target};
bcfdf9fb 26use crate::core::{SourceMap, Summary, Workspace};
04ddd4d0 27use crate::ops;
b48ae60e 28use crate::util::config::PackageCacheLock;
ebca5190 29use crate::util::errors::{CargoResult, HttpNot200};
7f73a6c7 30use crate::util::interning::InternedString;
04ddd4d0 31use crate::util::network::Retry;
7d7fe679 32use crate::util::{self, internal, Config, Progress, ProgressStyle};
f4d6d021 33
787e75b7
EH
34pub const MANIFEST_PREAMBLE: &str = "\
35# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
36#
37# When uploading crates to the registry Cargo will automatically
38# \"normalize\" Cargo.toml files for maximal compatibility
39# with all versions of Cargo and also rewrite `path` dependencies
7c66ef51 40# to registry (e.g., crates.io) dependencies.
787e75b7 41#
7c66ef51
EH
42# If you are reading this file be aware that the original Cargo.toml
43# will likely look very different (and much more reasonable).
44# See Cargo.toml.orig for the original contents.
787e75b7
EH
45";
46
bacb6be3 47/// Information about a package that is available somewhere in the file system.
c7641c24 48///
bacb6be3 49/// A package is a `Cargo.toml` file plus all the files that are part of it.
f7c91ba6
AR
50//
51// TODO: is `manifest_path` a relic?
de70d817 52#[derive(Clone)]
f4d6d021 53pub struct Package {
fbd34a69
AC
54 inner: Rc<PackageInner>,
55}
56
57#[derive(Clone)]
58struct PackageInner {
f7c91ba6 59 /// The package's manifest.
1ce76415 60 manifest: Manifest,
f7c91ba6 61 /// The root of the package.
a6dad622 62 manifest_path: PathBuf,
f4d6d021
CL
63}
64
fa0787aa
EH
65impl Ord for Package {
66 fn cmp(&self, other: &Package) -> Ordering {
dae87a26 67 self.package_id().cmp(&other.package_id())
fa0787aa
EH
68 }
69}
70
71impl PartialOrd for Package {
72 fn partial_cmp(&self, other: &Package) -> Option<Ordering> {
73 Some(self.cmp(other))
74 }
75}
76
624f9dbc 77/// A Package in a form where `Serialize` can be derived.
a5a298f1 78#[derive(Serialize)]
b2cceaf7
EH
79pub struct SerializedPackage {
80 name: InternedString,
81 version: Version,
dae87a26 82 id: PackageId,
b2cceaf7
EH
83 license: Option<String>,
84 license_file: Option<String>,
85 description: Option<String>,
e5a11190 86 source: SourceId,
b2cceaf7
EH
87 dependencies: Vec<Dependency>,
88 targets: Vec<Target>,
89 features: BTreeMap<InternedString, Vec<InternedString>>,
90 manifest_path: PathBuf,
91 metadata: Option<toml::Value>,
92 publish: Option<Vec<String>>,
93 authors: Vec<String>,
94 categories: Vec<String>,
95 keywords: Vec<String>,
96 readme: Option<String>,
97 repository: Option<String>,
98 homepage: Option<String>,
99 documentation: Option<String>,
100 edition: String,
101 links: Option<String>,
ecc87b17 102 #[serde(skip_serializing_if = "Option::is_none")]
b2cceaf7 103 metabuild: Option<Vec<String>>,
f9a56257 104 default_run: Option<String>,
390af271 105 rust_version: Option<String>,
d28ce723
YK
106}
107
f4d6d021 108impl Package {
f7c91ba6 109 /// Creates a package from a manifest and its location.
1e682848 110 pub fn new(manifest: Manifest, manifest_path: &Path) -> Package {
5accea9f 111 Package {
fbd34a69
AC
112 inner: Rc::new(PackageInner {
113 manifest,
114 manifest_path: manifest_path.to_path_buf(),
115 }),
5accea9f 116 }
0e619d88
YK
117 }
118
f7c91ba6 119 /// Gets the manifest dependencies.
1e682848 120 pub fn dependencies(&self) -> &[Dependency] {
fbd34a69 121 self.manifest().dependencies()
1e682848 122 }
f7c91ba6 123 /// Gets the manifest.
1e682848 124 pub fn manifest(&self) -> &Manifest {
fbd34a69 125 &self.inner.manifest
1e682848 126 }
014ef2e4
AC
127 /// Gets the manifest.
128 pub fn manifest_mut(&mut self) -> &mut Manifest {
fbd34a69 129 &mut Rc::make_mut(&mut self.inner).manifest
014ef2e4 130 }
f7c91ba6 131 /// Gets the path to the manifest.
1e682848 132 pub fn manifest_path(&self) -> &Path {
fbd34a69 133 &self.inner.manifest_path
1e682848 134 }
f7c91ba6 135 /// Gets the name of the package.
1e682848
AC
136 pub fn name(&self) -> InternedString {
137 self.package_id().name()
138 }
f7c91ba6 139 /// Gets the `PackageId` object for the package (fully defines a package).
dae87a26 140 pub fn package_id(&self) -> PackageId {
fbd34a69 141 self.manifest().package_id()
1e682848 142 }
f7c91ba6 143 /// Gets the root folder of the package.
1e682848 144 pub fn root(&self) -> &Path {
fbd34a69 145 self.manifest_path().parent().unwrap()
1e682848 146 }
f7c91ba6 147 /// Gets the summary for the package.
1e682848 148 pub fn summary(&self) -> &Summary {
fbd34a69 149 self.manifest().summary()
1e682848 150 }
f7c91ba6 151 /// Gets the targets specified in the manifest.
45d49579 152 pub fn targets(&self) -> &[Target] {
fbd34a69 153 self.manifest().targets()
1e682848 154 }
1b36ddc1
JN
155 /// Gets the library crate for this package, if it exists.
156 pub fn library(&self) -> Option<&Target> {
157 self.targets().iter().find(|t| t.is_lib())
158 }
f7c91ba6 159 /// Gets the current package version.
1e682848
AC
160 pub fn version(&self) -> &Version {
161 self.package_id().version()
162 }
f7c91ba6 163 /// Gets the package authors.
1e682848 164 pub fn authors(&self) -> &Vec<String> {
fbd34a69 165 &self.manifest().metadata().authors
1e682848 166 }
ae57964c
NCA
167
168 /// Returns `None` if the package is set to publish.
169 /// Returns `Some(allowed_registries)` if publishing is limited to specified
170 /// registries or if package is set to not publish.
1e682848 171 pub fn publish(&self) -> &Option<Vec<String>> {
fbd34a69 172 self.manifest().publish()
1e682848 173 }
944f5049
EH
174 /// Returns `true` if this package is a proc-macro.
175 pub fn proc_macro(&self) -> bool {
176 self.targets().iter().any(|target| target.proc_macro())
177 }
c221fec9
DO
178 /// Gets the package's minimum Rust version.
179 pub fn rust_version(&self) -> Option<&str> {
180 self.manifest().rust_version()
181 }
51c9cf0f 182
f7c91ba6 183 /// Returns `true` if the package uses a custom build script for any target.
71a89ef4 184 pub fn has_custom_build(&self) -> bool {
9e779198 185 self.targets().iter().any(|t| t.is_custom_build())
71a89ef4 186 }
ddf3c795 187
e5a11190 188 pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Package {
99ec335f 189 Package {
fbd34a69
AC
190 inner: Rc::new(PackageInner {
191 manifest: self.manifest().clone().map_source(to_replace, replace_with),
192 manifest_path: self.manifest_path().to_owned(),
193 }),
99ec335f
AC
194 }
195 }
57db8bde 196
787e75b7 197 pub fn to_registry_toml(&self, ws: &Workspace<'_>) -> CargoResult<String> {
90887707
EH
198 let manifest = self
199 .manifest()
200 .original()
787e75b7 201 .prepare_for_publish(ws, self.root())?;
c86a7bcd 202 let toml = toml::to_string(&manifest)?;
787e75b7 203 Ok(format!("{}\n{}", MANIFEST_PREAMBLE, toml))
57db8bde 204 }
7d202307
EH
205
206 /// Returns if package should include `Cargo.lock`.
207 pub fn include_lockfile(&self) -> bool {
34307c61 208 self.targets().iter().any(|t| t.is_example() || t.is_bin())
7d202307 209 }
b2cceaf7 210
43a063c8 211 pub fn serialized(&self) -> SerializedPackage {
b2cceaf7
EH
212 let summary = self.manifest().summary();
213 let package_id = summary.package_id();
214 let manmeta = self.manifest().metadata();
215 // Filter out metabuild targets. They are an internal implementation
216 // detail that is probably not relevant externally. There's also not a
217 // real path to show in `src_path`, and this avoids changing the format.
218 let targets: Vec<Target> = self
219 .manifest()
220 .targets()
221 .iter()
222 .filter(|t| t.src_path().is_path())
223 .cloned()
224 .collect();
43a063c8
EH
225 // Convert Vec<FeatureValue> to Vec<InternedString>
226 let features = summary
227 .features()
228 .iter()
229 .map(|(k, v)| {
230 (
231 *k,
232 v.iter()
233 .map(|fv| InternedString::new(&fv.to_string()))
234 .collect(),
235 )
236 })
237 .collect();
b2cceaf7
EH
238
239 SerializedPackage {
240 name: package_id.name(),
241 version: package_id.version().clone(),
242 id: package_id,
243 license: manmeta.license.clone(),
244 license_file: manmeta.license_file.clone(),
245 description: manmeta.description.clone(),
246 source: summary.source_id(),
247 dependencies: summary.dependencies().to_vec(),
248 targets,
249 features,
250 manifest_path: self.manifest_path().to_path_buf(),
251 metadata: self.manifest().custom_metadata().cloned(),
252 authors: manmeta.authors.clone(),
253 categories: manmeta.categories.clone(),
254 keywords: manmeta.keywords.clone(),
255 readme: manmeta.readme.clone(),
256 repository: manmeta.repository.clone(),
257 homepage: manmeta.homepage.clone(),
258 documentation: manmeta.documentation.clone(),
259 edition: self.manifest().edition().to_string(),
260 links: self.manifest().links().map(|s| s.to_owned()),
261 metabuild: self.manifest().metabuild().cloned(),
262 publish: self.publish().as_ref().cloned(),
f9a56257 263 default_run: self.manifest().default_run().map(|s| s.to_owned()),
390af271 264 rust_version: self.rust_version().map(|s| s.to_owned()),
b2cceaf7
EH
265 }
266 }
f4d6d021 267}
d70ce6b6 268
213afc02 269impl fmt::Display for Package {
b8b7faee 270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
7a2facba 271 write!(f, "{}", self.summary().package_id())
da0ec9a3
YK
272 }
273}
274
de70d817 275impl fmt::Debug for Package {
b8b7faee 276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
de70d817 277 f.debug_struct("Package")
dae87a26 278 .field("id", &self.summary().package_id())
de70d817
AC
279 .field("..", &"..")
280 .finish()
281 }
282}
283
9cbb91ac
YKCL
284impl PartialEq for Package {
285 fn eq(&self, other: &Package) -> bool {
7a2facba 286 self.package_id() == other.package_id()
9cbb91ac
YKCL
287 }
288}
289
c84bc16b
AC
290impl Eq for Package {}
291
973f6712
RG
292impl hash::Hash for Package {
293 fn hash<H: hash::Hasher>(&self, into: &mut H) {
e56965fb 294 self.package_id().hash(into)
c84bc16b
AC
295 }
296}
297
aa8e2577
EH
298/// A set of packages, with the intent to download.
299///
300/// This is primarily used to convert a set of `PackageId`s to `Package`s. It
301/// will download as needed, or used the cached download if available.
2d25fcac 302pub struct PackageSet<'cfg> {
fbd34a69 303 packages: HashMap<PackageId, LazyCell<Package>>,
b56b61c5 304 sources: RefCell<SourceMap<'cfg>>,
c94804bd 305 config: &'cfg Config,
0ea0c8ba 306 multi: Multi,
aa8e2577 307 /// Used to prevent reusing the PackageSet to download twice.
0ea0c8ba 308 downloading: Cell<bool>,
aa8e2577 309 /// Whether or not to use curl HTTP/2 multiplexing.
f4dcb5c1 310 multiplexing: bool,
7b1a0dc5
AC
311}
312
aa8e2577 313/// Helper for downloading crates.
46615d29 314pub struct Downloads<'a, 'cfg> {
0ea0c8ba 315 set: &'a PackageSet<'cfg>,
aa8e2577
EH
316 /// When a download is started, it is added to this map. The key is a
317 /// "token" (see `Download::token`). It is removed once the download is
318 /// finished.
c14839b5 319 pending: HashMap<usize, (Download<'cfg>, EasyHandle)>,
aa8e2577
EH
320 /// Set of packages currently being downloaded. This should stay in sync
321 /// with `pending`.
468f243e 322 pending_ids: HashSet<PackageId>,
aa8e2577
EH
323 /// The final result of each download. A pair `(token, result)`. This is a
324 /// temporary holding area, needed because curl can report multiple
325 /// downloads at once, but the main loop (`wait`) is written to only
326 /// handle one at a time.
0b0f089d 327 results: Vec<(usize, Result<(), curl::Error>)>,
aa8e2577 328 /// The next ID to use for creating a token (see `Download::token`).
468f243e 329 next: usize,
aa8e2577 330 /// Progress bar.
a46df8fe 331 progress: RefCell<Option<Progress<'cfg>>>,
aa8e2577 332 /// Number of downloads that have successfully finished.
468f243e 333 downloads_finished: usize,
aa8e2577 334 /// Total bytes for all successfully downloaded packages.
a46df8fe 335 downloaded_bytes: u64,
aa8e2577 336 /// Size (in bytes) and package name of the largest downloaded package.
a46df8fe 337 largest: (u64, String),
aa8e2577 338 /// Time when downloading started.
a46df8fe 339 start: Instant,
aa8e2577 340 /// Indicates *all* downloads were successful.
5a5b611a 341 success: bool,
4e1e3f74
AC
342
343 /// Timeout management, both of timeout thresholds as well as whether or not
344 /// our connection has timed out (and accompanying message if it has).
345 ///
346 /// Note that timeout management is done manually here instead of in libcurl
347 /// because we want to apply timeouts to an entire batch of operations, not
f7c91ba6 348 /// any one particular single operation.
aa8e2577
EH
349 timeout: ops::HttpTimeout,
350 /// Last time bytes were received.
351 updated_at: Cell<Instant>,
352 /// This is a slow-speed check. It is reset to `now + timeout_duration`
353 /// every time at least `threshold` bytes are received. If the current
354 /// time ever exceeds `next_speed_check`, then give up and report a
355 /// timeout error.
356 next_speed_check: Cell<Instant>,
357 /// This is the slow-speed threshold byte count. It starts at the
358 /// configured threshold value (default 10), and is decremented by the
359 /// number of bytes received in each chunk. If it is <= zero, the
360 /// threshold has been met and data is being received fast enough not to
361 /// trigger a timeout; reset `next_speed_check` and set this back to the
362 /// configured threshold.
363 next_speed_check_bytes_threshold: Cell<u64>,
5217280e
AC
364 /// Global filesystem lock to ensure only one Cargo is downloading at a
365 /// time.
366 _lock: PackageCacheLock<'cfg>,
468f243e
AC
367}
368
c14839b5 369struct Download<'cfg> {
f7c91ba6 370 /// The token for this download, used as the key of the `Downloads::pending` map
0b0f089d 371 /// and stored in `EasyHandle` as well.
468f243e 372 token: usize,
0b0f089d 373
f7c91ba6 374 /// The package that we're downloading.
468f243e 375 id: PackageId,
0b0f089d 376
f7c91ba6 377 /// Actual downloaded data, updated throughout the lifetime of this download.
468f243e 378 data: RefCell<Vec<u8>>,
0b0f089d
AC
379
380 /// The URL that we're downloading from, cached here for error messages and
381 /// reenqueuing.
468f243e 382 url: String,
0b0f089d 383
f7c91ba6 384 /// A descriptive string to print when we've finished downloading this crate.
468f243e 385 descriptor: String,
0b0f089d 386
f7c91ba6 387 /// Statistics updated from the progress callback in libcurl.
a46df8fe
AC
388 total: Cell<u64>,
389 current: Cell<u64>,
0b0f089d 390
f7c91ba6 391 /// The moment we started this transfer at.
a46df8fe 392 start: Instant,
0b0f089d 393 timed_out: Cell<Option<String>>,
0b0f089d
AC
394
395 /// Logic used to track retrying this download if it's a spurious failure.
c14839b5 396 retry: Retry<'cfg>,
d70ce6b6
CL
397}
398
2d25fcac 399impl<'cfg> PackageSet<'cfg> {
c94804bd
AC
400 pub fn new(
401 package_ids: &[PackageId],
402 sources: SourceMap<'cfg>,
403 config: &'cfg Config,
468f243e
AC
404 ) -> CargoResult<PackageSet<'cfg>> {
405 // We've enabled the `http2` feature of `curl` in Cargo, so treat
406 // failures here as fatal as it would indicate a build-time problem.
f4dcb5c1
AC
407 //
408 // Note that the multiplexing support is pretty new so we're having it
409 // off-by-default temporarily.
410 //
411 // Also note that pipelining is disabled as curl authors have indicated
412 // that it's buggy, and we've empirically seen that it's buggy with HTTP
413 // proxies.
468f243e 414 let mut multi = Multi::new();
2357bb04 415 let multiplexing = config.http_config()?.multiplexing.unwrap_or(true);
e5a11190
E
416 multi
417 .pipelining(false, multiplexing)
ebca5190 418 .with_context(|| "failed to enable multiplexing/pipelining in curl")?;
468f243e 419
f4dcb5c1
AC
420 // let's not flood crates.io with connections
421 multi.set_max_host_connections(2)?;
422
468f243e 423 Ok(PackageSet {
1e682848
AC
424 packages: package_ids
425 .iter()
dae87a26 426 .map(|&id| (id, LazyCell::new()))
1e682848 427 .collect(),
b56b61c5 428 sources: RefCell::new(sources),
c94804bd 429 config,
0ea0c8ba
AC
430 multi,
431 downloading: Cell::new(false),
f4dcb5c1 432 multiplexing,
468f243e 433 })
d70ce6b6
CL
434 }
435
e58c544f 436 pub fn package_ids(&self) -> impl Iterator<Item = PackageId> + '_ {
dae87a26 437 self.packages.keys().cloned()
04c6c748
AC
438 }
439
e58c544f 440 pub fn packages(&self) -> impl Iterator<Item = &Package> {
a031b03a
JG
441 self.packages.values().filter_map(|p| p.borrow())
442 }
443
0ea0c8ba
AC
444 pub fn enable_download<'a>(&'a self) -> CargoResult<Downloads<'a, 'cfg>> {
445 assert!(!self.downloading.replace(true));
4e1e3f74 446 let timeout = ops::HttpTimeout::new(self.config)?;
0ea0c8ba 447 Ok(Downloads {
a46df8fe 448 start: Instant::now(),
0ea0c8ba
AC
449 set: self,
450 next: 0,
451 pending: HashMap::new(),
452 pending_ids: HashSet::new(),
453 results: Vec::new(),
a46df8fe 454 progress: RefCell::new(Some(Progress::with_style(
0ea0c8ba
AC
455 "Downloading",
456 ProgressStyle::Ratio,
457 self.config,
a46df8fe 458 ))),
0ea0c8ba
AC
459 downloads_finished: 0,
460 downloaded_bytes: 0,
a46df8fe 461 largest: (0, String::new()),
5a5b611a 462 success: false,
4e1e3f74
AC
463 updated_at: Cell::new(Instant::now()),
464 timeout,
465 next_speed_check: Cell::new(Instant::now()),
466 next_speed_check_bytes_threshold: Cell::new(0),
5217280e 467 _lock: self.config.acquire_package_cache_lock()?,
0ea0c8ba
AC
468 })
469 }
e2637b65 470
fbd34a69 471 pub fn get_one(&self, id: PackageId) -> CargoResult<&Package> {
944f5049
EH
472 if let Some(pkg) = self.packages.get(&id).and_then(|slot| slot.borrow()) {
473 return Ok(pkg);
474 }
e2637b65
AC
475 Ok(self.get_many(Some(id))?.remove(0))
476 }
477
e06a911d 478 pub fn get_many(&self, ids: impl IntoIterator<Item = PackageId>) -> CargoResult<Vec<&Package>> {
e2637b65
AC
479 let mut pkgs = Vec::new();
480 let mut downloads = self.enable_download()?;
481 for id in ids {
482 pkgs.extend(downloads.start(id)?);
0ea0c8ba 483 }
e2637b65
AC
484 while downloads.remaining() > 0 {
485 pkgs.push(downloads.wait()?);
486 }
5a5b611a 487 downloads.success = true;
e2637b65 488 Ok(pkgs)
0ea0c8ba
AC
489 }
490
944f5049
EH
491 /// Downloads any packages accessible from the give root ids.
492 pub fn download_accessible(
493 &self,
494 resolve: &Resolve,
495 root_ids: &[PackageId],
496 has_dev_units: HasDevUnits,
3fd28143 497 requested_kinds: &[CompileKind],
5e11afc1 498 target_data: &RustcTargetData<'cfg>,
3d5a9083 499 force_all_targets: ForceAllTargets,
260a3555 500 ) -> CargoResult<()> {
944f5049
EH
501 fn collect_used_deps(
502 used: &mut BTreeSet<PackageId>,
503 resolve: &Resolve,
504 pkg_id: PackageId,
505 has_dev_units: HasDevUnits,
3fd28143 506 requested_kinds: &[CompileKind],
5e11afc1 507 target_data: &RustcTargetData<'_>,
3d5a9083 508 force_all_targets: ForceAllTargets,
944f5049
EH
509 ) -> CargoResult<()> {
510 if !used.insert(pkg_id) {
511 return Ok(());
512 }
27c10306 513 let filtered_deps = PackageSet::filter_deps(
514 pkg_id,
515 resolve,
516 has_dev_units,
517 requested_kinds,
518 target_data,
519 force_all_targets,
520 );
0b543baa 521 for pkg_id in filtered_deps {
944f5049
EH
522 collect_used_deps(
523 used,
524 resolve,
f1278314 525 pkg_id,
944f5049 526 has_dev_units,
3fd28143 527 requested_kinds,
944f5049 528 target_data,
3d5a9083 529 force_all_targets,
944f5049
EH
530 )?;
531 }
532 Ok(())
533 }
534
535 // This is sorted by PackageId to get consistent behavior and error
536 // messages for Cargo's testsuite. Perhaps there is a better ordering
537 // that optimizes download time?
538 let mut to_download = BTreeSet::new();
539
540 for id in root_ids {
541 collect_used_deps(
542 &mut to_download,
543 resolve,
544 *id,
545 has_dev_units,
3fd28143 546 requested_kinds,
944f5049 547 target_data,
3d5a9083 548 force_all_targets,
944f5049
EH
549 )?;
550 }
260a3555
JG
551 self.get_many(to_download.into_iter())?;
552 Ok(())
944f5049
EH
553 }
554
27c10306 555 /// Check if there are any dependency packages that do not have any libs.
556 pub(crate) fn no_lib_pkgs(
557 &self,
558 resolve: &Resolve,
559 root_ids: &[PackageId],
560 has_dev_units: HasDevUnits,
561 requested_kinds: &[CompileKind],
562 target_data: &RustcTargetData<'_>,
563 force_all_targets: ForceAllTargets,
61780a90 564 ) -> BTreeMap<PackageId, Vec<&Package>> {
0b543baa 565 root_ids
f1278314 566 .iter()
0b543baa 567 .map(|&root_id| {
568 let pkgs = PackageSet::filter_deps(
569 root_id,
570 resolve,
571 has_dev_units,
572 requested_kinds,
573 target_data,
574 force_all_targets,
575 )
576 .filter_map(|package_id| {
577 if let Ok(dep_pkg) = self.get_one(package_id) {
578 if !dep_pkg.targets().iter().any(|t| t.is_lib()) {
579 Some(dep_pkg)
580 } else {
581 None
582 }
61780a90 583 } else {
584 None
27c10306 585 }
0b543baa 586 })
587 .collect();
588 (root_id, pkgs)
61780a90 589 })
0b543baa 590 .collect()
27c10306 591 }
592
0b543baa 593 fn filter_deps<'a>(
27c10306 594 pkg_id: PackageId,
0b543baa 595 resolve: &'a Resolve,
27c10306 596 has_dev_units: HasDevUnits,
0b543baa 597 requested_kinds: &'a [CompileKind],
598 target_data: &'a RustcTargetData<'_>,
27c10306 599 force_all_targets: ForceAllTargets,
0b543baa 600 ) -> impl Iterator<Item = PackageId> + 'a {
f1278314 601 resolve
602 .deps(pkg_id)
0b543baa 603 .filter(move |&(_id, deps)| {
f1278314 604 deps.iter().any(|dep| {
605 if dep.kind() == DepKind::Development && has_dev_units == HasDevUnits::No {
27c10306 606 return false;
607 }
f1278314 608 if force_all_targets == ForceAllTargets::No {
609 let activated = requested_kinds
610 .iter()
611 .chain(Some(&CompileKind::Host))
612 .any(|kind| target_data.dep_platform_activated(dep, *kind));
613 if !activated {
614 return false;
615 }
616 }
617 true
618 })
27c10306 619 })
f1278314 620 .map(|(pkg_id, _)| pkg_id)
0b543baa 621 .into_iter()
27c10306 622 }
623
b8b7faee 624 pub fn sources(&self) -> Ref<'_, SourceMap<'cfg>> {
0ea0c8ba
AC
625 self.sources.borrow()
626 }
5f616eb1
EH
627
628 pub fn sources_mut(&self) -> RefMut<'_, SourceMap<'cfg>> {
629 self.sources.borrow_mut()
630 }
1f14fa31
EH
631
632 /// Merge the given set into self.
633 pub fn add_set(&mut self, set: PackageSet<'cfg>) {
634 assert!(!self.downloading.get());
635 assert!(!set.downloading.get());
636 for (pkg_id, p_cell) in set.packages {
637 self.packages.entry(pkg_id).or_insert(p_cell);
638 }
639 let mut sources = self.sources.borrow_mut();
640 let other_sources = set.sources.into_inner();
641 sources.add_source_map(other_sources);
642 }
0ea0c8ba
AC
643}
644
38fd476a
EH
645// When dynamically linked against libcurl, we want to ignore some failures
646// when using old versions that don't support certain features.
647macro_rules! try_old_curl {
648 ($e:expr, $msg:expr) => {
649 let result = $e;
650 if cfg!(target_os = "macos") {
651 if let Err(e) = result {
652 warn!("ignoring libcurl {} error: {}", $msg, e);
653 }
654 } else {
3a18c89a
AC
655 result.with_context(|| {
656 anyhow::format_err!("failed to enable {}, is curl not built right?", $msg)
38fd476a
EH
657 })?;
658 }
659 };
660}
661
0ea0c8ba 662impl<'a, 'cfg> Downloads<'a, 'cfg> {
468f243e
AC
663 /// Starts to download the package for the `id` specified.
664 ///
665 /// Returns `None` if the package is queued up for download and will
666 /// eventually be returned from `wait_for_download`. Returns `Some(pkg)` if
667 /// the package is ready and doesn't need to be downloaded.
fbd34a69 668 pub fn start(&mut self, id: PackageId) -> CargoResult<Option<&'a Package>> {
c4e5670b 669 self.start_inner(id)
ebca5190 670 .with_context(|| format!("failed to download `{}`", id))
2378a9be
EH
671 }
672
fbd34a69 673 fn start_inner(&mut self, id: PackageId) -> CargoResult<Option<&'a Package>> {
468f243e
AC
674 // First up see if we've already cached this package, in which case
675 // there's nothing to do.
e5a11190
E
676 let slot = self
677 .set
678 .packages
dae87a26 679 .get(&id)
1e682848 680 .ok_or_else(|| internal(format!("couldn't find `{}` in package set", id)))?;
b56b61c5 681 if let Some(pkg) = slot.borrow() {
7b1a0dc5 682 return Ok(Some(pkg));
b56b61c5 683 }
468f243e
AC
684
685 // Ask the original source fo this `PackageId` for the corresponding
686 // package. That may immediately come back and tell us that the package
687 // is ready, or it could tell us that it needs to be downloaded.
0ea0c8ba 688 let mut sources = self.set.sources.borrow_mut();
1e682848
AC
689 let source = sources
690 .get_mut(id.source_id())
691 .ok_or_else(|| internal(format!("couldn't find source for `{}`", id)))?;
692 let pkg = source
693 .download(id)
9099b491 694 .with_context(|| "unable to get packages from source")?;
468f243e 695 let (url, descriptor) = match pkg {
7b1a0dc5 696 MaybePackage::Ready(pkg) => {
468f243e 697 debug!("{} doesn't need a download", id);
fbd34a69 698 assert!(slot.fill(pkg).is_ok());
e5a11190 699 return Ok(Some(slot.borrow().unwrap()));
7b1a0dc5 700 }
468f243e
AC
701 MaybePackage::Download { url, descriptor } => (url, descriptor),
702 };
703
704 // Ok we're going to download this crate, so let's set up all our
705 // internal state and hand off an `Easy` handle to our libcurl `Multi`
706 // handle. This won't actually start the transfer, but later it'll
b4cd6095 707 // happen during `wait_for_download`
0ea0c8ba
AC
708 let token = self.next;
709 self.next += 1;
468f243e 710 debug!("downloading {} as {}", id, token);
dae87a26 711 assert!(self.pending_ids.insert(id));
468f243e 712
4e1e3f74 713 let (mut handle, _timeout) = ops::http_handle_and_timeout(self.set.config)?;
468f243e
AC
714 handle.get(true)?;
715 handle.url(&url)?;
716 handle.follow_location(true)?; // follow redirects
717
718 // Enable HTTP/2 to be used as it'll allow true multiplexing which makes
c181f490
AC
719 // downloads much faster.
720 //
721 // Currently Cargo requests the `http2` feature of the `curl` crate
722 // which means it should always be built in. On OSX, however, we ship
723 // cargo still linked against the system libcurl. Building curl with
724 // ALPN support for HTTP/2 requires newer versions of OSX (the
725 // SecureTransport API) than we want to ship Cargo for. By linking Cargo
726 // against the system libcurl then older curl installations won't use
727 // HTTP/2 but newer ones will. All that to basically say we ignore
728 // errors here on OSX, but consider this a fatal error to not activate
729 // HTTP/2 on all other platforms.
f4dcb5c1 730 if self.set.multiplexing {
38fd476a 731 try_old_curl!(handle.http_version(HttpVersion::V2), "HTTP2");
c181f490
AC
732 } else {
733 handle.http_version(HttpVersion::V11)?;
f4dcb5c1 734 }
468f243e
AC
735
736 // This is an option to `libcurl` which indicates that if there's a
737 // bunch of parallel requests to the same host they all wait until the
738 // pipelining status of the host is known. This means that we won't
739 // initiate dozens of connections to crates.io, but rather only one.
740 // Once the main one is opened we realized that pipelining is possible
741 // and multiplexing is possible with static.crates.io. All in all this
742 // reduces the number of connections done to a more manageable state.
38fd476a 743 try_old_curl!(handle.pipewait(true), "pipewait");
468f243e
AC
744
745 handle.write_function(move |buf| {
746 debug!("{} - {} bytes of data", token, buf.len());
747 tls::with(|downloads| {
78922ca8 748 if let Some(downloads) = downloads {
e5a11190
E
749 downloads.pending[&token]
750 .0
751 .data
78922ca8
AC
752 .borrow_mut()
753 .extend_from_slice(buf);
754 }
468f243e
AC
755 });
756 Ok(buf.len())
757 })?;
758
0ea0c8ba
AC
759 handle.progress(true)?;
760 handle.progress_function(move |dl_total, dl_cur, _, _| {
e5a11190
E
761 tls::with(|downloads| match downloads {
762 Some(d) => d.progress(token, dl_total as u64, dl_cur as u64),
763 None => false,
0ea0c8ba
AC
764 })
765 })?;
766
e2637b65
AC
767 // If the progress bar isn't enabled then it may be awhile before the
768 // first crate finishes downloading so we inform immediately that we're
769 // downloading crates here.
e5a11190 770 if self.downloads_finished == 0
92485f8c 771 && self.pending.is_empty()
e5a11190 772 && !self.progress.borrow().as_ref().unwrap().is_enabled()
e2637b65 773 {
e5a11190
E
774 self.set
775 .config
776 .shell()
777 .status("Downloading", "crates ...")?;
e2637b65
AC
778 }
779
468f243e
AC
780 let dl = Download {
781 token,
782 data: RefCell::new(Vec::new()),
dae87a26 783 id,
468f243e
AC
784 url,
785 descriptor,
0ea0c8ba
AC
786 total: Cell::new(0),
787 current: Cell::new(0),
a46df8fe 788 start: Instant::now(),
0b0f089d 789 timed_out: Cell::new(None),
c14839b5 790 retry: Retry::new(self.set.config)?,
468f243e 791 };
0ea0c8ba 792 self.enqueue(dl, handle)?;
a46df8fe 793 self.tick(WhyTick::DownloadStarted)?;
468f243e
AC
794
795 Ok(None)
7b1a0dc5
AC
796 }
797
f7c91ba6 798 /// Returns the number of crates that are still downloading.
0ea0c8ba
AC
799 pub fn remaining(&self) -> usize {
800 self.pending.len()
7b1a0dc5
AC
801 }
802
468f243e
AC
803 /// Blocks the current thread waiting for a package to finish downloading.
804 ///
805 /// This method will wait for a previously enqueued package to finish
806 /// downloading and return a reference to it after it's done downloading.
807 ///
808 /// # Panics
809 ///
810 /// This function will panic if there are no remaining downloads.
fbd34a69 811 pub fn wait(&mut self) -> CargoResult<&'a Package> {
468f243e 812 let (dl, data) = loop {
0ea0c8ba
AC
813 assert_eq!(self.pending.len(), self.pending_ids.len());
814 let (token, result) = self.wait_for_curl()?;
468f243e
AC
815 debug!("{} finished with {:?}", token, result);
816
e5a11190
E
817 let (mut dl, handle) = self
818 .pending
819 .remove(&token)
468f243e 820 .expect("got a token for a non-in-progress transfer");
6a6fa368 821 let data = mem::take(&mut *dl.data.borrow_mut());
0ea0c8ba 822 let mut handle = self.set.multi.remove(handle)?;
304871c7 823 self.pending_ids.remove(&dl.id);
468f243e
AC
824
825 // Check if this was a spurious error. If it was a spurious error
826 // then we want to re-enqueue our request for another attempt and
827 // then we wait for another request to finish.
828 let ret = {
0b0f089d 829 let timed_out = &dl.timed_out;
c14839b5 830 let url = &dl.url;
e5a11190 831 dl.retry
04ddd4d0 832 .r#try(|| {
e5a11190
E
833 if let Err(e) = result {
834 // If this error is "aborted by callback" then that's
835 // probably because our progress callback aborted due to
836 // a timeout. We'll find out by looking at the
837 // `timed_out` field, looking for a descriptive message.
838 // If one is found we switch the error code (to ensure
839 // it's flagged as spurious) and then attach our extra
840 // information to the error.
841 if !e.is_aborted_by_callback() {
842 return Err(e.into());
843 }
0b0f089d 844
e5a11190
E
845 return Err(match timed_out.replace(None) {
846 Some(msg) => {
847 let code = curl_sys::CURLE_OPERATION_TIMEDOUT;
848 let mut err = curl::Error::new(code);
849 err.set_extra(msg);
850 err
851 }
852 None => e,
0b0f089d 853 }
e5a11190
E
854 .into());
855 }
468f243e 856
e5a11190
E
857 let code = handle.response_code()?;
858 if code != 200 && code != 0 {
859 let url = handle.effective_url()?.unwrap_or(url);
860 return Err(HttpNot200 {
861 code,
862 url: url.to_string(),
863 }
864 .into());
865 }
866 Ok(())
867 })
ebca5190 868 .with_context(|| format!("failed to download from `{}`", dl.url))?
468f243e 869 };
304871c7
AC
870 match ret {
871 Some(()) => break (dl, data),
872 None => {
dae87a26 873 self.pending_ids.insert(dl.id);
304871c7
AC
874 self.enqueue(dl, handle)?
875 }
468f243e 876 }
468f243e 877 };
775b4ebc
AC
878
879 // If the progress bar isn't enabled then we still want to provide some
ef7e98f5
AC
880 // semblance of progress of how we're downloading crates, and if the
881 // progress bar is enabled this provides a good log of what's happening.
882 self.progress.borrow_mut().as_mut().unwrap().clear();
e5a11190
E
883 self.set
884 .config
885 .shell()
886 .status("Downloaded", &dl.descriptor)?;
775b4ebc 887
0ea0c8ba
AC
888 self.downloads_finished += 1;
889 self.downloaded_bytes += dl.total.get();
a46df8fe
AC
890 if dl.total.get() > self.largest.0 {
891 self.largest = (dl.total.get(), dl.id.name().to_string());
892 }
893
894 // We're about to synchronously extract the crate below. While we're
895 // doing that our download progress won't actually be updated, nor do we
896 // have a great view into the progress of the extraction. Let's prepare
897 // the user for this CPU-heavy step if it looks like it'll take some
898 // time to do so.
899 if dl.total.get() < ByteSize::kb(400).0 {
900 self.tick(WhyTick::DownloadFinished)?;
901 } else {
902 self.tick(WhyTick::Extracting(&dl.id.name()))?;
903 }
468f243e
AC
904
905 // Inform the original source that the download is finished which
906 // should allow us to actually get the package and fill it in now.
0ea0c8ba 907 let mut sources = self.set.sources.borrow_mut();
7b1a0dc5 908 let source = sources
468f243e
AC
909 .get_mut(dl.id.source_id())
910 .ok_or_else(|| internal(format!("couldn't find source for `{}`", dl.id)))?;
0b0f089d 911 let start = Instant::now();
dae87a26 912 let pkg = source.finish_download(dl.id, data)?;
0b0f089d
AC
913
914 // Assume that no time has passed while we were calling
915 // `finish_download`, update all speed checks and timeout limits of all
916 // active downloads to make sure they don't fire because of a slowly
917 // extracted tarball.
918 let finish_dur = start.elapsed();
4e1e3f74 919 self.updated_at.set(self.updated_at.get() + finish_dur);
e5a11190
E
920 self.next_speed_check
921 .set(self.next_speed_check.get() + finish_dur);
0b0f089d 922
0ea0c8ba 923 let slot = &self.set.packages[&dl.id];
fbd34a69 924 assert!(slot.fill(pkg).is_ok());
b56b61c5 925 Ok(slot.borrow().unwrap())
d70ce6b6 926 }
d70ce6b6 927
c14839b5 928 fn enqueue(&mut self, dl: Download<'cfg>, handle: Easy) -> CargoResult<()> {
0ea0c8ba 929 let mut handle = self.set.multi.add(handle)?;
0b0f089d 930 let now = Instant::now();
468f243e 931 handle.set_token(dl.token)?;
4e1e3f74
AC
932 self.updated_at.set(now);
933 self.next_speed_check.set(now + self.timeout.dur);
e5a11190 934 self.next_speed_check_bytes_threshold
7cd4a94d 935 .set(u64::from(self.timeout.low_speed_limit));
0b0f089d 936 dl.timed_out.set(None);
0b0f089d
AC
937 dl.current.set(0);
938 dl.total.set(0);
468f243e
AC
939 self.pending.insert(dl.token, (dl, handle));
940 Ok(())
941 }
942
aa8e2577
EH
943 /// Block, waiting for curl. Returns a token and a `Result` for that token
944 /// (`Ok` means the download successfully finished).
0b0f089d 945 fn wait_for_curl(&mut self) -> CargoResult<(usize, Result<(), curl::Error>)> {
468f243e
AC
946 // This is the main workhorse loop. We use libcurl's portable `wait`
947 // method to actually perform blocking. This isn't necessarily too
948 // efficient in terms of fd management, but we should only be juggling
949 // a few anyway.
950 //
951 // Here we start off by asking the `multi` handle to do some work via
f7c91ba6 952 // the `perform` method. This will actually do I/O work (non-blocking)
468f243e
AC
953 // and attempt to make progress. Afterwards we ask about the `messages`
954 // contained in the handle which will inform us if anything has finished
955 // transferring.
956 //
957 // If we've got a finished transfer after all that work we break out
958 // and process the finished transfer at the end. Otherwise we need to
959 // actually block waiting for I/O to happen, which we achieve with the
960 // `wait` method on `multi`.
961 loop {
962 let n = tls::set(self, || {
e5a11190
E
963 self.set
964 .multi
965 .perform()
ebca5190 966 .with_context(|| "failed to perform http requests")
c94804bd 967 })?;
468f243e
AC
968 debug!("handles remaining: {}", n);
969 let results = &mut self.results;
bc942919 970 let pending = &self.pending;
0ea0c8ba 971 self.set.multi.messages(|msg| {
468f243e 972 let token = msg.token().expect("failed to read token");
bc942919 973 let handle = &pending[&token].1;
ef0b4776 974 if let Some(result) = msg.result_for(handle) {
0b0f089d 975 results.push((token, result));
468f243e
AC
976 } else {
977 debug!("message without a result (?)");
978 }
979 });
980
981 if let Some(pair) = results.pop() {
e5a11190 982 break Ok(pair);
468f243e 983 }
7cd4a94d
E
984 assert!(!self.pending.is_empty());
985 let timeout = self
986 .set
987 .multi
988 .get_timeout()?
989 .unwrap_or_else(|| Duration::new(5, 0));
e5a11190
E
990 self.set
991 .multi
992 .wait(&mut [], timeout)
ebca5190 993 .with_context(|| "failed to wait on curl `Multi`")?;
468f243e
AC
994 }
995 }
996
0b0f089d
AC
997 fn progress(&self, token: usize, total: u64, cur: u64) -> bool {
998 let dl = &self.pending[&token].0;
999 dl.total.set(total);
1000 let now = Instant::now();
3d20973e 1001 if cur > dl.current.get() {
4e1e3f74
AC
1002 let delta = cur - dl.current.get();
1003 let threshold = self.next_speed_check_bytes_threshold.get();
1004
0b0f089d 1005 dl.current.set(cur);
4e1e3f74 1006 self.updated_at.set(now);
0b0f089d 1007
4e1e3f74
AC
1008 if delta >= threshold {
1009 self.next_speed_check.set(now + self.timeout.dur);
e5a11190 1010 self.next_speed_check_bytes_threshold
7cd4a94d 1011 .set(u64::from(self.timeout.low_speed_limit));
4e1e3f74
AC
1012 } else {
1013 self.next_speed_check_bytes_threshold.set(threshold - delta);
0b0f089d
AC
1014 }
1015 }
7cd4a94d 1016 if self.tick(WhyTick::DownloadUpdate).is_err() {
e5a11190 1017 return false;
0b0f089d
AC
1018 }
1019
1020 // If we've spent too long not actually receiving any data we time out.
be020a55 1021 if now > self.updated_at.get() + self.timeout.dur {
4e1e3f74 1022 self.updated_at.set(now);
e5a11190
E
1023 let msg = format!(
1024 "failed to download any data for `{}` within {}s",
1025 dl.id,
1026 self.timeout.dur.as_secs()
1027 );
0b0f089d 1028 dl.timed_out.set(Some(msg));
e5a11190 1029 return false;
0b0f089d
AC
1030 }
1031
1032 // If we reached the point in time that we need to check our speed
1033 // limit, see if we've transferred enough data during this threshold. If
1034 // it fails this check then we fail because the download is going too
1035 // slowly.
4e1e3f74
AC
1036 if now >= self.next_speed_check.get() {
1037 self.next_speed_check.set(now + self.timeout.dur);
1038 assert!(self.next_speed_check_bytes_threshold.get() > 0);
e5a11190
E
1039 let msg = format!(
1040 "download of `{}` failed to transfer more \
1041 than {} bytes in {}s",
1042 dl.id,
1043 self.timeout.low_speed_limit,
1044 self.timeout.dur.as_secs()
1045 );
0b0f089d 1046 dl.timed_out.set(Some(msg));
e5a11190 1047 return false;
0b0f089d
AC
1048 }
1049
1050 true
1051 }
1052
b8b7faee 1053 fn tick(&self, why: WhyTick<'_>) -> CargoResult<()> {
a46df8fe
AC
1054 let mut progress = self.progress.borrow_mut();
1055 let progress = progress.as_mut().unwrap();
1056
1057 if let WhyTick::DownloadUpdate = why {
1058 if !progress.update_allowed() {
e5a11190 1059 return Ok(());
a46df8fe
AC
1060 }
1061 }
ebced4ac
MK
1062 let pending = self.pending.len();
1063 let mut msg = if pending == 1 {
1064 format!("{} crate", pending)
1065 } else {
1066 format!("{} crates", pending)
1067 };
a46df8fe
AC
1068 match why {
1069 WhyTick::Extracting(krate) => {
1070 msg.push_str(&format!(", extracting {} ...", krate));
1071 }
1072 _ => {
1073 let mut dur = Duration::new(0, 0);
1074 let mut remaining = 0;
1075 for (dl, _) in self.pending.values() {
1076 dur += dl.start.elapsed();
2245fc40
AC
1077 // If the total/current look weird just throw out the data
1078 // point, sounds like curl has more to learn before we have
1079 // the true information.
1080 if dl.total.get() >= dl.current.get() {
1081 remaining += dl.total.get() - dl.current.get();
1082 }
a46df8fe
AC
1083 }
1084 if remaining > 0 && dur > Duration::from_millis(500) {
1085 msg.push_str(&format!(", remaining bytes: {}", ByteSize(remaining)));
1086 }
1087 }
1088 }
1089 progress.print_now(&msg)
468f243e
AC
1090 }
1091}
1092
7cd4a94d 1093#[derive(Copy, Clone)]
a46df8fe
AC
1094enum WhyTick<'a> {
1095 DownloadStarted,
1096 DownloadUpdate,
1097 DownloadFinished,
1098 Extracting(&'a str),
1099}
1100
0ea0c8ba
AC
1101impl<'a, 'cfg> Drop for Downloads<'a, 'cfg> {
1102 fn drop(&mut self) {
1103 self.set.downloading.set(false);
a46df8fe
AC
1104 let progress = self.progress.get_mut().take().unwrap();
1105 // Don't print a download summary if we're not using a progress bar,
1106 // we've already printed lots of `Downloading...` items.
1107 if !progress.is_enabled() {
e5a11190 1108 return;
a46df8fe 1109 }
f7c91ba6 1110 // If we didn't download anything, no need for a summary.
a46df8fe 1111 if self.downloads_finished == 0 {
e5a11190 1112 return;
a46df8fe 1113 }
f7c91ba6 1114 // If an error happened, let's not clutter up the output.
5a5b611a 1115 if !self.success {
e5a11190 1116 return;
5a5b611a 1117 }
dd47416a
MK
1118 // pick the correct plural of crate(s)
1119 let crate_string = if self.downloads_finished == 1 {
1120 "crate"
1121 } else {
1122 "crates"
1123 };
e5a11190 1124 let mut status = format!(
dd47416a 1125 "{} {} ({}) in {}",
e5a11190 1126 self.downloads_finished,
dd47416a 1127 crate_string,
e5a11190
E
1128 ByteSize(self.downloaded_bytes),
1129 util::elapsed(self.start.elapsed())
1130 );
a7d7821e
MK
1131 // print the size of largest crate if it was >1mb
1132 // however don't print if only a single crate was downloaded
1133 // because it is obvious that it will be the largest then
1134 if self.largest.0 > ByteSize::mb(1).0 && self.downloads_finished > 1 {
a46df8fe
AC
1135 status.push_str(&format!(
1136 " (largest was `{}` at {})",
1137 self.largest.1,
1138 ByteSize(self.largest.0),
1139 ));
1140 }
f64a3be9
EH
1141 // Clear progress before displaying final summary.
1142 drop(progress);
a46df8fe 1143 drop(self.set.config.shell().status("Downloaded", status));
0ea0c8ba
AC
1144 }
1145}
1146
468f243e
AC
1147mod tls {
1148 use std::cell::Cell;
1149
1150 use super::Downloads;
1151
1152 thread_local!(static PTR: Cell<usize> = Cell::new(0));
1153
b8b7faee 1154 pub(crate) fn with<R>(f: impl FnOnce(Option<&Downloads<'_, '_>>) -> R) -> R {
468f243e 1155 let ptr = PTR.with(|p| p.get());
78922ca8
AC
1156 if ptr == 0 {
1157 f(None)
1158 } else {
b8b7faee 1159 unsafe { f(Some(&*(ptr as *const Downloads<'_, '_>))) }
c94804bd 1160 }
468f243e
AC
1161 }
1162
b8b7faee
AC
1163 pub(crate) fn set<R>(dl: &Downloads<'_, '_>, f: impl FnOnce() -> R) -> R {
1164 struct Reset<'a, T: Copy>(&'a Cell<T>, T);
468f243e
AC
1165
1166 impl<'a, T: Copy> Drop for Reset<'a, T> {
1167 fn drop(&mut self) {
1168 self.0.set(self.1);
1169 }
c94804bd 1170 }
468f243e
AC
1171
1172 PTR.with(|p| {
1173 let _reset = Reset(p, p.get());
b8b7faee 1174 p.set(dl as *const Downloads<'_, '_> as usize);
468f243e
AC
1175 f()
1176 })
1177 }
c94804bd 1178}