Skip to content

Commit 9efa0d5

Browse files
committed
Fix non-determinism with new feature resolver.
1 parent 2c10f26 commit 9efa0d5

File tree

9 files changed

+287
-24
lines changed

9 files changed

+287
-24
lines changed

src/cargo/core/compiler/build_context/target_info.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,17 @@ impl RustcTargetData {
683683
}
684684
}
685685

686+
// This is a hack. The unit_dependency graph builder "pretends" that
687+
// `CompileKind::Host` is `CompileKind::Target(host)` if the
688+
// `--target` flag is not specified. Since the unit_dependency code
689+
// needs access to the target config data, create a copy so that it
690+
// can be found. See `rebuild_unit_graph_shared` for why this is done.
691+
if requested_kinds.iter().any(CompileKind::is_host) {
692+
let ct = CompileTarget::new(&rustc.host)?;
693+
target_info.insert(ct, host_info.clone());
694+
target_config.insert(ct, host_config.clone());
695+
}
696+
686697
Ok(RustcTargetData {
687698
rustc,
688699
target_config,

src/cargo/core/compiler/standard_lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ pub fn generate_std_roots(
179179
mode,
180180
features.clone(),
181181
/*is_std*/ true,
182+
/*dep_hash*/ 0,
182183
));
183184
}
184185
}

src/cargo/core/compiler/unit.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ pub struct UnitInner {
5757
pub features: Vec<InternedString>,
5858
/// Whether this is a standard library unit.
5959
pub is_std: bool,
60+
/// A hash of all dependencies of this unit.
61+
///
62+
/// This is used to keep the `Unit` unique in the situation where two
63+
/// otherwise identical units need to link to different dependencies. This
64+
/// can happen, for example, when there are shared dependencies that need
65+
/// to be built with different features between normal and build
66+
/// dependencies. See `rebuild_unit_graph_shared` for more on why this is
67+
/// done.
68+
///
69+
/// This value initially starts as 0, and then is filled in via a
70+
/// second-pass after all the unit dependencies have been computed.
71+
pub dep_hash: u64,
6072
}
6173

6274
impl UnitInner {
@@ -123,6 +135,8 @@ impl fmt::Debug for Unit {
123135
.field("kind", &self.kind)
124136
.field("mode", &self.mode)
125137
.field("features", &self.features)
138+
.field("is_std", &self.is_std)
139+
.field("dep_hash", &self.dep_hash)
126140
.finish()
127141
}
128142
}
@@ -164,6 +178,7 @@ impl UnitInterner {
164178
mode: CompileMode,
165179
features: Vec<InternedString>,
166180
is_std: bool,
181+
dep_hash: u64,
167182
) -> Unit {
168183
let target = match (is_std, target.kind()) {
169184
// This is a horrible hack to support build-std. `libstd` declares
@@ -194,6 +209,7 @@ impl UnitInterner {
194209
mode,
195210
features,
196211
is_std,
212+
dep_hash,
197213
});
198214
Unit { inner }
199215
}

src/cargo/core/compiler/unit_dependencies.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ fn new_unit_dep_with_profile(
612612
let features = state.activated_features(pkg.package_id(), features_for);
613613
let unit = state
614614
.interner
615-
.intern(pkg, target, profile, kind, mode, features, state.is_std);
615+
.intern(pkg, target, profile, kind, mode, features, state.is_std, 0);
616616
Ok(UnitDep {
617617
unit,
618618
unit_for,

src/cargo/core/compiler/unit_graph.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub struct UnitDep {
1616
/// The dependency unit.
1717
pub unit: Unit,
1818
/// The purpose of this dependency (a dependency for a test, or a build
19-
/// script, etc.).
19+
/// script, etc.). Do not use this after the unit graph has been built.
2020
pub unit_for: UnitFor,
2121
/// The name the parent uses to refer to this dependency.
2222
pub extern_crate_name: InternedString,

src/cargo/ops/cargo_compile.rs

Lines changed: 140 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@
2323
//! repeats until the queue is empty.
2424
2525
use std::collections::{BTreeSet, HashMap, HashSet};
26+
use std::hash::{Hash, Hasher};
2627
use std::iter::FromIterator;
2728
use std::sync::Arc;
2829

30+
use crate::core::compiler::standard_lib;
2931
use crate::core::compiler::unit_dependencies::build_unit_dependencies;
30-
use crate::core::compiler::{standard_lib, unit_graph};
32+
use crate::core::compiler::unit_graph::{self, UnitDep, UnitGraph};
3133
use crate::core::compiler::{BuildConfig, BuildContext, Compilation, Context};
32-
use crate::core::compiler::{CompileKind, CompileMode, RustcTargetData, Unit};
34+
use crate::core::compiler::{CompileKind, CompileMode, CompileTarget, RustcTargetData, Unit};
3335
use crate::core::compiler::{DefaultExecutor, Executor, UnitInterner};
3436
use crate::core::profiles::{Profiles, UnitFor};
3537
use crate::core::resolver::features::{self, FeaturesFor};
@@ -39,7 +41,7 @@ use crate::core::{PackageId, PackageIdSpec, TargetKind, Workspace};
3941
use crate::ops;
4042
use crate::ops::resolve::WorkspaceResolve;
4143
use crate::util::config::Config;
42-
use crate::util::{closest_msg, profile, CargoResult};
44+
use crate::util::{closest_msg, profile, CargoResult, StableHasher};
4345

4446
/// Contains information about how a package should be compiled.
4547
///
@@ -410,11 +412,24 @@ pub fn create_bcx<'a, 'cfg>(
410412
workspace_resolve.as_ref().unwrap_or(&resolve),
411413
)?;
412414

413-
let units = generate_targets(
415+
// If `--target` has not been specified, then the unit graph is built
416+
// assuming `--target $HOST` was specified. See
417+
// `rebuild_unit_graph_shared` for more on why this is done.
418+
let explicit_host_kind = CompileKind::Target(CompileTarget::new(&target_data.rustc.host)?);
419+
let explicit_host_kinds: Vec<_> = build_config
420+
.requested_kinds
421+
.iter()
422+
.map(|kind| match kind {
423+
CompileKind::Host => explicit_host_kind,
424+
CompileKind::Target(t) => CompileKind::Target(*t),
425+
})
426+
.collect();
427+
428+
let mut units = generate_targets(
414429
ws,
415430
&to_builds,
416431
filter,
417-
&build_config.requested_kinds,
432+
&explicit_host_kinds,
418433
build_config.mode,
419434
&resolve,
420435
&workspace_resolve,
@@ -442,7 +457,7 @@ pub fn create_bcx<'a, 'cfg>(
442457
&crates,
443458
std_resolve,
444459
std_features,
445-
&build_config.requested_kinds,
460+
&explicit_host_kinds,
446461
&pkg_set,
447462
interner,
448463
&profiles,
@@ -451,6 +466,34 @@ pub fn create_bcx<'a, 'cfg>(
451466
Default::default()
452467
};
453468

469+
let mut unit_graph = build_unit_dependencies(
470+
ws,
471+
&pkg_set,
472+
&resolve,
473+
&resolved_features,
474+
std_resolve_features.as_ref(),
475+
&units,
476+
&std_roots,
477+
build_config.mode,
478+
&target_data,
479+
&profiles,
480+
interner,
481+
)?;
482+
483+
if build_config
484+
.requested_kinds
485+
.iter()
486+
.any(CompileKind::is_host)
487+
{
488+
// Rebuild the unit graph, replacing the explicit host targets with
489+
// CompileKind::Host, merging any dependencies shared with build
490+
// dependencies.
491+
let new_graph = rebuild_unit_graph_shared(interner, unit_graph, &units, explicit_host_kind);
492+
// This would be nicer with destructuring assignment.
493+
units = new_graph.0;
494+
unit_graph = new_graph.1;
495+
}
496+
454497
let mut extra_compiler_args = HashMap::new();
455498
if let Some(args) = extra_args {
456499
if units.len() != 1 {
@@ -485,20 +528,6 @@ pub fn create_bcx<'a, 'cfg>(
485528
}
486529
}
487530

488-
let unit_graph = build_unit_dependencies(
489-
ws,
490-
&pkg_set,
491-
&resolve,
492-
&resolved_features,
493-
std_resolve_features.as_ref(),
494-
&units,
495-
&std_roots,
496-
build_config.mode,
497-
&target_data,
498-
&profiles,
499-
interner,
500-
)?;
501-
502531
let bcx = BuildContext::new(
503532
ws,
504533
pkg_set,
@@ -789,6 +818,7 @@ fn generate_targets(
789818
target_mode,
790819
features.clone(),
791820
/*is_std*/ false,
821+
/*dep_hash*/ 0,
792822
);
793823
units.insert(unit);
794824
}
@@ -1169,3 +1199,93 @@ fn filter_targets<'a>(
11691199
}
11701200
proposals
11711201
}
1202+
1203+
/// This is used to rebuild the unit graph, sharing host dependencies if possible.
1204+
///
1205+
/// This will translate any unit's `CompileKind::Target(host)` to
1206+
/// `CompileKind::Host` if the kind is equal to `to_host`. This also handles
1207+
/// generating the unit `dep_hash`, and merging shared units if possible.
1208+
///
1209+
/// This is necessary because if normal dependencies used `CompileKind::Host`,
1210+
/// there would be no way to distinguish those units from build-dependency
1211+
/// units. This can cause a problem if a shared normal/build dependency needs
1212+
/// to link to another dependency whose features differ based on whether or
1213+
/// not it is a normal or build dependency. If both units used
1214+
/// `CompileKind::Host`, then they would end up being identical, causing a
1215+
/// collision in the `UnitGraph`, and Cargo would end up randomly choosing one
1216+
/// value or the other.
1217+
///
1218+
/// The solution is to keep normal and build dependencies separate when
1219+
/// building the unit graph, and then run this second pass which will try to
1220+
/// combine shared dependencies safely. By adding a hash of the dependencies
1221+
/// to the `Unit`, this allows the `CompileKind` to be changed back to `Host`
1222+
/// without fear of an unwanted collision.
1223+
fn rebuild_unit_graph_shared(
1224+
interner: &UnitInterner,
1225+
unit_graph: UnitGraph,
1226+
roots: &[Unit],
1227+
to_host: CompileKind,
1228+
) -> (Vec<Unit>, UnitGraph) {
1229+
let mut result = UnitGraph::new();
1230+
// Map of the old unit to the new unit, used to avoid recursing into units
1231+
// that have already been computed to improve performance.
1232+
let mut memo = HashMap::new();
1233+
let new_roots = roots
1234+
.iter()
1235+
.map(|root| {
1236+
traverse_and_share(interner, &mut memo, &mut result, &unit_graph, root, to_host)
1237+
})
1238+
.collect();
1239+
(new_roots, result)
1240+
}
1241+
1242+
/// Recursive function for rebuilding the graph.
1243+
///
1244+
/// This walks `unit_graph`, starting at the given `unit`. It inserts the new
1245+
/// units into `new_graph`, and returns a new updated version of the given
1246+
/// unit (`dep_hash` is filled in, and `kind` switched if necessary).
1247+
fn traverse_and_share(
1248+
interner: &UnitInterner,
1249+
memo: &mut HashMap<Unit, Unit>,
1250+
new_graph: &mut UnitGraph,
1251+
unit_graph: &UnitGraph,
1252+
unit: &Unit,
1253+
to_host: CompileKind,
1254+
) -> Unit {
1255+
if let Some(new_unit) = memo.get(unit) {
1256+
// Already computed, no need to recompute.
1257+
return new_unit.clone();
1258+
}
1259+
let mut dep_hash = StableHasher::new();
1260+
let new_deps: Vec<_> = unit_graph[unit]
1261+
.iter()
1262+
.map(|dep| {
1263+
let new_dep_unit =
1264+
traverse_and_share(interner, memo, new_graph, unit_graph, &dep.unit, to_host);
1265+
new_dep_unit.hash(&mut dep_hash);
1266+
UnitDep {
1267+
unit: new_dep_unit,
1268+
..dep.clone()
1269+
}
1270+
})
1271+
.collect();
1272+
let new_dep_hash = dep_hash.finish();
1273+
let new_kind = if unit.kind == to_host {
1274+
CompileKind::Host
1275+
} else {
1276+
unit.kind
1277+
};
1278+
let new_unit = interner.intern(
1279+
&unit.pkg,
1280+
&unit.target,
1281+
unit.profile,
1282+
new_kind,
1283+
unit.mode,
1284+
unit.features.clone(),
1285+
unit.is_std,
1286+
new_dep_hash,
1287+
);
1288+
assert!(memo.insert(unit.clone(), new_unit.clone()).is_none());
1289+
new_graph.entry(new_unit.clone()).or_insert(new_deps);
1290+
new_unit
1291+
}

src/cargo/util/config/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,7 @@ pub struct CargoBuildConfig {
17881788
/// a = 'a b c'
17891789
/// b = ['a', 'b', 'c']
17901790
/// ```
1791-
#[derive(Debug, Deserialize)]
1791+
#[derive(Debug, Deserialize, Clone)]
17921792
pub struct StringList(Vec<String>);
17931793

17941794
impl StringList {

src/cargo/util/config/target.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub struct TargetCfgConfig {
2020
}
2121

2222
/// Config definition of a `[target]` table.
23-
#[derive(Debug)]
23+
#[derive(Debug, Clone)]
2424
pub struct TargetConfig {
2525
/// Process to run as a wrapper for `cargo run`, `test`, and `bench` commands.
2626
pub runner: OptValue<PathAndArgs>,

0 commit comments

Comments
 (0)