Skip to content

Commit d2d7b30

Browse files
committed
Auto merge of #4683 - djc:requirements, r=alexcrichton
Introduce Requirements struct to clarify code `cargo::core::resolver::build_requirements()` previously, in this order: * Defined local variables to track state * Called a function to mutate the local variables * Defined the aforementioned function * Returned two out of three local variables as a tuple This PR changes the code so that this is a recast as a struct (appropriately called `Requirements`), which is mutated in a more fine-grained way by its methods and acts as the return type for `build_requirements()`. To me, this seems a lot easier to understand. This work was done in the context of #1286, but I figured it was easier to start landing some of the refactoring to avoid bitrot and get early (well, earlier) feedback.
2 parents 7c9c153 + abf4122 commit d2d7b30

File tree

1 file changed

+102
-79
lines changed

1 file changed

+102
-79
lines changed

src/cargo/core/resolver/mod.rs

Lines changed: 102 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -859,55 +859,68 @@ fn compatible(a: &semver::Version, b: &semver::Version) -> bool {
859859
a.patch == b.patch
860860
}
861861

862-
// Returns a pair of (feature dependencies, all used features)
863-
//
864-
// The feature dependencies map is a mapping of package name to list of features
865-
// enabled. Each package should be enabled, and each package should have the
866-
// specified set of features enabled. The boolean indicates whether this
867-
// package was specifically requested (rather than just requesting features
868-
// *within* this package).
869-
//
870-
// The all used features set is the set of features which this local package had
871-
// enabled, which is later used when compiling to instruct the code what
872-
// features were enabled.
873-
fn build_features<'a>(s: &'a Summary, method: &'a Method)
874-
-> CargoResult<(HashMap<&'a str, (bool, Vec<String>)>, HashSet<&'a str>)> {
875-
let mut deps = HashMap::new();
876-
let mut used = HashSet::new();
877-
let mut visited = HashSet::new();
878-
match *method {
879-
Method::Everything => {
880-
for key in s.features().keys() {
881-
add_feature(s, key, &mut deps, &mut used, &mut visited)?;
882-
}
883-
for dep in s.dependencies().iter().filter(|d| d.is_optional()) {
884-
add_feature(s, dep.name(), &mut deps, &mut used,
885-
&mut visited)?;
886-
}
862+
struct Requirements<'a> {
863+
summary: &'a Summary,
864+
// The deps map is a mapping of package name to list of features enabled.
865+
// Each package should be enabled, and each package should have the
866+
// specified set of features enabled. The boolean indicates whether this
867+
// package was specifically requested (rather than just requesting features
868+
// *within* this package).
869+
deps: HashMap<&'a str, (bool, Vec<String>)>,
870+
// The used features set is the set of features which this local package had
871+
// enabled, which is later used when compiling to instruct the code what
872+
// features were enabled.
873+
used: HashSet<&'a str>,
874+
visited: HashSet<&'a str>,
875+
}
876+
877+
impl<'r> Requirements<'r> {
878+
fn new<'a>(summary: &'a Summary) -> Requirements<'a> {
879+
Requirements {
880+
summary,
881+
deps: HashMap::new(),
882+
used: HashSet::new(),
883+
visited: HashSet::new(),
887884
}
888-
Method::Required { features: requested_features, .. } => {
889-
for feat in requested_features.iter() {
890-
add_feature(s, feat, &mut deps, &mut used, &mut visited)?;
891-
}
885+
}
886+
887+
fn require_crate_feature(&mut self, package: &'r str, feat: &'r str) {
888+
self.used.insert(package);
889+
self.deps.entry(package)
890+
.or_insert((false, Vec::new()))
891+
.1.push(feat.to_string());
892+
}
893+
894+
fn seen(&mut self, feat: &'r str) -> bool {
895+
if self.visited.insert(feat) {
896+
self.used.insert(feat);
897+
false
898+
} else {
899+
true
892900
}
893901
}
894-
match *method {
895-
Method::Everything |
896-
Method::Required { uses_default_features: true, .. } => {
897-
if s.features().get("default").is_some() {
898-
add_feature(s, "default", &mut deps, &mut used,
899-
&mut visited)?;
902+
903+
fn require_dependency(&mut self, pkg: &'r str) {
904+
if self.seen(pkg) {
905+
return;
906+
}
907+
self.deps.entry(pkg).or_insert((false, Vec::new())).0 = true;
908+
}
909+
910+
fn require_feature(&mut self, feat: &'r str) -> CargoResult<()> {
911+
if self.seen(feat) {
912+
return Ok(());
913+
}
914+
for f in self.summary.features().get(feat).expect("must be a valid feature") {
915+
if f == &feat {
916+
bail!("Cyclic feature dependency: feature `{}` depends on itself", feat);
900917
}
918+
self.add_feature(f)?;
901919
}
902-
Method::Required { uses_default_features: false, .. } => {}
920+
Ok(())
903921
}
904-
return Ok((deps, used));
905922

906-
fn add_feature<'a>(s: &'a Summary,
907-
feat: &'a str,
908-
deps: &mut HashMap<&'a str, (bool, Vec<String>)>,
909-
used: &mut HashSet<&'a str>,
910-
visited: &mut HashSet<&'a str>) -> CargoResult<()> {
923+
fn add_feature(&mut self, feat: &'r str) -> CargoResult<()> {
911924
if feat.is_empty() { return Ok(()) }
912925

913926
// If this feature is of the form `foo/bar`, then we just lookup package
@@ -919,44 +932,53 @@ fn build_features<'a>(s: &'a Summary, method: &'a Method)
919932
let feat_or_package = parts.next().unwrap();
920933
match parts.next() {
921934
Some(feat) => {
922-
let package = feat_or_package;
923-
used.insert(package);
924-
deps.entry(package)
925-
.or_insert((false, Vec::new()))
926-
.1.push(feat.to_string());
935+
self.require_crate_feature(feat_or_package, feat);
927936
}
928937
None => {
929-
let feat = feat_or_package;
930-
931-
//if this feature has already been added, then just return Ok
932-
if !visited.insert(feat) {
933-
return Ok(());
934-
}
935-
936-
used.insert(feat);
937-
match s.features().get(feat) {
938-
Some(recursive) => {
939-
// This is a feature, add it recursively.
940-
for f in recursive {
941-
if f == feat {
942-
bail!("Cyclic feature dependency: feature `{}` depends \
943-
on itself", feat);
944-
}
945-
946-
add_feature(s, f, deps, used, visited)?;
947-
}
948-
}
949-
None => {
950-
// This is a dependency, mark it as explicitly requested.
951-
deps.entry(feat).or_insert((false, Vec::new())).0 = true;
952-
}
938+
if self.summary.features().contains_key(feat_or_package) {
939+
self.require_feature(feat_or_package)?;
940+
} else {
941+
self.require_dependency(feat_or_package);
953942
}
954943
}
955944
}
956945
Ok(())
957946
}
958947
}
959948

949+
// Takes requested features for a single package from the input Method and
950+
// recurses to find all requested features, dependencies and requested
951+
// dependency features in a Requirements object, returning it to the resolver.
952+
fn build_requirements<'a, 'b: 'a>(s: &'a Summary, method: &'b Method)
953+
-> CargoResult<Requirements<'a>> {
954+
let mut reqs = Requirements::new(s);
955+
match *method {
956+
Method::Everything => {
957+
for key in s.features().keys() {
958+
reqs.require_feature(key)?;
959+
}
960+
for dep in s.dependencies().iter().filter(|d| d.is_optional()) {
961+
reqs.require_dependency(dep.name());
962+
}
963+
}
964+
Method::Required { features: requested_features, .. } => {
965+
for feat in requested_features.iter() {
966+
reqs.add_feature(feat)?;
967+
}
968+
}
969+
}
970+
match *method {
971+
Method::Everything |
972+
Method::Required { uses_default_features: true, .. } => {
973+
if s.features().get("default").is_some() {
974+
reqs.require_feature("default")?;
975+
}
976+
}
977+
Method::Required { uses_default_features: false, .. } => {}
978+
}
979+
return Ok(reqs);
980+
}
981+
960982
impl<'a> Context<'a> {
961983
// Activate this summary by inserting it into our list of known activations.
962984
//
@@ -1117,18 +1139,18 @@ impl<'a> Context<'a> {
11171139
let deps = s.dependencies();
11181140
let deps = deps.iter().filter(|d| d.is_transitive() || dev_deps);
11191141

1120-
let (mut feature_deps, used_features) = build_features(s, method)?;
1142+
let mut reqs = build_requirements(s, method)?;
11211143
let mut ret = Vec::new();
11221144

11231145
// Next, collect all actually enabled dependencies and their features.
11241146
for dep in deps {
11251147
// Skip optional dependencies, but not those enabled through a feature
1126-
if dep.is_optional() && !feature_deps.contains_key(dep.name()) {
1148+
if dep.is_optional() && !reqs.deps.contains_key(dep.name()) {
11271149
continue
11281150
}
11291151
// So we want this dependency. Move the features we want from `feature_deps`
11301152
// to `ret`.
1131-
let base = feature_deps.remove(dep.name()).unwrap_or((false, vec![]));
1153+
let base = reqs.deps.remove(dep.name()).unwrap_or((false, vec![]));
11321154
if !dep.is_optional() && base.0 {
11331155
self.warnings.push(
11341156
format!("Package `{}` does not have feature `{}`. It has a required dependency \
@@ -1151,21 +1173,22 @@ impl<'a> Context<'a> {
11511173
// Any remaining entries in feature_deps are bugs in that the package does not actually
11521174
// have those dependencies. We classified them as dependencies in the first place
11531175
// because there is no such feature, either.
1154-
if !feature_deps.is_empty() {
1155-
let unknown = feature_deps.keys().map(|s| &s[..])
1156-
.collect::<Vec<&str>>();
1176+
if !reqs.deps.is_empty() {
1177+
let unknown = reqs.deps.keys()
1178+
.map(|s| &s[..])
1179+
.collect::<Vec<&str>>();
11571180
let features = unknown.join(", ");
11581181
bail!("Package `{}` does not have these features: `{}`",
11591182
s.package_id(), features)
11601183
}
11611184

11621185
// Record what list of features is active for this package.
1163-
if !used_features.is_empty() {
1186+
if !reqs.used.is_empty() {
11641187
let pkgid = s.package_id();
11651188

11661189
let set = self.resolve_features.entry(pkgid.clone())
11671190
.or_insert_with(HashSet::new);
1168-
for feature in used_features {
1191+
for feature in reqs.used {
11691192
if !set.contains(feature) {
11701193
set.insert(feature.to_string());
11711194
}

0 commit comments

Comments
 (0)