cargo/core/
package.rs

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