Skip to content

Commit 2381cbd

Browse files
committed
Auto merge of #11478 - rvolosatovs:fix/bindeps-target, r=weihanglo
fix: deduplicate dependencies by artifact target ### What does this PR try to resolve? In cases when a compile target is specified for a bindep and the crate depending on it, cargo fails to deduplicate the crate dependencies and attempts to build the dependent crate only once with non-deterministic feature set, which breaks e.g. https://github.com/rvolosatovs/musl-bindep-feature-bug Fix the issue by including the optional artifact compile target in the `Unit` in order to avoid wrongfully deduplicating the dependent crates Fixes #11463 Fixes #10837 Fixes #10525 Note, that this issue is already accounted for by `cargo`, but in different context a similar situation can occur while building the build script, which: 1. may be built for different target than the actual package target 2. may contain dependencies with different feature sets than the same dependencies in the dependency graph of the package itself That's why this PR is simply reusing the existing functionality for deduplication ### How should we test and review this PR? Build https://github.com/rvolosatovs/musl-bindep-feature-bug ### Additional information This is based on analysis by `@weihanglo` in #10837 (comment) I experimented with adding the whole `UnitFor` to the internal unit struct, but that seems unnecessary. It would probably be nicer to refactor `IsArtifact` and instead turn it into a 3-variant enum with a possible compile target, but I decided against that to minimize the diff. Perhaps it's worth a follow-up?
2 parents 2a4a9b4 + 385bba3 commit 2381cbd

File tree

6 files changed

+458
-20
lines changed

6 files changed

+458
-20
lines changed

src/cargo/core/compiler/standard_lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ pub fn generate_std_roots(
214214
/*is_std*/ true,
215215
/*dep_hash*/ 0,
216216
IsArtifact::No,
217+
None,
217218
));
218219
}
219220
}

src/cargo/core/compiler/unit.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
use crate::core::compiler::{unit_dependencies::IsArtifact, CompileKind, CompileMode, CrateType};
1+
use crate::core::compiler::unit_dependencies::IsArtifact;
2+
use crate::core::compiler::{CompileKind, CompileMode, CompileTarget, CrateType};
23
use crate::core::manifest::{Target, TargetKind};
3-
use crate::core::{profiles::Profile, Package};
4+
use crate::core::profiles::Profile;
5+
use crate::core::Package;
46
use crate::util::hex::short_hash;
57
use crate::util::interning::InternedString;
68
use crate::util::Config;
@@ -72,6 +74,12 @@ pub struct UnitInner {
7274
/// This value initially starts as 0, and then is filled in via a
7375
/// second-pass after all the unit dependencies have been computed.
7476
pub dep_hash: u64,
77+
78+
/// This is used for target-dependent feature resolution and is copied from
79+
/// [`FeaturesFor::ArtifactDep`], if the enum matches the variant.
80+
///
81+
/// [`FeaturesFor::ArtifactDep`]: crate::core::resolver::features::FeaturesFor::ArtifactDep
82+
pub artifact_target_for_features: Option<CompileTarget>,
7583
}
7684

7785
impl UnitInner {
@@ -139,6 +147,10 @@ impl fmt::Debug for Unit {
139147
.field("mode", &self.mode)
140148
.field("features", &self.features)
141149
.field("artifact", &self.artifact.is_true())
150+
.field(
151+
"artifact_target_for_features",
152+
&self.artifact_target_for_features,
153+
)
142154
.field("is_std", &self.is_std)
143155
.field("dep_hash", &self.dep_hash)
144156
.finish()
@@ -184,6 +196,7 @@ impl UnitInterner {
184196
is_std: bool,
185197
dep_hash: u64,
186198
artifact: IsArtifact,
199+
artifact_target_for_features: Option<CompileTarget>,
187200
) -> Unit {
188201
let target = match (is_std, target.kind()) {
189202
// This is a horrible hack to support build-std. `libstd` declares
@@ -216,6 +229,7 @@ impl UnitInterner {
216229
is_std,
217230
dep_hash,
218231
artifact,
232+
artifact_target_for_features,
219233
});
220234
Unit { inner }
221235
}

src/cargo/core/compiler/unit_dependencies.rs

+5
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,10 @@ fn new_unit_dep_with_profile(
885885
.resolve()
886886
.is_public_dep(parent.pkg.package_id(), pkg.package_id());
887887
let features_for = unit_for.map_to_features_for(artifact);
888+
let artifact_target = match features_for {
889+
FeaturesFor::ArtifactDep(target) => Some(target),
890+
_ => None,
891+
};
888892
let features = state.activated_features(pkg.package_id(), features_for);
889893
let unit = state.interner.intern(
890894
pkg,
@@ -896,6 +900,7 @@ fn new_unit_dep_with_profile(
896900
state.is_std,
897901
/*dep_hash*/ 0,
898902
artifact.map_or(IsArtifact::No, |_| IsArtifact::Yes),
903+
artifact_target,
899904
);
900905
Ok(UnitDep {
901906
unit,

src/cargo/ops/cargo_compile/mod.rs

+28-18
Original file line numberDiff line numberDiff line change
@@ -415,20 +415,25 @@ pub fn create_bcx<'a, 'cfg>(
415415
remove_duplicate_doc(build_config, &units, &mut unit_graph);
416416
}
417417

418-
if build_config
418+
let host_kind_requested = build_config
419419
.requested_kinds
420420
.iter()
421-
.any(CompileKind::is_host)
422-
{
421+
.any(CompileKind::is_host);
422+
let should_share_deps = host_kind_requested
423+
|| config.cli_unstable().bindeps
424+
&& unit_graph
425+
.iter()
426+
.any(|(unit, _)| unit.artifact_target_for_features.is_some());
427+
if should_share_deps {
423428
// Rebuild the unit graph, replacing the explicit host targets with
424-
// CompileKind::Host, merging any dependencies shared with build
425-
// dependencies.
429+
// CompileKind::Host, removing `artifact_target_for_features` and merging any dependencies
430+
// shared with build and artifact dependencies.
426431
let new_graph = rebuild_unit_graph_shared(
427432
interner,
428433
unit_graph,
429434
&units,
430435
&scrape_units,
431-
explicit_host_kind,
436+
host_kind_requested.then_some(explicit_host_kind),
432437
);
433438
// This would be nicer with destructuring assignment.
434439
units = new_graph.0;
@@ -540,29 +545,31 @@ pub fn create_bcx<'a, 'cfg>(
540545
/// This is used to rebuild the unit graph, sharing host dependencies if possible.
541546
///
542547
/// This will translate any unit's `CompileKind::Target(host)` to
543-
/// `CompileKind::Host` if the kind is equal to `to_host`. This also handles
544-
/// generating the unit `dep_hash`, and merging shared units if possible.
548+
/// `CompileKind::Host` if `to_host` is not `None` and the kind is equal to `to_host`.
549+
/// This also handles generating the unit `dep_hash`, and merging shared units if possible.
545550
///
546551
/// This is necessary because if normal dependencies used `CompileKind::Host`,
547552
/// there would be no way to distinguish those units from build-dependency
548-
/// units. This can cause a problem if a shared normal/build dependency needs
553+
/// units or artifact dependency units.
554+
/// This can cause a problem if a shared normal/build/artifact dependency needs
549555
/// to link to another dependency whose features differ based on whether or
550-
/// not it is a normal or build dependency. If both units used
556+
/// not it is a normal, build or artifact dependency. If all units used
551557
/// `CompileKind::Host`, then they would end up being identical, causing a
552558
/// collision in the `UnitGraph`, and Cargo would end up randomly choosing one
553559
/// value or the other.
554560
///
555-
/// The solution is to keep normal and build dependencies separate when
561+
/// The solution is to keep normal, build and artifact dependencies separate when
556562
/// building the unit graph, and then run this second pass which will try to
557563
/// combine shared dependencies safely. By adding a hash of the dependencies
558564
/// to the `Unit`, this allows the `CompileKind` to be changed back to `Host`
559-
/// without fear of an unwanted collision.
565+
/// and `artifact_target_for_features` to be removed without fear of an unwanted
566+
/// collision for build or artifact dependencies.
560567
fn rebuild_unit_graph_shared(
561568
interner: &UnitInterner,
562569
unit_graph: UnitGraph,
563570
roots: &[Unit],
564571
scrape_units: &[Unit],
565-
to_host: CompileKind,
572+
to_host: Option<CompileKind>,
566573
) -> (Vec<Unit>, Vec<Unit>, UnitGraph) {
567574
let mut result = UnitGraph::new();
568575
// Map of the old unit to the new unit, used to avoid recursing into units
@@ -595,7 +602,7 @@ fn traverse_and_share(
595602
new_graph: &mut UnitGraph,
596603
unit_graph: &UnitGraph,
597604
unit: &Unit,
598-
to_host: CompileKind,
605+
to_host: Option<CompileKind>,
599606
) -> Unit {
600607
if let Some(new_unit) = memo.get(unit) {
601608
// Already computed, no need to recompute.
@@ -615,10 +622,9 @@ fn traverse_and_share(
615622
})
616623
.collect();
617624
let new_dep_hash = dep_hash.finish();
618-
let new_kind = if unit.kind == to_host {
619-
CompileKind::Host
620-
} else {
621-
unit.kind
625+
let new_kind = match to_host {
626+
Some(to_host) if to_host == unit.kind => CompileKind::Host,
627+
_ => unit.kind,
622628
};
623629
let new_unit = interner.intern(
624630
&unit.pkg,
@@ -630,6 +636,9 @@ fn traverse_and_share(
630636
unit.is_std,
631637
new_dep_hash,
632638
unit.artifact,
639+
// Since `dep_hash` is now filled in, there's no need to specify the artifact target
640+
// for target-dependent feature resolution
641+
None,
633642
);
634643
assert!(memo.insert(unit.clone(), new_unit.clone()).is_none());
635644
new_graph.entry(new_unit.clone()).or_insert(new_deps);
@@ -788,6 +797,7 @@ fn override_rustc_crate_types(
788797
unit.is_std,
789798
unit.dep_hash,
790799
unit.artifact,
800+
unit.artifact_target_for_features,
791801
)
792802
};
793803
units[0] = match unit.target.kind() {

src/cargo/ops/cargo_compile/unit_generator.rs

+1
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ impl<'a> UnitGenerator<'a, '_> {
172172
/*is_std*/ false,
173173
/*dep_hash*/ 0,
174174
IsArtifact::No,
175+
None,
175176
)
176177
})
177178
.collect()

0 commit comments

Comments
 (0)