Skip to content

Commit 607f954

Browse files
committed
Auto merge of #5232 - matklad:unit-map, r=alexcrichton
Refactor context to extract dependencies calculation to a separate mod This makes unit dependency graph construction eager and moves it to a separate file. Hopefully, this makes `Context` slightly easier to understand :)
2 parents d4bd588 + 1b63fcf commit 607f954

File tree

8 files changed

+341
-246
lines changed

8 files changed

+341
-246
lines changed

src/cargo/ops/cargo_clean.rs

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ pub fn clean(ws: &Workspace, opts: &CleanOptions) -> CargoResult<()> {
100100
}
101101

102102
cx.probe_target_info()?;
103+
cx.build_unit_dependencies(&units)?;
103104

104105
for unit in units.iter() {
105106
rm_rf(&cx.fingerprint_dir(unit), config)?;

src/cargo/ops/cargo_rustc/context.rs renamed to src/cargo/ops/cargo_rustc/context/mod.rs

+29-238
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use jobserver::Client;
1414

1515
use core::{Package, PackageId, PackageSet, Profile, Resolve, Target};
1616
use core::{Dependency, Profiles, TargetKind, Workspace};
17-
use core::dependency::Kind as DepKind;
1817
use util::{self, internal, profile, Cfg, CfgExpr, Config, ProcessBuilder};
1918
use util::errors::{CargoResult, CargoResultExt};
2019

@@ -25,6 +24,9 @@ use super::layout::Layout;
2524
use super::links::Links;
2625
use super::{BuildConfig, Compilation, Kind};
2726

27+
mod unit_dependencies;
28+
use self::unit_dependencies::build_unit_dependencies;
29+
2830
/// All information needed to define a Unit.
2931
///
3032
/// A unit is an object that has enough information so that cargo knows how to build it.
@@ -102,6 +104,7 @@ pub struct Context<'a, 'cfg: 'a> {
102104
profiles: &'a Profiles,
103105
incremental_env: Option<bool>,
104106

107+
unit_dependencies: Option<HashMap<Unit<'a>, Vec<Unit<'a>>>>,
105108
/// For each Unit, a list all files produced as a triple of
106109
///
107110
/// - File name that will be produced by the build process (in `deps`)
@@ -204,6 +207,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
204207
jobserver,
205208
build_script_overridden: HashSet::new(),
206209

210+
unit_dependencies: None,
207211
// TODO: Pre-Calculate these with a topo-sort, rather than lazy-calculating
208212
target_filenames: HashMap::new(),
209213
target_metadatas: HashMap::new(),
@@ -232,6 +236,12 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
232236
Ok(())
233237
}
234238

239+
pub fn build_unit_dependencies(&mut self, units: &[Unit<'a>]) -> CargoResult<()> {
240+
assert!(self.unit_dependencies.is_none());
241+
self.unit_dependencies = Some(build_unit_dependencies(units, self)?);
242+
Ok(())
243+
}
244+
235245
/// Ensure that we've collected all target-specific information to compile
236246
/// all the units mentioned in `units`.
237247
pub fn probe_target_info(&mut self) -> CargoResult<()> {
@@ -361,7 +371,7 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
361371
if is_plugin {
362372
self.used_in_plugin.insert(*unit);
363373
}
364-
for unit in self.dep_targets(unit)? {
374+
for unit in self.dep_targets(unit) {
365375
self.walk_used_in_plugin_map(&unit, is_plugin || unit.target.for_host(), visited)?;
366376
}
367377
Ok(())
@@ -526,9 +536,10 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
526536
.hash(&mut hasher);
527537

528538
// Mix in the target-metadata of all the dependencies of this target
529-
if let Ok(deps) = self.dep_targets(unit) {
530-
let mut deps_metadata = deps.into_iter()
531-
.map(|dep_unit| self.target_metadata(&dep_unit))
539+
{
540+
let mut deps_metadata = self.dep_targets(unit)
541+
.iter()
542+
.map(|dep_unit| self.target_metadata(dep_unit))
532543
.collect::<Vec<_>>();
533544
deps_metadata.sort();
534545
deps_metadata.hash(&mut hasher);
@@ -770,236 +781,25 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
770781

771782
/// For a package, return all targets which are registered as dependencies
772783
/// for that package.
773-
pub fn dep_targets(&self, unit: &Unit<'a>) -> CargoResult<Vec<Unit<'a>>> {
774-
if unit.profile.run_custom_build {
775-
return self.dep_run_custom_build(unit);
776-
} else if unit.profile.doc && !unit.profile.test {
777-
return self.doc_deps(unit);
778-
}
779-
780-
let id = unit.pkg.package_id();
781-
let deps = self.resolve.deps(id);
782-
let mut ret = deps.filter(|dep| {
783-
unit.pkg
784-
.dependencies()
785-
.iter()
786-
.filter(|d| d.name() == dep.name() && d.version_req().matches(dep.version()))
787-
.any(|d| {
788-
// If this target is a build command, then we only want build
789-
// dependencies, otherwise we want everything *other than* build
790-
// dependencies.
791-
if unit.target.is_custom_build() != d.is_build() {
792-
return false;
793-
}
794-
795-
// If this dependency is *not* a transitive dependency, then it
796-
// only applies to test/example targets
797-
if !d.is_transitive() && !unit.target.is_test() && !unit.target.is_example()
798-
&& !unit.profile.test
799-
{
800-
return false;
801-
}
802-
803-
// If this dependency is only available for certain platforms,
804-
// make sure we're only enabling it for that platform.
805-
if !self.dep_platform_activated(d, unit.kind) {
806-
return false;
807-
}
808-
809-
// If the dependency is optional, then we're only activating it
810-
// if the corresponding feature was activated
811-
if d.is_optional() && !self.resolve.features(id).contains(&*d.name()) {
812-
return false;
813-
}
814-
815-
// If we've gotten past all that, then this dependency is
816-
// actually used!
817-
true
818-
})
819-
}).filter_map(|id| match self.get_package(id) {
820-
Ok(pkg) => pkg.targets().iter().find(|t| t.is_lib()).map(|t| {
821-
let unit = Unit {
822-
pkg,
823-
target: t,
824-
profile: self.lib_or_check_profile(unit, t),
825-
kind: unit.kind.for_target(t),
826-
};
827-
Ok(unit)
828-
}),
829-
Err(e) => Some(Err(e)),
830-
})
831-
.collect::<CargoResult<Vec<_>>>()?;
832-
833-
// If this target is a build script, then what we've collected so far is
834-
// all we need. If this isn't a build script, then it depends on the
835-
// build script if there is one.
836-
if unit.target.is_custom_build() {
837-
return Ok(ret);
838-
}
839-
ret.extend(self.dep_build_script(unit));
840-
841-
// If this target is a binary, test, example, etc, then it depends on
842-
// the library of the same package. The call to `resolve.deps` above
843-
// didn't include `pkg` in the return values, so we need to special case
844-
// it here and see if we need to push `(pkg, pkg_lib_target)`.
845-
if unit.target.is_lib() && !unit.profile.doc {
846-
return Ok(ret);
847-
}
848-
ret.extend(self.maybe_lib(unit));
849-
850-
// Integration tests/benchmarks require binaries to be built
851-
if unit.profile.test && (unit.target.is_test() || unit.target.is_bench()) {
852-
ret.extend(
853-
unit.pkg
854-
.targets()
855-
.iter()
856-
.filter(|t| {
857-
let no_required_features = Vec::new();
858-
859-
t.is_bin() &&
860-
// Skip binaries with required features that have not been selected.
861-
t.required_features().unwrap_or(&no_required_features).iter().all(|f| {
862-
self.resolve.features(id).contains(f)
863-
})
864-
})
865-
.map(|t| Unit {
866-
pkg: unit.pkg,
867-
target: t,
868-
profile: self.lib_or_check_profile(unit, t),
869-
kind: unit.kind.for_target(t),
870-
}),
871-
);
872-
}
873-
Ok(ret)
874-
}
875-
876-
/// Returns the dependencies needed to run a build script.
877-
///
878-
/// The `unit` provided must represent an execution of a build script, and
879-
/// the returned set of units must all be run before `unit` is run.
880-
pub fn dep_run_custom_build(&self, unit: &Unit<'a>) -> CargoResult<Vec<Unit<'a>>> {
784+
// TODO: this ideally should be `-> &[Unit<'a>]`
785+
pub fn dep_targets(&self, unit: &Unit<'a>) -> Vec<Unit<'a>> {
881786
// If this build script's execution has been overridden then we don't
882787
// actually depend on anything, we've reached the end of the dependency
883788
// chain as we've got all the info we're gonna get.
884-
let key = (unit.pkg.package_id().clone(), unit.kind);
885-
if self.build_script_overridden.contains(&key) {
886-
return Ok(Vec::new());
887-
}
888-
889-
// When not overridden, then the dependencies to run a build script are:
890789
//
891-
// 1. Compiling the build script itself
892-
// 2. For each immediate dependency of our package which has a `links`
893-
// key, the execution of that build script.
894-
let not_custom_build = unit.pkg
895-
.targets()
896-
.iter()
897-
.find(|t| !t.is_custom_build())
898-
.unwrap();
899-
let tmp = Unit {
900-
target: not_custom_build,
901-
profile: &self.profiles.dev,
902-
..*unit
903-
};
904-
let deps = self.dep_targets(&tmp)?;
905-
Ok(deps.iter()
906-
.filter_map(|unit| {
907-
if !unit.target.linkable() || unit.pkg.manifest().links().is_none() {
908-
return None;
909-
}
910-
self.dep_build_script(unit)
911-
})
912-
.chain(Some(Unit {
913-
profile: self.build_script_profile(unit.pkg.package_id()),
914-
kind: Kind::Host, // build scripts always compiled for the host
915-
..*unit
916-
}))
917-
.collect())
918-
}
919-
920-
/// Returns the dependencies necessary to document a package
921-
fn doc_deps(&self, unit: &Unit<'a>) -> CargoResult<Vec<Unit<'a>>> {
922-
let deps = self.resolve
923-
.deps(unit.pkg.package_id())
924-
.filter(|dep| {
925-
unit.pkg
926-
.dependencies()
927-
.iter()
928-
.filter(|d| d.name() == dep.name())
929-
.any(|dep| match dep.kind() {
930-
DepKind::Normal => self.dep_platform_activated(dep, unit.kind),
931-
_ => false,
932-
})
933-
})
934-
.map(|dep| self.get_package(dep));
935-
936-
// To document a library, we depend on dependencies actually being
937-
// built. If we're documenting *all* libraries, then we also depend on
938-
// the documentation of the library being built.
939-
let mut ret = Vec::new();
940-
for dep in deps {
941-
let dep = dep?;
942-
let lib = match dep.targets().iter().find(|t| t.is_lib()) {
943-
Some(lib) => lib,
944-
None => continue,
945-
};
946-
ret.push(Unit {
947-
pkg: dep,
948-
target: lib,
949-
profile: self.lib_or_check_profile(unit, lib),
950-
kind: unit.kind.for_target(lib),
951-
});
952-
if self.build_config.doc_all {
953-
ret.push(Unit {
954-
pkg: dep,
955-
target: lib,
956-
profile: &self.profiles.doc,
957-
kind: unit.kind.for_target(lib),
958-
});
790+
// Note there's a subtlety about this piece of code! The
791+
// `build_script_overridden` map here is populated in
792+
// `custom_build::build_map` which you need to call before inspecting
793+
// dependencies. However, that code itself calls this method and
794+
// gets a full pre-filtered set of dependencies. This is not super
795+
// obvious, and clear, but it does work at the moment.
796+
if unit.profile.run_custom_build {
797+
let key = (unit.pkg.package_id().clone(), unit.kind);
798+
if self.build_script_overridden.contains(&key) {
799+
return Vec::new();
959800
}
960801
}
961-
962-
// Be sure to build/run the build script for documented libraries as
963-
ret.extend(self.dep_build_script(unit));
964-
965-
// If we document a binary, we need the library available
966-
if unit.target.is_bin() {
967-
ret.extend(self.maybe_lib(unit));
968-
}
969-
Ok(ret)
970-
}
971-
972-
/// If a build script is scheduled to be run for the package specified by
973-
/// `unit`, this function will return the unit to run that build script.
974-
///
975-
/// Overriding a build script simply means that the running of the build
976-
/// script itself doesn't have any dependencies, so even in that case a unit
977-
/// of work is still returned. `None` is only returned if the package has no
978-
/// build script.
979-
fn dep_build_script(&self, unit: &Unit<'a>) -> Option<Unit<'a>> {
980-
unit.pkg
981-
.targets()
982-
.iter()
983-
.find(|t| t.is_custom_build())
984-
.map(|t| Unit {
985-
pkg: unit.pkg,
986-
target: t,
987-
profile: &self.profiles.custom_build,
988-
kind: unit.kind,
989-
})
990-
}
991-
992-
fn maybe_lib(&self, unit: &Unit<'a>) -> Option<Unit<'a>> {
993-
unit.pkg
994-
.targets()
995-
.iter()
996-
.find(|t| t.linkable())
997-
.map(|t| Unit {
998-
pkg: unit.pkg,
999-
target: t,
1000-
profile: self.lib_or_check_profile(unit, t),
1001-
kind: unit.kind.for_target(t),
1002-
})
802+
self.unit_dependencies.as_ref().unwrap()[unit].clone()
1003803
}
1004804

1005805
fn dep_platform_activated(&self, dep: &Dependency, kind: Kind) -> bool {
@@ -1066,15 +866,6 @@ impl<'a, 'cfg> Context<'a, 'cfg> {
1066866
}
1067867
}
1068868

1069-
pub fn lib_or_check_profile(&self, unit: &Unit, target: &Target) -> &'a Profile {
1070-
if !target.is_custom_build() && !target.for_host()
1071-
&& (unit.profile.check || (unit.profile.doc && !unit.profile.test))
1072-
{
1073-
return &self.profiles.check;
1074-
}
1075-
self.lib_profile()
1076-
}
1077-
1078869
pub fn build_script_profile(&self, _pkg: &PackageId) -> &'a Profile {
1079870
// TODO: should build scripts always be built with the same library
1080871
// profile? How is this controlled at the CLI layer?

0 commit comments

Comments
 (0)