Skip to content

Commit 3c53211

Browse files
committed
Auto merge of #7857 - ehuss:fix-build-script-dupe, r=alexcrichton
Fix BuildScriptOutput when a build script is run multiple times. When I implemented profile build overrides, I created a scenario where a build script could run more than once for different scenarios. See the test for an example. However, the `BuildScriptOutputs` map did not take this into consideration. This caused multiple build script runs to stomp on the output of previous runs. This is further exacerbated by the new feature resolver in #7820 where build scripts can run with different features enabled. The solution is to make the map key unique for the Unit it is running for. Since this map must be updated on different threads, `Unit` cannot be used as a key, so I chose just using the metadata hash which is hopefully unique. Most of this patch is involved with the fussiness of getting the correct metadata hash (we want the RunCustomBuild unit's hash). I also added some checks to avoid collisions and assert assumptions.
2 parents 1bc7f53 + 2296af2 commit 3c53211

File tree

9 files changed

+296
-81
lines changed

9 files changed

+296
-81
lines changed

src/cargo/core/compiler/compilation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::core::compiler::CompileKind;
1111
use crate::core::{Edition, Package, PackageId, Target};
1212
use crate::util::{self, config, join_paths, process, CargoResult, Config, ProcessBuilder};
1313

14+
/// Structure with enough information to run `rustdoc --test`.
1415
pub struct Doctest {
1516
/// The package being doc-tested.
1617
pub package: Package,

src/cargo/core/compiler/context/compilation_files.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use crate::util::{self, CargoResult};
4141
///
4242
/// Note that the `Fingerprint` is in charge of tracking everything needed to determine if a
4343
/// rebuild is needed.
44-
#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
44+
#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
4545
pub struct Metadata(u64);
4646

4747
impl fmt::Display for Metadata {
@@ -50,6 +50,12 @@ impl fmt::Display for Metadata {
5050
}
5151
}
5252

53+
impl fmt::Debug for Metadata {
54+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55+
write!(f, "Metadata({:016x})", self.0)
56+
}
57+
}
58+
5359
/// Collection of information about the files emitted by the compiler, and the
5460
/// output directory structure.
5561
pub struct CompilationFiles<'a, 'cfg> {
@@ -213,6 +219,7 @@ impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
213219
pub fn build_script_dir(&self, unit: &Unit<'a>) -> PathBuf {
214220
assert!(unit.target.is_custom_build());
215221
assert!(!unit.mode.is_run_custom_build());
222+
assert!(self.metas.contains_key(unit));
216223
let dir = self.pkg_dir(unit);
217224
self.layout(CompileKind::Host).build().join(dir)
218225
}

src/cargo/core/compiler/context/mod.rs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
122122
})
123123
}
124124

125-
// Returns a mapping of the root package plus its immediate dependencies to
126-
// where the compiled libraries are all located.
125+
/// Starts compilation, waits for it to finish, and returns information
126+
/// about the result of compilation.
127127
pub fn compile(
128128
mut self,
129129
units: &[Unit<'a>],
@@ -245,16 +245,16 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
245245
super::output_depinfo(&mut self, unit)?;
246246
}
247247

248-
for (&(ref pkg, _), output) in self.build_script_outputs.lock().unwrap().iter() {
248+
for (pkg_id, output) in self.build_script_outputs.lock().unwrap().iter() {
249249
self.compilation
250250
.cfgs
251-
.entry(pkg.clone())
251+
.entry(pkg_id)
252252
.or_insert_with(HashSet::new)
253253
.extend(output.cfgs.iter().cloned());
254254

255255
self.compilation
256256
.extra_env
257-
.entry(pkg.clone())
257+
.entry(pkg_id)
258258
.or_insert_with(Vec::new)
259259
.extend(output.env.iter().cloned());
260260

@@ -347,6 +347,39 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
347347
&self.unit_dependencies[unit]
348348
}
349349

350+
/// Returns the RunCustomBuild Unit associated with the given Unit.
351+
///
352+
/// If the package does not have a build script, this returns None.
353+
pub fn find_build_script_unit(&self, unit: Unit<'a>) -> Option<Unit<'a>> {
354+
if unit.mode.is_run_custom_build() {
355+
return Some(unit);
356+
}
357+
self.unit_dependencies[&unit]
358+
.iter()
359+
.find(|unit_dep| {
360+
unit_dep.unit.mode.is_run_custom_build()
361+
&& unit_dep.unit.pkg.package_id() == unit.pkg.package_id()
362+
})
363+
.map(|unit_dep| unit_dep.unit)
364+
}
365+
366+
/// Returns the metadata hash for the RunCustomBuild Unit associated with
367+
/// the given unit.
368+
///
369+
/// If the package does not have a build script, this returns None.
370+
pub fn find_build_script_metadata(&self, unit: Unit<'a>) -> Option<Metadata> {
371+
let script_unit = self.find_build_script_unit(unit)?;
372+
Some(self.get_run_build_script_metadata(&script_unit))
373+
}
374+
375+
/// Returns the metadata hash for a RunCustomBuild unit.
376+
pub fn get_run_build_script_metadata(&self, unit: &Unit<'a>) -> Metadata {
377+
assert!(unit.mode.is_run_custom_build());
378+
self.files()
379+
.metadata(unit)
380+
.expect("build script should always have hash")
381+
}
382+
350383
pub fn is_primary_package(&self, unit: &Unit<'a>) -> bool {
351384
self.primary_packages.contains(&unit.pkg.package_id())
352385
}

src/cargo/core/compiler/custom_build.rs

Lines changed: 98 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::job::{Freshness, Job, Work};
2-
use super::{fingerprint, CompileKind, Context, Unit};
2+
use super::{fingerprint, Context, Unit};
3+
use crate::core::compiler::context::Metadata;
34
use crate::core::compiler::job_queue::JobState;
45
use crate::core::{profiles::ProfileRoot, PackageId};
56
use crate::util::errors::{CargoResult, CargoResultExt};
@@ -41,37 +42,49 @@ pub struct BuildOutput {
4142
/// This initially starts out as empty. Overridden build scripts get
4243
/// inserted during `build_map`. The rest of the entries are added
4344
/// immediately after each build script runs.
44-
pub type BuildScriptOutputs = HashMap<(PackageId, CompileKind), BuildOutput>;
45+
///
46+
/// The `Metadata` is the unique metadata hash for the RunCustomBuild Unit of
47+
/// the package. It needs a unique key, since the build script can be run
48+
/// multiple times with different profiles or features. We can't embed a
49+
/// `Unit` because this structure needs to be shareable between threads.
50+
#[derive(Default)]
51+
pub struct BuildScriptOutputs {
52+
outputs: HashMap<(PackageId, Metadata), BuildOutput>,
53+
}
4554

4655
/// Linking information for a `Unit`.
4756
///
4857
/// See `build_map` for more details.
4958
#[derive(Default)]
5059
pub struct BuildScripts {
60+
/// List of build script outputs this Unit needs to include for linking. Each
61+
/// element is an index into `BuildScriptOutputs`.
62+
///
5163
/// Cargo will use this `to_link` vector to add `-L` flags to compiles as we
5264
/// propagate them upwards towards the final build. Note, however, that we
5365
/// need to preserve the ordering of `to_link` to be topologically sorted.
5466
/// This will ensure that build scripts which print their paths properly will
5567
/// correctly pick up the files they generated (if there are duplicates
5668
/// elsewhere).
5769
///
58-
/// To preserve this ordering, the (id, kind) is stored in two places, once
70+
/// To preserve this ordering, the (id, metadata) is stored in two places, once
5971
/// in the `Vec` and once in `seen_to_link` for a fast lookup. We maintain
6072
/// this as we're building interactively below to ensure that the memory
6173
/// usage here doesn't blow up too much.
6274
///
6375
/// For more information, see #2354.
64-
pub to_link: Vec<(PackageId, CompileKind)>,
76+
pub to_link: Vec<(PackageId, Metadata)>,
6577
/// This is only used while constructing `to_link` to avoid duplicates.
66-
seen_to_link: HashSet<(PackageId, CompileKind)>,
67-
/// Host-only dependencies that have build scripts.
78+
seen_to_link: HashSet<(PackageId, Metadata)>,
79+
/// Host-only dependencies that have build scripts. Each element is an
80+
/// index into `BuildScriptOutputs`.
6881
///
6982
/// This is the set of transitive dependencies that are host-only
7083
/// (proc-macro, plugin, build-dependency) that contain a build script.
7184
/// Any `BuildOutput::library_paths` path relative to `target` will be
7285
/// added to LD_LIBRARY_PATH so that the compiler can find any dynamic
7386
/// libraries a build script may have generated.
74-
pub plugins: BTreeSet<PackageId>,
87+
pub plugins: BTreeSet<(PackageId, Metadata)>,
7588
}
7689

7790
/// Dependency information as declared by a build script.
@@ -94,9 +107,13 @@ pub fn prepare<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRe
94107
unit.target.name()
95108
));
96109

97-
let key = (unit.pkg.package_id(), unit.kind);
98-
99-
if cx.build_script_outputs.lock().unwrap().contains_key(&key) {
110+
let metadata = cx.get_run_build_script_metadata(unit);
111+
if cx
112+
.build_script_outputs
113+
.lock()
114+
.unwrap()
115+
.contains_key(unit.pkg.package_id(), metadata)
116+
{
100117
// The output is already set, thus the build script is overridden.
101118
fingerprint::prepare_target(cx, unit, false)
102119
} else {
@@ -231,9 +248,11 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
231248
.iter()
232249
.filter_map(|dep| {
233250
if dep.unit.mode.is_run_custom_build() {
251+
let dep_metadata = cx.get_run_build_script_metadata(&dep.unit);
234252
Some((
235253
dep.unit.pkg.manifest().links().unwrap().to_string(),
236254
dep.unit.pkg.package_id(),
255+
dep_metadata,
237256
))
238257
} else {
239258
None
@@ -255,10 +274,10 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
255274
script_out_dir.clone(),
256275
);
257276
let build_scripts = cx.build_scripts.get(unit).cloned();
258-
let kind = unit.kind;
259277
let json_messages = bcx.build_config.emit_json();
260278
let extra_verbose = bcx.config.extra_verbose();
261279
let (prev_output, prev_script_out_dir) = prev_build_output(cx, unit);
280+
let metadata_hash = cx.get_run_build_script_metadata(unit);
262281

263282
paths::create_dir_all(&script_dir)?;
264283
paths::create_dir_all(&script_out_dir)?;
@@ -286,15 +305,16 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
286305
// native dynamic libraries.
287306
if !build_plan {
288307
let build_script_outputs = build_script_outputs.lock().unwrap();
289-
for (name, id) in lib_deps {
290-
let key = (id, kind);
291-
let script_output = build_script_outputs.get(&key).ok_or_else(|| {
292-
internal(format!(
293-
"failed to locate build state for env \
294-
vars: {}/{:?}",
295-
id, kind
296-
))
297-
})?;
308+
for (name, dep_id, dep_metadata) in lib_deps {
309+
let script_output =
310+
build_script_outputs
311+
.get(dep_id, dep_metadata)
312+
.ok_or_else(|| {
313+
internal(format!(
314+
"failed to locate build state for env vars: {}/{}",
315+
dep_id, dep_metadata
316+
))
317+
})?;
298318
let data = &script_output.metadata;
299319
for &(ref key, ref value) in data.iter() {
300320
cmd.env(
@@ -360,7 +380,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
360380
build_script_outputs
361381
.lock()
362382
.unwrap()
363-
.insert((id, kind), parsed_output);
383+
.insert(id, metadata_hash, parsed_output);
364384
Ok(())
365385
});
366386

@@ -386,7 +406,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
386406
build_script_outputs
387407
.lock()
388408
.unwrap()
389-
.insert((id, kind), output);
409+
.insert(id, metadata_hash, output);
390410
Ok(())
391411
});
392412

@@ -614,7 +634,7 @@ impl BuildDeps {
614634
/// scripts.
615635
///
616636
/// The important one here is `build_scripts`, which for each `(package,
617-
/// kind)` stores a `BuildScripts` object which contains a list of
637+
/// metadata)` stores a `BuildScripts` object which contains a list of
618638
/// dependencies with build scripts that the unit should consider when
619639
/// linking. For example this lists all dependencies' `-L` flags which need to
620640
/// be propagated transitively.
@@ -644,20 +664,27 @@ pub fn build_map<'b, 'cfg>(cx: &mut Context<'b, 'cfg>, units: &[Unit<'b>]) -> Ca
644664
}
645665

646666
// If there is a build script override, pre-fill the build output.
647-
if let Some(links) = unit.pkg.manifest().links() {
648-
if let Some(output) = cx.bcx.script_override(links, unit.kind) {
649-
let key = (unit.pkg.package_id(), unit.kind);
650-
cx.build_script_outputs
651-
.lock()
652-
.unwrap()
653-
.insert(key, output.clone());
667+
if unit.mode.is_run_custom_build() {
668+
if let Some(links) = unit.pkg.manifest().links() {
669+
if let Some(output) = cx.bcx.script_override(links, unit.kind) {
670+
let metadata = cx.get_run_build_script_metadata(unit);
671+
cx.build_script_outputs.lock().unwrap().insert(
672+
unit.pkg.package_id(),
673+
metadata,
674+
output.clone(),
675+
);
676+
}
654677
}
655678
}
656679

657680
let mut ret = BuildScripts::default();
658681

682+
// If a package has a build script, add itself as something to inspect for linking.
659683
if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {
660-
add_to_link(&mut ret, unit.pkg.package_id(), unit.kind);
684+
let script_meta = cx
685+
.find_build_script_metadata(*unit)
686+
.expect("has_custom_build should have RunCustomBuild");
687+
add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
661688
}
662689

663690
// Load any dependency declarations from a previous run.
@@ -676,11 +703,10 @@ pub fn build_map<'b, 'cfg>(cx: &mut Context<'b, 'cfg>, units: &[Unit<'b>]) -> Ca
676703
let dep_scripts = build(out, cx, dep_unit)?;
677704

678705
if dep_unit.target.for_host() {
679-
ret.plugins
680-
.extend(dep_scripts.to_link.iter().map(|p| &p.0).cloned());
706+
ret.plugins.extend(dep_scripts.to_link.iter().cloned());
681707
} else if dep_unit.target.linkable() {
682-
for &(pkg, kind) in dep_scripts.to_link.iter() {
683-
add_to_link(&mut ret, pkg, kind);
708+
for &(pkg, metadata) in dep_scripts.to_link.iter() {
709+
add_to_link(&mut ret, pkg, metadata);
684710
}
685711
}
686712
}
@@ -693,9 +719,9 @@ pub fn build_map<'b, 'cfg>(cx: &mut Context<'b, 'cfg>, units: &[Unit<'b>]) -> Ca
693719

694720
// When adding an entry to 'to_link' we only actually push it on if the
695721
// script hasn't seen it yet (e.g., we don't push on duplicates).
696-
fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, kind: CompileKind) {
697-
if scripts.seen_to_link.insert((pkg, kind)) {
698-
scripts.to_link.push((pkg, kind));
722+
fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, metadata: Metadata) {
723+
if scripts.seen_to_link.insert((pkg, metadata)) {
724+
scripts.to_link.push((pkg, metadata));
699725
}
700726
}
701727

@@ -741,3 +767,37 @@ fn prev_build_output<'a, 'cfg>(
741767
prev_script_out_dir,
742768
)
743769
}
770+
771+
impl BuildScriptOutputs {
772+
/// Inserts a new entry into the map.
773+
fn insert(&mut self, pkg_id: PackageId, metadata: Metadata, parsed_output: BuildOutput) {
774+
match self.outputs.entry((pkg_id, metadata)) {
775+
Entry::Vacant(entry) => {
776+
entry.insert(parsed_output);
777+
}
778+
Entry::Occupied(entry) => panic!(
779+
"build script output collision for {}/{}\n\
780+
old={:?}\nnew={:?}",
781+
pkg_id,
782+
metadata,
783+
entry.get(),
784+
parsed_output
785+
),
786+
}
787+
}
788+
789+
/// Returns `true` if the given key already exists.
790+
fn contains_key(&self, pkg_id: PackageId, metadata: Metadata) -> bool {
791+
self.outputs.contains_key(&(pkg_id, metadata))
792+
}
793+
794+
/// Gets the build output for the given key.
795+
pub fn get(&self, pkg_id: PackageId, meta: Metadata) -> Option<&BuildOutput> {
796+
self.outputs.get(&(pkg_id, meta))
797+
}
798+
799+
/// Returns an iterator over all entries.
800+
pub fn iter(&self) -> impl Iterator<Item = (PackageId, &BuildOutput)> {
801+
self.outputs.iter().map(|(key, value)| (key.0, value))
802+
}
803+
}

0 commit comments

Comments
 (0)