Skip to content

Commit bb9ff3d

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 cb019b6 commit bb9ff3d

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
@@ -19,14 +19,16 @@ use super::{BuildContext, CompileMode, Kind, Unit};
1919
use core::dependency::Kind as DepKind;
2020
use core::profiles::ProfileFor;
2121
use core::{Package, Target};
22-
use std::collections::HashMap;
22+
use std::collections::{HashMap, HashSet};
2323
use CargoResult;
2424

2525
pub fn build_unit_dependencies<'a, 'cfg>(
2626
roots: &[Unit<'a>],
2727
bcx: &BuildContext<'a, 'cfg>,
28-
mut deps: &mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
28+
deps: &mut HashMap<Unit<'a>, Vec<Unit<'a>>>,
2929
) -> CargoResult<()> {
30+
assert!(deps.len() == 0, "can only build unit deps once");
31+
3032
for unit in roots.iter() {
3133
// Dependencies of tests/benches should not have `panic` set.
3234
// We check the global test mode to see if we are running in `cargo
@@ -39,33 +41,35 @@ pub fn build_unit_dependencies<'a, 'cfg>(
3941
} else {
4042
ProfileFor::Any
4143
};
42-
deps_of(unit, bcx, &mut deps, profile_for)?;
44+
deps_of(unit, bcx, deps, profile_for)?;
4345
}
4446

47+
connect_run_custom_build_deps(bcx, deps);
48+
4549
Ok(())
4650
}
4751

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

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

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

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)