Skip to content

Commit 40b7793

Browse files
committed
Auto merge of #8777 - ehuss:fix-doc-itarget, r=alexcrichton
Fix panic in `cargo doc` with -Zfeatures=itarget There are some situations where `cargo doc -Zfeatures=itarget` can panic where an optional shared dependency is part of an inactive target. The issue is that the filtering logic in `compute_deps` should have been shared with `compute_deps_doc`. I moved the common filtering into `State` to try to share the code. Fixes #8774
2 parents 60194f2 + 7f02a67 commit 40b7793

File tree

2 files changed

+108
-50
lines changed

2 files changed

+108
-50
lines changed

src/cargo/core/compiler/unit_dependencies.rs

+59-50
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::core::dependency::DepKind;
2222
use crate::core::profiles::{Profile, Profiles, UnitFor};
2323
use crate::core::resolver::features::{FeaturesFor, ResolvedFeatures};
2424
use crate::core::resolver::Resolve;
25-
use crate::core::{Package, PackageId, PackageSet, Target, Workspace};
25+
use crate::core::{Dependency, Package, PackageId, PackageSet, Target, Workspace};
2626
use crate::ops::resolve_all_features;
2727
use crate::util::interning::InternedString;
2828
use crate::util::Config;
@@ -223,50 +223,33 @@ fn compute_deps(
223223
}
224224

225225
let id = unit.pkg.package_id();
226-
let filtered_deps = state.resolve().deps(id).filter(|&(_id, deps)| {
227-
assert!(!deps.is_empty());
228-
deps.iter().any(|dep| {
229-
// If this target is a build command, then we only want build
230-
// dependencies, otherwise we want everything *other than* build
231-
// dependencies.
232-
if unit.target.is_custom_build() != dep.is_build() {
233-
return false;
234-
}
235-
236-
// If this dependency is **not** a transitive dependency, then it
237-
// only applies to test/example targets.
238-
if !dep.is_transitive()
239-
&& !unit.target.is_test()
240-
&& !unit.target.is_example()
241-
&& !unit.mode.is_any_test()
242-
{
243-
return false;
244-
}
245-
246-
// If this dependency is only available for certain platforms,
247-
// make sure we're only enabling it for that platform.
248-
if !state.target_data.dep_platform_activated(dep, unit.kind) {
249-
return false;
250-
}
251-
252-
// If this is an optional dependency, and the new feature resolver
253-
// did not enable it, don't include it.
254-
if dep.is_optional() {
255-
let features_for = unit_for.map_to_features_for();
226+
let filtered_deps = state
227+
.deps(unit, unit_for)
228+
.into_iter()
229+
.filter(|&(_id, deps)| {
230+
deps.iter().any(|dep| {
231+
// If this target is a build command, then we only want build
232+
// dependencies, otherwise we want everything *other than* build
233+
// dependencies.
234+
if unit.target.is_custom_build() != dep.is_build() {
235+
return false;
236+
}
256237

257-
let feats = state.activated_features(id, features_for);
258-
if !feats.contains(&dep.name_in_toml()) {
238+
// If this dependency is **not** a transitive dependency, then it
239+
// only applies to test/example targets.
240+
if !dep.is_transitive()
241+
&& !unit.target.is_test()
242+
&& !unit.target.is_example()
243+
&& !unit.mode.is_any_test()
244+
{
259245
return false;
260246
}
261-
}
262247

263-
// If we've gotten past all that, then this dependency is
264-
// actually used!
265-
true
266-
})
267-
});
268-
// Separate line to avoid rustfmt indentation. Must collect due to `state` capture.
269-
let filtered_deps: Vec<_> = filtered_deps.collect();
248+
// If we've gotten past all that, then this dependency is
249+
// actually used!
250+
true
251+
})
252+
});
270253

271254
let mut ret = Vec::new();
272255
for (id, _) in filtered_deps {
@@ -410,16 +393,10 @@ fn compute_deps_custom_build(
410393

411394
/// Returns the dependencies necessary to document a package.
412395
fn compute_deps_doc(unit: &Unit, state: &mut State<'_, '_>) -> CargoResult<Vec<UnitDep>> {
413-
let target_data = state.target_data;
414396
let deps = state
415-
.resolve()
416-
.deps(unit.pkg.package_id())
417-
.filter(|&(_id, deps)| {
418-
deps.iter().any(|dep| match dep.kind() {
419-
DepKind::Normal => target_data.dep_platform_activated(dep, unit.kind),
420-
_ => false,
421-
})
422-
});
397+
.deps(unit, UnitFor::new_normal())
398+
.into_iter()
399+
.filter(|&(_id, deps)| deps.iter().any(|dep| dep.kind() == DepKind::Normal));
423400

424401
// To document a library, we depend on dependencies actually being
425402
// built. If we're documenting *all* libraries, then we also depend on
@@ -741,4 +718,36 @@ impl<'a, 'cfg> State<'a, 'cfg> {
741718
.get_one(id)
742719
.unwrap_or_else(|_| panic!("expected {} to be downloaded", id))
743720
}
721+
722+
/// Returns a filtered set of dependencies for the given unit.
723+
fn deps(&self, unit: &Unit, unit_for: UnitFor) -> Vec<(PackageId, &HashSet<Dependency>)> {
724+
let pkg_id = unit.pkg.package_id();
725+
let kind = unit.kind;
726+
self.resolve()
727+
.deps(pkg_id)
728+
.filter(|&(_id, deps)| {
729+
assert!(!deps.is_empty());
730+
deps.iter().any(|dep| {
731+
// If this dependency is only available for certain platforms,
732+
// make sure we're only enabling it for that platform.
733+
if !self.target_data.dep_platform_activated(dep, kind) {
734+
return false;
735+
}
736+
737+
// If this is an optional dependency, and the new feature resolver
738+
// did not enable it, don't include it.
739+
if dep.is_optional() {
740+
let features_for = unit_for.map_to_features_for();
741+
742+
let feats = self.activated_features(pkg_id, features_for);
743+
if !feats.contains(&dep.name_in_toml()) {
744+
return false;
745+
}
746+
}
747+
748+
true
749+
})
750+
})
751+
.collect()
752+
}
744753
}

tests/testsuite/features2.rs

+49
Original file line numberDiff line numberDiff line change
@@ -1946,3 +1946,52 @@ fn test_proc_macro() {
19461946
.masquerade_as_nightly_cargo()
19471947
.run();
19481948
}
1949+
1950+
#[cargo_test]
1951+
fn doc_optional() {
1952+
// Checks for a bug where `cargo doc` was failing with an inactive target
1953+
// that enables a shared optional dependency.
1954+
Package::new("spin", "1.0.0").publish();
1955+
Package::new("bar", "1.0.0")
1956+
.add_dep(Dependency::new("spin", "1.0").optional(true))
1957+
.publish();
1958+
// The enabler package enables the `spin` feature, which we don't want.
1959+
Package::new("enabler", "1.0.0")
1960+
.feature_dep("bar", "1.0", &["spin"])
1961+
.publish();
1962+
let p = project()
1963+
.file(
1964+
"Cargo.toml",
1965+
r#"
1966+
[package]
1967+
name = "foo"
1968+
version = "0.1.0"
1969+
1970+
[target.'cfg(whatever)'.dependencies]
1971+
enabler = "1.0"
1972+
1973+
[dependencies]
1974+
bar = "1.0"
1975+
"#,
1976+
)
1977+
.file("src/lib.rs", "")
1978+
.build();
1979+
1980+
p.cargo("doc -Zfeatures=itarget")
1981+
.masquerade_as_nightly_cargo()
1982+
.with_stderr_unordered(
1983+
"\
1984+
[UPDATING] [..]
1985+
[DOWNLOADING] crates ...
1986+
[DOWNLOADED] spin v1.0.0 [..]
1987+
[DOWNLOADED] bar v1.0.0 [..]
1988+
[DOWNLOADING] crates ...
1989+
[DOWNLOADED] enabler v1.0.0 [..]
1990+
[DOCUMENTING] bar v1.0.0
1991+
[CHECKING] bar v1.0.0
1992+
[DOCUMENTING] foo v0.1.0 [..]
1993+
[FINISHED] [..]
1994+
",
1995+
)
1996+
.run();
1997+
}

0 commit comments

Comments
 (0)