Skip to content

Commit f9d4927

Browse files
committed
Partially revert dep changes in #5651
Some logic which was tweaked around the dependencies of build script targets was tweaked slightly in a way that causes cargo to stack overflow by accientally adding a dependency loop. This commit implements one of the strategies discussed in #5711 to fix this situation. The problem here is that when calculating the deps of a build script we need the build scripts of *other* packages, but the exact profile is somewhat difficult to guess at the moment we're generating our build script unit. To solve this the dependencies towards other build scripts' executions is added in a different pass after all other units have been assembled. At this point we should know for sure that all build script executions are in the dependency graph, and we just need to add a few more edges. Closes #5708
1 parent 88f92e5 commit f9d4927

File tree

3 files changed

+128
-39
lines changed

3 files changed

+128
-39
lines changed

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

Lines changed: 99 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
//! (for example, with and without tests), so we actually build a dependency
1616
//! graph of `Unit`s, which capture these properties.
1717
18-
use std::collections::HashMap;
18+
use std::collections::{HashMap, HashSet};
1919

2020
use CargoResult;
2121
use core::dependency::Kind as DepKind;
@@ -26,8 +26,10 @@ use super::{BuildContext, CompileMode, Kind, Unit};
2626
pub fn build_unit_dependencies<'a, 'cfg>(
2727
roots: &[Unit<'a>],
2828
bcx: &BuildContext<'a, 'cfg>,
29-
mut deps: &mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
29+
deps: &mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
3030
) -> CargoResult<()> {
31+
assert!(deps.len() == 0, "can only build unit deps once");
32+
3133
for unit in roots.iter() {
3234
// Dependencies of tests/benches should not have `panic` set.
3335
// We check the global test mode to see if we are running in `cargo
@@ -40,34 +42,36 @@ pub fn build_unit_dependencies<'a, 'cfg>(
4042
} else {
4143
ProfileFor::Any
4244
};
43-
deps_of(unit, bcx, &mut deps, profile_for)?;
45+
deps_of(unit, bcx, deps, profile_for)?;
4446
}
4547
trace!("ALL UNIT DEPENDENCIES {:#?}", deps);
4648

49+
connect_run_custom_build_deps(bcx, deps);
50+
4751
Ok(())
4852
}
4953

50-
fn deps_of<'a, 'b, 'cfg>(
54+
fn deps_of<'a, 'cfg>(
5155
unit: &Unit<'a>,
5256
bcx: &BuildContext<'a, 'cfg>,
53-
deps: &'b mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
57+
deps: &mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
5458
profile_for: ProfileFor,
55-
) -> CargoResult<&'b [Unit<'a>]> {
59+
) -> CargoResult<()> {
5660
// Currently the `deps` map does not include `profile_for`. This should
5761
// be safe for now. `TestDependency` only exists to clear the `panic`
5862
// flag, and you'll never ask for a `unit` with `panic` set as a
5963
// `TestDependency`. `CustomBuild` should also be fine since if the
6064
// requested unit's settings are the same as `Any`, `CustomBuild` can't
6165
// affect anything else in the hierarchy.
6266
if !deps.contains_key(unit) {
63-
let unit_deps = compute_deps(unit, bcx, deps, profile_for)?;
67+
let unit_deps = compute_deps(unit, bcx, profile_for)?;
6468
let to_insert: Vec<_> = unit_deps.iter().map(|&(unit, _)| unit).collect();
6569
deps.insert(*unit, to_insert);
6670
for (unit, profile_for) in unit_deps {
6771
deps_of(&unit, bcx, deps, profile_for)?;
6872
}
6973
}
70-
Ok(deps[unit].as_ref())
74+
Ok(())
7175
}
7276

7377
/// For a package, return all targets which are registered as dependencies
@@ -77,11 +81,10 @@ fn deps_of<'a, 'b, 'cfg>(
7781
fn compute_deps<'a, 'b, 'cfg>(
7882
unit: &Unit<'a>,
7983
bcx: &BuildContext<'a, 'cfg>,
80-
deps: &'b mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
8184
profile_for: ProfileFor,
8285
) -> CargoResult<Vec<(Unit<'a>, ProfileFor)>> {
8386
if unit.mode.is_run_custom_build() {
84-
return compute_deps_custom_build(unit, bcx, deps);
87+
return compute_deps_custom_build(unit, bcx);
8588
} else if unit.mode.is_doc() && !unit.mode.is_any_test() {
8689
// Note: This does not include Doctest.
8790
return compute_deps_doc(unit, bcx);
@@ -194,39 +197,27 @@ fn compute_deps<'a, 'b, 'cfg>(
194197
fn compute_deps_custom_build<'a, 'cfg>(
195198
unit: &Unit<'a>,
196199
bcx: &BuildContext<'a, 'cfg>,
197-
deps: &mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
198200
) -> CargoResult<Vec<(Unit<'a>, ProfileFor)>> {
199201
// When not overridden, then the dependencies to run a build script are:
200202
//
201203
// 1. Compiling the build script itself
202204
// 2. For each immediate dependency of our package which has a `links`
203205
// key, the execution of that build script.
204-
let deps = deps
205-
.iter()
206-
.find(|(key, _deps)| key.pkg == unit.pkg && !key.target.is_custom_build())
207-
.expect("can't find package deps")
208-
.1;
209-
Ok(deps.iter()
210-
.filter_map(|unit| {
211-
if !unit.target.linkable() || unit.pkg.manifest().links().is_none() {
212-
return None;
213-
}
214-
dep_build_script(unit, bcx)
215-
})
216-
.chain(Some((
217-
new_unit(
218-
bcx,
219-
unit.pkg,
220-
unit.target,
221-
ProfileFor::CustomBuild,
222-
Kind::Host, // build scripts always compiled for the host
223-
CompileMode::Build,
224-
),
225-
// All dependencies of this unit should use profiles for custom
226-
// builds.
227-
ProfileFor::CustomBuild,
228-
)))
229-
.collect())
206+
//
207+
// We don't have a great way of handling (2) here right now so this is
208+
// deferred until after the graph of all unit dependencies has been
209+
// constructed.
210+
let unit = new_unit(
211+
bcx,
212+
unit.pkg,
213+
unit.target,
214+
ProfileFor::CustomBuild,
215+
Kind::Host, // build scripts always compiled for the host
216+
CompileMode::Build,
217+
);
218+
// All dependencies of this unit should use profiles for custom
219+
// builds.
220+
Ok(vec![(unit, ProfileFor::CustomBuild)])
230221
}
231222

232223
/// Returns the dependencies necessary to document a package
@@ -371,3 +362,74 @@ fn new_unit<'a>(
371362
mode,
372363
}
373364
}
365+
366+
/// Fill in missing dependencies for units of the `RunCustomBuild`
367+
///
368+
/// As mentioned above in `compute_deps_custom_build` each build script
369+
/// execution has two dependencies. The first is compiling the build script
370+
/// itself (already added) and the second is that all crates the package of the
371+
/// build script depends on with `links` keys, their build script execution. (a
372+
/// bit confusing eh?)
373+
///
374+
/// Here we take the entire `deps` map and add more dependencies from execution
375+
/// of one build script to execution of another build script.
376+
fn connect_run_custom_build_deps<'a>(
377+
bcx: &BuildContext,
378+
deps: &mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
379+
) {
380+
let mut new_deps = Vec::new();
381+
382+
{
383+
// First up build a reverse dependency map. This is a mapping of all
384+
// `RunCustomBuild` known steps to the unit which depends on them. For
385+
// example a library might depend on a build script, so this map will
386+
// have the build script as the key and the library would be in the
387+
// value's set.
388+
let mut reverse_deps = HashMap::new();
389+
for (unit, deps) in deps.iter() {
390+
for dep in deps {
391+
if dep.mode == CompileMode::RunCustomBuild {
392+
reverse_deps.entry(dep)
393+
.or_insert(HashSet::new())
394+
.insert(unit);
395+
}
396+
}
397+
}
398+
399+
// And next we take a look at all build scripts executions listed in the
400+
// dependency map. Our job here is to take everything that depends on
401+
// this build script (from our reverse map above) and look at the other
402+
// package dependencies of these parents.
403+
//
404+
// If we depend on a linkable target and the build script mentions
405+
// `links`, then we depend on that package's build script! Here we use
406+
// `dep_build_script` to manufacture an appropriate build script unit to
407+
// depend on.
408+
for unit in deps.keys().filter(|k| k.mode == CompileMode::RunCustomBuild) {
409+
let reverse_deps = match reverse_deps.get(unit) {
410+
Some(set) => set,
411+
None => continue,
412+
};
413+
414+
let to_add = reverse_deps
415+
.iter()
416+
.flat_map(|reverse_dep| deps[reverse_dep].iter())
417+
.filter(|other| {
418+
other.pkg != unit.pkg &&
419+
other.target.linkable() &&
420+
other.pkg.manifest().links().is_some()
421+
})
422+
.filter_map(|other| dep_build_script(other, bcx).map(|p| p.0))
423+
.collect::<HashSet<_>>();
424+
425+
if !to_add.is_empty() {
426+
new_deps.push((*unit, to_add));
427+
}
428+
}
429+
}
430+
431+
// And finally, add in all the missing dependencies!
432+
for (unit, new_deps) in new_deps {
433+
deps.get_mut(&unit).unwrap().extend(new_deps);
434+
}
435+
}

tests/testsuite/build_script.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3080,9 +3080,11 @@ fn panic_abort_with_build_scripts() {
30803080
execs().with_status(0),
30813081
);
30823082

3083+
p.root().join("target").rm_rf();
3084+
30833085
assert_that(
3084-
p.cargo("test --release"),
3085-
execs().with_status(0)
3086+
p.cargo("test --release -v"),
3087+
execs().with_status(0).with_stderr_does_not_contain("[..]panic[..]"),
30863088
);
30873089
}
30883090

tests/testsuite/test.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4138,3 +4138,28 @@ fn json_artifact_includes_test_flag() {
41384138
),
41394139
);
41404140
}
4141+
4142+
#[test]
4143+
fn test_build_script_links() {
4144+
let p = project("foo")
4145+
.file(
4146+
"Cargo.toml",
4147+
r#"
4148+
[package]
4149+
name = "foo"
4150+
version = "0.0.1"
4151+
links = 'something'
4152+
4153+
[lib]
4154+
test = false
4155+
"#,
4156+
)
4157+
.file("build.rs", "fn main() {}")
4158+
.file("src/lib.rs", "")
4159+
.build();
4160+
4161+
assert_that(
4162+
p.cargo("test --no-run"),
4163+
execs().with_status(0),
4164+
);
4165+
}

0 commit comments

Comments
 (0)