Skip to content

Commit b9bba2d

Browse files
committed
Auto merge of #6814 - ehuss:offline-optional-dep, r=alexcrichton
Resolve: Be less strict while offline. When offline, the resolver was requiring everything to be downloaded, even dependencies that are not used. This changes it so that the resolver can still resolve unavailable dependencies when offline. This pushes the failure to a later stage of Cargo where it attempts to download the dependency. This makes `-Z offline` work for target-cfg or optional dependencies that are not being used. Fixes #6014. This changes the error message significantly for the "unavailable" case (see test diff). I personally think the new error message is clearer, although it is shorter and provides less information. The old error message seemed large and scary, and was a little hard for me to grok. However, I'd be willing to look at tweaking the error behavior if not everyone agrees.
2 parents 11b17b5 + 2378a9b commit b9bba2d

File tree

4 files changed

+118
-20
lines changed

4 files changed

+118
-20
lines changed

src/cargo/core/package.rs

+6
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,12 @@ impl<'a, 'cfg> Downloads<'a, 'cfg> {
476476
/// eventually be returned from `wait_for_download`. Returns `Some(pkg)` if
477477
/// the package is ready and doesn't need to be downloaded.
478478
pub fn start(&mut self, id: PackageId) -> CargoResult<Option<&'a Package>> {
479+
Ok(self
480+
.start_inner(id)
481+
.chain_err(|| format!("failed to download `{}`", id))?)
482+
}
483+
484+
fn start_inner(&mut self, id: PackageId) -> CargoResult<Option<&'a Package>> {
479485
// First up see if we've already cached this package, in which case
480486
// there's nothing to do.
481487
let slot = self

src/cargo/sources/registry/index.rs

+35-12
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ impl<'cfg> RegistryIndex<'cfg> {
200200
ret.reserve(contents.lines().count());
201201
let lines = contents.lines().map(|s| s.trim()).filter(|l| !l.is_empty());
202202

203-
let online = !self.config.cli_unstable().offline;
204203
// Attempt forwards-compatibility on the index by ignoring
205204
// everything that we ourselves don't understand, that should
206205
// allow future cargo implementations to break the
@@ -215,11 +214,7 @@ impl<'cfg> RegistryIndex<'cfg> {
215214
return None;
216215
}
217216
};
218-
if online || load.is_crate_downloaded(summary.package_id()) {
219-
Some((summary, locked))
220-
} else {
221-
None
222-
}
217+
Some((summary, locked))
223218
}));
224219

225220
Ok(())
@@ -274,17 +269,43 @@ impl<'cfg> RegistryIndex<'cfg> {
274269
yanked_whitelist: &HashSet<PackageId>,
275270
f: &mut dyn FnMut(Summary),
276271
) -> CargoResult<()> {
272+
if self.config.cli_unstable().offline {
273+
if self.query_inner_with_online(dep, load, yanked_whitelist, f, false)? != 0 {
274+
return Ok(());
275+
}
276+
// If offline, and there are no matches, try again with online.
277+
// This is necessary for dependencies that are not used (such as
278+
// target-cfg or optional), but are not downloaded. Normally the
279+
// build should succeed if they are not downloaded and not used,
280+
// but they still need to resolve. If they are actually needed
281+
// then cargo will fail to download and an error message
282+
// indicating that the required dependency is unavailable while
283+
// offline will be displayed.
284+
}
285+
self.query_inner_with_online(dep, load, yanked_whitelist, f, true)?;
286+
Ok(())
287+
}
288+
289+
fn query_inner_with_online(
290+
&mut self,
291+
dep: &Dependency,
292+
load: &mut dyn RegistryData,
293+
yanked_whitelist: &HashSet<PackageId>,
294+
f: &mut dyn FnMut(Summary),
295+
online: bool,
296+
) -> CargoResult<usize> {
277297
let source_id = self.source_id;
278298
let name = dep.package_name().as_str();
279299
let summaries = self.summaries(name, load)?;
280300
let summaries = summaries
281301
.iter()
282302
.filter(|&(summary, yanked)| {
283-
!yanked || {
284-
log::debug!("{:?}", yanked_whitelist);
285-
log::debug!("{:?}", summary.package_id());
286-
yanked_whitelist.contains(&summary.package_id())
287-
}
303+
(online || load.is_crate_downloaded(summary.package_id()))
304+
&& (!yanked || {
305+
log::debug!("{:?}", yanked_whitelist);
306+
log::debug!("{:?}", summary.package_id());
307+
yanked_whitelist.contains(&summary.package_id())
308+
})
288309
})
289310
.map(|s| s.0.clone());
290311

@@ -308,9 +329,11 @@ impl<'cfg> RegistryIndex<'cfg> {
308329
_ => true,
309330
});
310331

332+
let mut count = 0;
311333
for summary in summaries {
312334
f(summary);
335+
count += 1;
313336
}
314-
Ok(())
337+
Ok(count)
315338
}
316339
}

tests/testsuite/build.rs

+76-8
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,77 @@ fn main(){
11401140
.run();
11411141
}
11421142

1143+
#[test]
1144+
fn offline_unused_target_dep() {
1145+
// -Z offline with a target dependency that is not used and not downloaded.
1146+
Package::new("unused_dep", "1.0.0").publish();
1147+
Package::new("used_dep", "1.0.0").publish();
1148+
let p = project()
1149+
.file(
1150+
"Cargo.toml",
1151+
r#"
1152+
[project]
1153+
name = "foo"
1154+
version = "0.1.0"
1155+
[dependencies]
1156+
used_dep = "1.0"
1157+
[target.'cfg(unused)'.dependencies]
1158+
unused_dep = "1.0"
1159+
"#,
1160+
)
1161+
.file("src/lib.rs", "")
1162+
.build();
1163+
// Do a build that downloads only what is necessary.
1164+
p.cargo("build")
1165+
.with_stderr_contains("[DOWNLOADED] used_dep [..]")
1166+
.with_stderr_does_not_contain("[DOWNLOADED] unused_dep [..]")
1167+
.run();
1168+
p.cargo("clean").run();
1169+
// Build offline, make sure it works.
1170+
p.cargo("build -Z offline")
1171+
.masquerade_as_nightly_cargo()
1172+
.run();
1173+
}
1174+
1175+
#[test]
1176+
fn offline_missing_optional() {
1177+
Package::new("opt_dep", "1.0.0").publish();
1178+
let p = project()
1179+
.file(
1180+
"Cargo.toml",
1181+
r#"
1182+
[project]
1183+
name = "foo"
1184+
version = "0.1.0"
1185+
[dependencies]
1186+
opt_dep = { version = "1.0", optional = true }
1187+
"#,
1188+
)
1189+
.file("src/lib.rs", "")
1190+
.build();
1191+
// Do a build that downloads only what is necessary.
1192+
p.cargo("build")
1193+
.with_stderr_does_not_contain("[DOWNLOADED] opt_dep [..]")
1194+
.run();
1195+
p.cargo("clean").run();
1196+
// Build offline, make sure it works.
1197+
p.cargo("build -Z offline")
1198+
.masquerade_as_nightly_cargo()
1199+
.run();
1200+
p.cargo("build -Z offline --features=opt_dep")
1201+
.masquerade_as_nightly_cargo()
1202+
.with_stderr(
1203+
"\
1204+
[ERROR] failed to download `opt_dep v1.0.0`
1205+
1206+
Caused by:
1207+
can't make HTTP request in the offline mode
1208+
",
1209+
)
1210+
.with_status(101)
1211+
.run();
1212+
}
1213+
11431214
#[test]
11441215
fn incompatible_dependencies() {
11451216
Package::new("bad", "0.1.0").publish();
@@ -1282,14 +1353,11 @@ fn compile_offline_while_transitive_dep_not_cached() {
12821353
.with_status(101)
12831354
.with_stderr(
12841355
"\
1285-
error: no matching package named `baz` found
1286-
location searched: registry `[..]`
1287-
required by package `bar v0.1.0`
1288-
... which is depended on by `foo v0.0.1 ([CWD])`
1289-
As a reminder, you're using offline mode (-Z offline) \
1290-
which can sometimes cause surprising resolution failures, \
1291-
if this error is too confusing you may wish to retry \
1292-
without the offline flag.",
1356+
[ERROR] failed to download `baz v1.0.0`
1357+
1358+
Caused by:
1359+
can't make HTTP request in the offline mode
1360+
",
12931361
)
12941362
.run();
12951363
}

tests/testsuite/support/registry.rs

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ pub fn alt_api_url() -> Url {
126126
///
127127
/// p.cargo("run").with_stdout("24").run();
128128
/// ```
129+
#[must_use]
129130
pub struct Package {
130131
name: String,
131132
vers: String,

0 commit comments

Comments
 (0)