diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 4cb5ac5e44b..1c37bc3463d 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -232,6 +232,7 @@ impl Features { pub struct CliUnstable { pub print_im_a_teapot: bool, pub unstable_options: bool, + pub airplane: bool, } impl CliUnstable { @@ -262,6 +263,7 @@ impl CliUnstable { match k { "print-im-a-teapot" => self.print_im_a_teapot = parse_bool(v)?, "unstable-options" => self.unstable_options = true, + "airplane" => self.airplane = parse_bool(v)?, _ => bail!("unknown `-Z` flag specified: {}", k), } diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index cfefbf48992..777779ad59e 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -380,7 +380,7 @@ pub fn resolve(summaries: &[(Summary, Method)], warnings: RcList::new(), }; let _p = profile::start("resolving"); - let cx = activate_deps_loop(cx, registry, summaries, config)?; + let cx = activate_deps_loop(cx, registry, summaries, &config)?; let mut resolve = Resolve { graph: cx.graph(), @@ -589,7 +589,7 @@ impl RemainingCandidates { fn activate_deps_loop<'a>(mut cx: Context<'a>, registry: &mut Registry, summaries: &[(Summary, Method)], - config: Option<&Config>) + config: &Option<&Config>) -> CargoResult> { // Note that a `BinaryHeap` is used for the remaining dependencies that need // activation. This heap is sorted such that the "largest value" is the most @@ -640,7 +640,7 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>, // like `Instant::now` by only checking every N iterations of this loop // to amortize the cost of the current time lookup. ticks += 1; - if let Some(config) = config { + if let &Some(config) = config { if config.shell().is_err_tty() && !printed && ticks % 1000 == 0 && @@ -722,7 +722,8 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>, None => return Err(activation_error(&cx, registry, &parent, &dep, cx.prev_active(&dep), - &candidates)), + &candidates, + config)), Some(candidate) => candidate, } } @@ -788,7 +789,8 @@ fn activation_error(cx: &Context, parent: &Summary, dep: &Dependency, prev_active: &[Summary], - candidates: &[Candidate]) -> CargoError { + candidates: &[Candidate], + config: &Option<&Config>) -> CargoError { if !candidates.is_empty() { let mut msg = format!("failed to select a version for `{}` \ (required by `{}`):\n\ @@ -843,7 +845,7 @@ fn activation_error(cx: &Context, b.version().cmp(a.version()) }); - let msg = if !candidates.is_empty() { + let mut msg = if !candidates.is_empty() { let versions = { let mut versions = candidates.iter().take(3).map(|cand| { cand.version().to_string() @@ -885,6 +887,13 @@ fn activation_error(cx: &Context, dep.source_id(), dep.version_req()) }; + // If airplane mode is enabled, then this probably just means that we + // haven't downloaded the package yet + if let &Some(conf) = config { + if conf.cli_unstable().airplane { + msg.push_str("\nthis may be failing due to the -Zairplane flag"); + } + } format_err!("{}", msg) } diff --git a/src/cargo/ops/cargo_generate_lockfile.rs b/src/cargo/ops/cargo_generate_lockfile.rs index 0d6ebefb726..3a81b5d5f27 100644 --- a/src/cargo/ops/cargo_generate_lockfile.rs +++ b/src/cargo/ops/cargo_generate_lockfile.rs @@ -37,6 +37,10 @@ pub fn update_lockfile(ws: &Workspace, opts: &UpdateOptions) bail!("you can't generate a lockfile for an empty workspace.") } + if !ws.config().network_allowed() { + bail!("cannot update when network operations are not allowed."); + } + let previous_resolve = match ops::load_pkg_lockfile(ws)? { Some(resolve) => resolve, None => return generate_lockfile(ws), diff --git a/src/cargo/ops/registry.rs b/src/cargo/ops/registry.rs index 3d53c59507a..294c74364a3 100644 --- a/src/cargo/ops/registry.rs +++ b/src/cargo/ops/registry.rs @@ -20,6 +20,7 @@ use util::config::{self, Config}; use util::paths; use util::ToUrl; use util::errors::{CargoResult, CargoResultExt}; +use util::network::maybe_spurious; use util::important_paths::find_root_manifest_for_wd; pub struct RegistryConfig { @@ -252,8 +253,15 @@ pub fn registry(config: &Config, }; let api_host = { let mut src = RegistrySource::remote(&sid, config); - src.update().chain_err(|| { - format!("failed to update {}", sid) + src.update().map_err(|e| { + if maybe_spurious(&e) { + e.context( + format!("failed to update {} due to network issues. \ + Try using airplane mode by passing the \ + -Zaiplane flag", sid)) + } else { + e.context(format!("failed to update {}", sid)) + } })?; (src.config()?).unwrap().api.unwrap() }; diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index 2ccf3b394fa..709186003ac 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -44,7 +44,7 @@ pub fn resolve_ws_precisely<'a>(ws: &Workspace<'a>, let resolve = if ws.require_optional_deps() { // First, resolve the root_package's *listed* dependencies, as well as // downloading and updating all remotes and such. - let resolve = resolve_with_registry(ws, &mut registry, false)?; + let resolve = resolve_with_registry(ws, &mut registry, true)?; // Second, resolve with precisely what we're doing. Filter out // transitive dependencies if necessary, specify features, handle diff --git a/src/cargo/sources/git/source.rs b/src/cargo/sources/git/source.rs index a07782b4fdf..bdc7e2e8d9b 100644 --- a/src/cargo/sources/git/source.rs +++ b/src/cargo/sources/git/source.rs @@ -159,7 +159,7 @@ impl<'cfg> Source for GitSource<'cfg> { let should_update = actual_rev.is_err() || self.source_id.precise().is_none(); - let (repo, actual_rev) = if should_update { + let (repo, actual_rev) = if should_update && self.config.network_allowed() { self.config.shell().status("Updating", format!("git repository `{}`", self.remote.url()))?; diff --git a/src/cargo/sources/git/utils.rs b/src/cargo/sources/git/utils.rs index f29bdb8fdb9..d4fb1c6dbec 100644 --- a/src/cargo/sources/git/utils.rs +++ b/src/cargo/sources/git/utils.rs @@ -145,7 +145,7 @@ impl GitDatabase { let checkout = match git2::Repository::open(dest) { Ok(repo) => { let mut checkout = GitCheckout::new(dest, self, rev, repo); - if !checkout.is_fresh() { + if !checkout.is_fresh() && cargo_config.network_allowed() { checkout.fetch(cargo_config)?; checkout.reset(cargo_config)?; assert!(checkout.is_fresh()); diff --git a/src/cargo/sources/registry/index.rs b/src/cargo/sources/registry/index.rs index 4f92238c17f..021227e07ac 100644 --- a/src/cargo/sources/registry/index.rs +++ b/src/cargo/sources/registry/index.rs @@ -115,10 +115,18 @@ impl<'cfg> RegistryIndex<'cfg> { // allow future cargo implementations to break the // interpretation of each line here and older cargo will simply // ignore the new lines. - ret.extend(lines.filter_map(|line| { + let summaries = lines.filter_map(|line| { self.parse_registry_package(line).ok() - })); - + }); + if self.config.network_allowed() { + ret.extend(summaries); + } else { + // Since we're in airplane mode, we need to further filter + // the packages to only ones that we have downloaded + ret.extend(summaries.filter(|&(ref sum, _)| { + load.is_crate_downloaded(sum.package_id()) + })); + } Ok(()) }); diff --git a/src/cargo/sources/registry/local.rs b/src/cargo/sources/registry/local.rs index 5803fd77d94..fa5dbba3c51 100644 --- a/src/cargo/sources/registry/local.rs +++ b/src/cargo/sources/registry/local.rs @@ -102,4 +102,9 @@ impl<'cfg> RegistryData for LocalRegistry<'cfg> { Ok(crate_file) } + + fn is_crate_downloaded(&self, pkg: &PackageId) -> bool { + let crate_file = format!("{}-{}.crate", pkg.name(), pkg.version()); + self.root.open_ro(&crate_file, self.config, "crate file").is_ok() + } } diff --git a/src/cargo/sources/registry/mod.rs b/src/cargo/sources/registry/mod.rs index 27984aa6cba..e849569ceec 100644 --- a/src/cargo/sources/registry/mod.rs +++ b/src/cargo/sources/registry/mod.rs @@ -249,6 +249,7 @@ pub trait RegistryData { fn download(&mut self, pkg: &PackageId, checksum: &str) -> CargoResult; + fn is_crate_downloaded(&self, pkg: &PackageId) -> bool; } mod index; diff --git a/src/cargo/sources/registry/remote.rs b/src/cargo/sources/registry/remote.rs index 489d001b952..37585ed2cff 100644 --- a/src/cargo/sources/registry/remote.rs +++ b/src/cargo/sources/registry/remote.rs @@ -16,7 +16,8 @@ use sources::registry::{RegistryData, RegistryConfig, INDEX_LOCK, CRATE_TEMPLATE use util::network; use util::{FileLock, Filesystem, LazyCell}; use util::{Config, Sha256, ToUrl, Progress}; -use util::errors::{CargoResult, CargoResultExt, HttpNot200}; +use util::errors::{CargoResult, HttpNot200}; +use util::network::maybe_spurious; pub struct RemoteRegistry<'cfg> { index_path: Filesystem, @@ -153,6 +154,12 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { } fn update_index(&mut self) -> CargoResult<()> { + // Before checking the HTTP handle, check if we're in airplane mode + // If we are, then return Ok early, since we know that we don't + // want to update the index, but it's not an error. + if !self.config.network_allowed(){ + return Ok(()); + } // Ensure that we'll actually be able to acquire an HTTP handle later on // once we start trying to download crates. This will weed out any // problems with `.cargo/config` configuration related to HTTP. @@ -173,8 +180,15 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { let url = self.source_id.url(); let refspec = "refs/heads/master:refs/remotes/origin/master"; let repo = self.repo.borrow_mut().unwrap(); - git::fetch(repo, url, refspec, self.config).chain_err(|| { - format!("failed to fetch `{}`", url) + git::fetch(repo, url, refspec, self.config).map_err(|e| { + if maybe_spurious(&e) { + e.context( + format!("failed to fetch `{}` due to network issues. \ + Try using airplane mode by passing the \ + -Zaiplane flag", url)) + } else { + e.context(format!("failed to fetch `{}`", url)) + } })?; Ok(()) } @@ -258,6 +272,18 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> { dst.seek(SeekFrom::Start(0))?; Ok(dst) } + + fn is_crate_downloaded(&self, pkg: &PackageId) -> bool { + let filename = format!("{}-{}.crate", pkg.name(), pkg.version()); + let path = Path::new(&filename); + + if let Ok(dst) = self.cache_path.open_ro(path, self.config, &filename) { + if let Ok(metadata) = dst.file().metadata() { + return metadata.len() > 0; + } + } + false + } } impl<'cfg> Drop for RemoteRegistry<'cfg> { diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index 77577ab3077..e8e35049798 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -504,7 +504,7 @@ impl Config { } pub fn network_allowed(&self) -> bool { - !self.frozen + !(self.frozen || self.cli_unstable().airplane) } pub fn lock_update_allowed(&self) -> bool { diff --git a/src/cargo/util/network.rs b/src/cargo/util/network.rs index 170805695b3..d87e8fcf2bc 100644 --- a/src/cargo/util/network.rs +++ b/src/cargo/util/network.rs @@ -6,7 +6,7 @@ use failure::Error; use util::Config; use util::errors::{CargoResult, HttpNot200}; -fn maybe_spurious(err: &Error) -> bool { +pub fn maybe_spurious(err: &Error) -> bool { for e in err.causes() { if let Some(git_err) = e.downcast_ref::() { match git_err.class() { diff --git a/tests/cargo-features.rs b/tests/cargo-features.rs index 0434b776e10..9ab40dcbdfd 100644 --- a/tests/cargo-features.rs +++ b/tests/cargo-features.rs @@ -238,6 +238,16 @@ error: the `-Z` flag is only accepted on the nightly channel of Cargo execs().with_status(101) .with_stderr("\ error: unknown `-Z` flag specified: arg +")); + + assert_that(p.cargo("build") + .masquerade_as_nightly_cargo() + .arg("-Zairplane"), + execs().with_status(0) + .with_stdout("") + .with_stderr("\ +[COMPILING] a [..] +[FINISHED] [..] ")); assert_that(p.cargo("build") @@ -246,7 +256,6 @@ error: unknown `-Z` flag specified: arg execs().with_status(0) .with_stdout("im-a-teapot = true\n") .with_stderr("\ -[COMPILING] a [..] [FINISHED] [..] ")); }