Skip to content

Commit 89dcb2a

Browse files
committed
Auto merge of #9059 - ehuss:fix-unit-for-host, r=alexcrichton
Fix unit_for computation on proc-macros in shared workspace. There was a bug where the UnitFor was not being computed properly for proc-macros in a workspace with shared dependencies, integration tests, and a mixture of various flags (`--workspace --all-targets --all-features`). The issue is that [this line](https://github.com/rust-lang/cargo/blob/faf05ac316cd71100a691799cd8da02fce6dd85d/src/cargo/core/compiler/unit_dependencies.rs#L474), which is used when attaching the implicit dependency from an integration test to its library, was using the wrong unit_for value (it was not checking if the implicit lib is a proc-macro). The consequence is that the graph could be built inconsistently, causing features to be randomly selected incorrectly if the integration test happened to be the first unit processed. The solution here is to use a common function for transitioning the unit_for value. The with_for_host/with_host_features split was mostly a consequence of how things evolved over time, and keeping them separate wasn't really necessary.
2 parents 3021739 + a846898 commit 89dcb2a

File tree

3 files changed

+165
-37
lines changed

3 files changed

+165
-37
lines changed

src/cargo/core/compiler/unit_dependencies.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,7 @@ fn compute_deps(
265265
None => continue,
266266
};
267267
let mode = check_or_build_mode(unit.mode, lib);
268-
let dep_unit_for = unit_for
269-
.with_for_host(lib.for_host())
270-
// If it is a custom build script, then it *only* has build dependencies.
271-
.with_host_features(unit.target.is_custom_build() || lib.proc_macro());
268+
let dep_unit_for = unit_for.with_dependency(unit, lib);
272269

273270
if state.config.cli_unstable().dual_proc_macros && lib.proc_macro() && !unit.kind.is_host()
274271
{
@@ -417,9 +414,7 @@ fn compute_deps_doc(unit: &Unit, state: &mut State<'_, '_>) -> CargoResult<Vec<U
417414
// Rustdoc only needs rmeta files for regular dependencies.
418415
// However, for plugins/proc macros, deps should be built like normal.
419416
let mode = check_or_build_mode(unit.mode, lib);
420-
let dep_unit_for = UnitFor::new_normal()
421-
.with_for_host(lib.for_host())
422-
.with_host_features(lib.proc_macro());
417+
let dep_unit_for = UnitFor::new_normal().with_dependency(unit, lib);
423418
let lib_unit_dep = new_unit_dep(
424419
state,
425420
unit,
@@ -466,12 +461,13 @@ fn maybe_lib(
466461
.find(|t| t.is_linkable())
467462
.map(|t| {
468463
let mode = check_or_build_mode(unit.mode, t);
464+
let dep_unit_for = unit_for.with_dependency(unit, t);
469465
new_unit_dep(
470466
state,
471467
unit,
472468
&unit.pkg,
473469
t,
474-
unit_for,
470+
dep_unit_for,
475471
unit.kind.for_target(t),
476472
mode,
477473
)

src/cargo/core/profiles.rs

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use crate::core::compiler::CompileMode;
1+
use crate::core::compiler::{CompileMode, Unit};
22
use crate::core::resolver::features::FeaturesFor;
3-
use crate::core::{Feature, Features, PackageId, PackageIdSpec, Resolve, Shell};
3+
use crate::core::{Feature, Features, PackageId, PackageIdSpec, Resolve, Shell, Target};
44
use crate::util::errors::CargoResultExt;
55
use crate::util::interning::InternedString;
66
use crate::util::toml::{ProfilePackageSpec, StringOrBool, TomlProfile, TomlProfiles, U32OrBool};
@@ -976,36 +976,38 @@ impl UnitFor {
976976
unit_for
977977
}
978978

979-
/// Returns a new copy based on `for_host` setting.
979+
/// Returns a new copy updated based on the target dependency.
980980
///
981-
/// When `for_host` is true, this clears `panic_abort_ok` in a sticky
982-
/// fashion so that all its dependencies also have `panic_abort_ok=false`.
983-
/// This'll help ensure that once we start compiling for the host platform
984-
/// (build scripts, plugins, proc macros, etc) we'll share the same build
985-
/// graph where everything is `panic=unwind`.
986-
pub fn with_for_host(self, for_host: bool) -> UnitFor {
981+
/// This is where the magic happens that the host/host_features settings
982+
/// transition in a sticky fashion. As the dependency graph is being
983+
/// built, once those flags are set, they stay set for the duration of
984+
/// that portion of tree.
985+
pub fn with_dependency(self, parent: &Unit, dep_target: &Target) -> UnitFor {
986+
// A build script or proc-macro transitions this to being built for the host.
987+
let dep_for_host = dep_target.for_host();
988+
// This is where feature decoupling of host versus target happens.
989+
//
990+
// Once host features are desired, they are always desired.
991+
//
992+
// A proc-macro should always use host features.
993+
//
994+
// Dependencies of a build script should use host features (subtle
995+
// point: the build script itself does *not* use host features, that's
996+
// why the parent is checked here, and not the dependency).
997+
let host_features =
998+
self.host_features || parent.target.is_custom_build() || dep_target.proc_macro();
999+
// Build scripts and proc macros, and all of their dependencies are
1000+
// AlwaysUnwind.
1001+
let panic_setting = if dep_for_host {
1002+
PanicSetting::AlwaysUnwind
1003+
} else {
1004+
self.panic_setting
1005+
};
9871006
UnitFor {
988-
host: self.host || for_host,
989-
host_features: self.host_features,
990-
panic_setting: if for_host {
991-
PanicSetting::AlwaysUnwind
992-
} else {
993-
self.panic_setting
994-
},
995-
}
996-
}
997-
998-
/// Returns a new copy updating it whether or not it should use features
999-
/// for build dependencies and proc-macros.
1000-
///
1001-
/// This is part of the machinery responsible for handling feature
1002-
/// decoupling for build dependencies in the new feature resolver.
1003-
pub fn with_host_features(mut self, host_features: bool) -> UnitFor {
1004-
if host_features {
1005-
assert!(self.host);
1007+
host: self.host || dep_for_host,
1008+
host_features,
1009+
panic_setting,
10061010
}
1007-
self.host_features = self.host_features || host_features;
1008-
self
10091011
}
10101012

10111013
/// Returns `true` if this unit is for a build script or any of its

tests/testsuite/features2.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,3 +2147,133 @@ foo v0.1.0 ([ROOT]/foo)
21472147
.run();
21482148
clear();
21492149
}
2150+
2151+
#[cargo_test]
2152+
fn pm_with_int_shared() {
2153+
// This is a somewhat complex scenario of a proc-macro in a workspace with
2154+
// an integration test where the proc-macro is used for other things, and
2155+
// *everything* is built at once (`--workspace --all-targets
2156+
// --all-features`). There was a bug where the UnitFor settings were being
2157+
// incorrectly computed based on the order that the graph was traversed.
2158+
//
2159+
// There are some uncertainties about exactly how proc-macros should behave
2160+
// with `--workspace`, see https://github.com/rust-lang/cargo/issues/8312.
2161+
//
2162+
// This uses a const-eval hack to do compile-time feature checking.
2163+
let p = project()
2164+
.file(
2165+
"Cargo.toml",
2166+
r#"
2167+
[workspace]
2168+
members = ["foo", "pm", "shared"]
2169+
resolver = "2"
2170+
"#,
2171+
)
2172+
.file(
2173+
"foo/Cargo.toml",
2174+
r#"
2175+
[package]
2176+
name = "foo"
2177+
version = "0.1.0"
2178+
edition = "2018"
2179+
2180+
[dependencies]
2181+
pm = { path = "../pm" }
2182+
shared = { path = "../shared", features = ["norm-feat"] }
2183+
"#,
2184+
)
2185+
.file(
2186+
"foo/src/lib.rs",
2187+
r#"
2188+
// foo->shared always has both features set
2189+
const _CHECK: [(); 0] = [(); 0-!(shared::FEATS==3) as usize];
2190+
"#,
2191+
)
2192+
.file(
2193+
"pm/Cargo.toml",
2194+
r#"
2195+
[package]
2196+
name = "pm"
2197+
version = "0.1.0"
2198+
2199+
[lib]
2200+
proc-macro = true
2201+
2202+
[dependencies]
2203+
shared = { path = "../shared", features = ["host-feat"] }
2204+
"#,
2205+
)
2206+
.file(
2207+
"pm/src/lib.rs",
2208+
r#"
2209+
// pm->shared always has just host
2210+
const _CHECK: [(); 0] = [(); 0-!(shared::FEATS==1) as usize];
2211+
"#,
2212+
)
2213+
.file(
2214+
"pm/tests/pm_test.rs",
2215+
r#"
2216+
// integration test gets both set
2217+
const _CHECK: [(); 0] = [(); 0-!(shared::FEATS==3) as usize];
2218+
"#,
2219+
)
2220+
.file(
2221+
"shared/Cargo.toml",
2222+
r#"
2223+
[package]
2224+
name = "shared"
2225+
version = "0.1.0"
2226+
2227+
[features]
2228+
norm-feat = []
2229+
host-feat = []
2230+
"#,
2231+
)
2232+
.file(
2233+
"shared/src/lib.rs",
2234+
r#"
2235+
pub const FEATS: u32 = {
2236+
if cfg!(feature="norm-feat") && cfg!(feature="host-feat") {
2237+
3
2238+
} else if cfg!(feature="norm-feat") {
2239+
2
2240+
} else if cfg!(feature="host-feat") {
2241+
1
2242+
} else {
2243+
0
2244+
}
2245+
};
2246+
"#,
2247+
)
2248+
.build();
2249+
2250+
p.cargo("build --workspace --all-targets --all-features -v")
2251+
.with_stderr_unordered(
2252+
"\
2253+
[COMPILING] shared [..]
2254+
[RUNNING] `rustc --crate-name shared [..]--crate-type lib [..]
2255+
[RUNNING] `rustc --crate-name shared [..]--crate-type lib [..]
2256+
[RUNNING] `rustc --crate-name shared [..]--test[..]
2257+
[COMPILING] pm [..]
2258+
[RUNNING] `rustc --crate-name pm [..]--crate-type proc-macro[..]
2259+
[RUNNING] `rustc --crate-name pm [..]--test[..]
2260+
[COMPILING] foo [..]
2261+
[RUNNING] `rustc --crate-name foo [..]--test[..]
2262+
[RUNNING] `rustc --crate-name pm_test [..]--test[..]
2263+
[RUNNING] `rustc --crate-name foo [..]--crate-type lib[..]
2264+
[FINISHED] [..]
2265+
",
2266+
)
2267+
.run();
2268+
2269+
// And again, should stay fresh.
2270+
p.cargo("build --workspace --all-targets --all-features -v")
2271+
.with_stderr_unordered(
2272+
"\
2273+
[FRESH] shared [..]
2274+
[FRESH] pm [..]
2275+
[FRESH] foo [..]
2276+
[FINISHED] [..]",
2277+
)
2278+
.run();
2279+
}

0 commit comments

Comments
 (0)