]>
Commit | Line | Data |
---|---|---|
5f616eb1 | 1 | use std::cell::{Cell, Ref, RefCell, RefMut}; |
fa0787aa | 2 | use std::cmp::Ordering; |
bcfdf9fb | 3 | use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; |
e723d41c | 4 | use std::fmt; |
c84bc16b | 5 | use std::hash; |
468f243e | 6 | use std::mem; |
a6dad622 | 7 | use std::path::{Path, PathBuf}; |
98b6f816 | 8 | use std::rc::Rc; |
e5a11190 | 9 | use std::time::{Duration, Instant}; |
b56b61c5 | 10 | |
3a18c89a | 11 | use anyhow::Context; |
a46df8fe | 12 | use bytesize::ByteSize; |
e5a11190 E |
13 | use curl::easy::{Easy, HttpVersion}; |
14 | use curl::multi::{EasyHandle, Multi}; | |
a46df8fe | 15 | use lazycell::LazyCell; |
9ed82b57 | 16 | use log::{debug, warn}; |
41b6f526 | 17 | use semver::Version; |
9ed82b57 | 18 | use serde::Serialize; |
e05b4dc8 | 19 | |
944f5049 EH |
20 | use crate::core::compiler::{CompileKind, RustcTargetData}; |
21 | use crate::core::dependency::DepKind; | |
3d5a9083 | 22 | use crate::core::resolver::features::ForceAllTargets; |
944f5049 | 23 | use crate::core::resolver::{HasDevUnits, Resolve}; |
04ddd4d0 DW |
24 | use crate::core::source::MaybePackage; |
25 | use crate::core::{Dependency, Manifest, PackageId, SourceId, Target}; | |
bcfdf9fb | 26 | use crate::core::{SourceMap, Summary, Workspace}; |
04ddd4d0 | 27 | use crate::ops; |
b48ae60e | 28 | use crate::util::config::PackageCacheLock; |
ebca5190 | 29 | use crate::util::errors::{CargoResult, HttpNot200}; |
7f73a6c7 | 30 | use crate::util::interning::InternedString; |
04ddd4d0 | 31 | use crate::util::network::Retry; |
7d7fe679 | 32 | use crate::util::{self, internal, Config, Progress, ProgressStyle}; |
f4d6d021 | 33 | |
787e75b7 EH |
34 | pub 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 | 53 | pub struct Package { |
fbd34a69 AC |
54 | inner: Rc<PackageInner>, |
55 | } | |
56 | ||
57 | #[derive(Clone)] | |
58 | struct 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 |
65 | impl Ord for Package { |
66 | fn cmp(&self, other: &Package) -> Ordering { | |
dae87a26 | 67 | self.package_id().cmp(&other.package_id()) |
fa0787aa EH |
68 | } |
69 | } | |
70 | ||
71 | impl 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 |
79 | pub 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 | 108 | impl 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 | 269 | impl 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 | 275 | impl 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 |
284 | impl 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 |
290 | impl Eq for Package {} |
291 | ||
973f6712 RG |
292 | impl 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 | 302 | pub 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 | 314 | pub 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 | 369 | struct 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 | 399 | impl<'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. | |
647 | macro_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 | 662 | impl<'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 |
1094 | enum WhyTick<'a> { |
1095 | DownloadStarted, | |
1096 | DownloadUpdate, | |
1097 | DownloadFinished, | |
1098 | Extracting(&'a str), | |
1099 | } | |
1100 | ||
0ea0c8ba AC |
1101 | impl<'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 |
1147 | mod 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 | } |