test: refactor sat resolver clauses computation

Add new intermediate boolean variables for each dependency and each dependency feature declared in a package.
This is used to simplify computation of the sat clauses.

Add support for multiple dependencies with the same name, if their kind or target is different.

A non-weak dependency feature for an optional dependency now activates a feature of the same name in the sat resolver.
This commit is contained in:
x-hgg-x 2024-09-29 13:49:27 +02:00
parent 04e4270758
commit 870f6d31d7
4 changed files with 620 additions and 139 deletions

1
Cargo.lock generated
View File

@ -2936,6 +2936,7 @@ name = "resolver-tests"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"cargo", "cargo",
"cargo-platform 0.1.8",
"cargo-util", "cargo-util",
"cargo-util-schemas", "cargo-util-schemas",
"proptest", "proptest",

View File

@ -6,6 +6,7 @@ publish = false
[dependencies] [dependencies]
cargo.workspace = true cargo.workspace = true
cargo-platform.workspace = true
cargo-util-schemas.workspace = true cargo-util-schemas.workspace = true
cargo-util.workspace = true cargo-util.workspace = true
proptest.workspace = true proptest.workspace = true

View File

@ -1,8 +1,10 @@
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt::Write; use std::fmt::Write;
use cargo::core::dependency::DepKind;
use cargo::core::{Dependency, FeatureMap, FeatureValue, PackageId, Summary}; use cargo::core::{Dependency, FeatureMap, FeatureValue, PackageId, Summary};
use cargo::util::interning::{InternedString, INTERNED_DEFAULT}; use cargo::util::interning::{InternedString, INTERNED_DEFAULT};
use cargo_platform::Platform;
use varisat::ExtendFormula; use varisat::ExtendFormula;
const fn num_bits<T>() -> usize { const fn num_bits<T>() -> usize {
@ -34,7 +36,7 @@ fn sat_at_most_one(solver: &mut varisat::Solver<'_>, vars: &[varisat::Var]) {
} }
// There are more efficient ways to do it for large numbers of versions. // There are more efficient ways to do it for large numbers of versions.
// //
// use the "Binary Encoding" from // Use the "Binary Encoding" from
// https://www.it.uu.se/research/group/astra/ModRef10/papers/Alan%20M.%20Frisch%20and%20Paul%20A.%20Giannoros.%20SAT%20Encodings%20of%20the%20At-Most-k%20Constraint%20-%20ModRef%202010.pdf // https://www.it.uu.se/research/group/astra/ModRef10/papers/Alan%20M.%20Frisch%20and%20Paul%20A.%20Giannoros.%20SAT%20Encodings%20of%20the%20At-Most-k%20Constraint%20-%20ModRef%202010.pdf
let bits: Vec<varisat::Var> = solver.new_var_iter(log_bits(vars.len())).collect(); let bits: Vec<varisat::Var> = solver.new_var_iter(log_bits(vars.len())).collect();
for (i, p) in vars.iter().enumerate() { for (i, p) in vars.iter().enumerate() {
@ -48,7 +50,7 @@ fn sat_at_most_one_by_key<K: std::hash::Hash + Eq>(
solver: &mut varisat::Solver<'_>, solver: &mut varisat::Solver<'_>,
data: impl Iterator<Item = (K, varisat::Var)>, data: impl Iterator<Item = (K, varisat::Var)>,
) -> HashMap<K, Vec<varisat::Var>> { ) -> HashMap<K, Vec<varisat::Var>> {
// no two packages with the same keys set // No two packages with the same keys set
let mut by_keys: HashMap<K, Vec<varisat::Var>> = HashMap::new(); let mut by_keys: HashMap<K, Vec<varisat::Var>> = HashMap::new();
for (p, v) in data { for (p, v) in data {
by_keys.entry(p).or_default().push(v) by_keys.entry(p).or_default().push(v)
@ -59,40 +61,130 @@ fn sat_at_most_one_by_key<K: std::hash::Hash + Eq>(
by_keys by_keys
} }
fn find_compatible_dep_summaries_by_name_in_toml( type DependencyVarMap<'a> =
HashMap<InternedString, HashMap<(DepKind, Option<&'a Platform>), varisat::Var>>;
type DependencyFeatureVarMap<'a> = HashMap<
InternedString,
HashMap<(DepKind, Option<&'a Platform>), HashMap<InternedString, varisat::Var>>,
>;
fn create_dependencies_vars<'a>(
solver: &mut varisat::Solver<'_>,
pkg_var: varisat::Var,
pkg_dependencies: &'a [Dependency],
pkg_features: &FeatureMap,
) -> (DependencyVarMap<'a>, DependencyFeatureVarMap<'a>) {
let mut var_for_is_dependencies_used = DependencyVarMap::new();
let mut var_for_is_dependencies_features_used = DependencyFeatureVarMap::new();
for dep in pkg_dependencies {
let (name, kind, platform) = (dep.name_in_toml(), dep.kind(), dep.platform());
var_for_is_dependencies_used
.entry(name)
.or_default()
.insert((kind, platform), solver.new_var());
let dep_feature_var_map = dep
.features()
.iter()
.map(|&f| (f, solver.new_var()))
.collect();
var_for_is_dependencies_features_used
.entry(name)
.or_default()
.insert((kind, platform), dep_feature_var_map);
}
for feature_values in pkg_features.values() {
for feature_value in feature_values {
let FeatureValue::DepFeature {
dep_name,
dep_feature,
weak: _,
} = *feature_value
else {
continue;
};
for dep_features_vars in var_for_is_dependencies_features_used
.get_mut(&dep_name)
.expect("feature dep name exists")
.values_mut()
{
dep_features_vars.insert(dep_feature, solver.new_var());
}
}
}
// If a package dependency is used, then the package is used
for dep_var_map in var_for_is_dependencies_used.values() {
for dep_var in dep_var_map.values() {
solver.add_clause(&[dep_var.negative(), pkg_var.positive()]);
}
}
// If a dependency feature is used, then the dependency is used
for (&dep_name, map) in &mut var_for_is_dependencies_features_used {
for (&(dep_kind, dep_platform), dep_feature_var_map) in map {
for dep_feature_var in dep_feature_var_map.values() {
let dep_var_map = &var_for_is_dependencies_used[&dep_name];
let dep_var = dep_var_map[&(dep_kind, dep_platform)];
solver.add_clause(&[dep_feature_var.negative(), dep_var.positive()]);
}
}
}
(
var_for_is_dependencies_used,
var_for_is_dependencies_features_used,
)
}
fn process_pkg_dependencies(
solver: &mut varisat::Solver<'_>,
var_for_is_dependencies_used: &DependencyVarMap<'_>,
var_for_is_dependencies_features_used: &DependencyFeatureVarMap<'_>,
pkg_var: varisat::Var,
pkg_dependencies: &[Dependency], pkg_dependencies: &[Dependency],
by_name: &HashMap<InternedString, Vec<Summary>>, ) {
) -> HashMap<InternedString, Vec<Summary>> { // Add clauses for package dependencies
let empty_vec = vec![]; for dep in pkg_dependencies {
let (name, kind, platform) = (dep.name_in_toml(), dep.kind(), dep.platform());
let dep_var_map = &var_for_is_dependencies_used[&name];
let dep_var = dep_var_map[&(kind, platform)];
pkg_dependencies if !dep.is_optional() {
.iter() solver.add_clause(&[pkg_var.negative(), dep_var.positive()]);
.map(|dep| { }
let name_in_toml = dep.name_in_toml();
let compatible_summaries = by_name for &feature_name in dep.features() {
.get(&dep.package_name()) let dep_feature_var =
.unwrap_or(&empty_vec) &var_for_is_dependencies_features_used[&name][&(kind, platform)][&feature_name];
.iter()
.filter(|s| dep.matches_id(s.package_id()))
.filter(|s| dep.features().iter().all(|f| s.features().contains_key(f)))
.cloned()
.collect::<Vec<_>>();
(name_in_toml, compatible_summaries) solver.add_clause(&[dep_var.negative(), dep_feature_var.positive()]);
}) }
.collect() }
} }
fn process_pkg_features( fn process_pkg_features(
solver: &mut varisat::Solver<'_>, solver: &mut varisat::Solver<'_>,
var_for_is_packages_used: &HashMap<PackageId, varisat::Var>, var_for_is_dependencies_used: &DependencyVarMap<'_>,
var_for_is_packages_features_used: &HashMap<PackageId, HashMap<InternedString, varisat::Var>>, var_for_is_dependencies_features_used: &DependencyFeatureVarMap<'_>,
pkg_feature_var_map: &HashMap<InternedString, varisat::Var>, pkg_feature_var_map: &HashMap<InternedString, varisat::Var>,
pkg_dependencies: &[Dependency],
pkg_features: &FeatureMap, pkg_features: &FeatureMap,
compatible_dep_summaries_by_name_in_toml: &HashMap<InternedString, Vec<Summary>>, check_dev_dependencies: bool,
) { ) {
// add clauses for package features let optional_dependencies = pkg_dependencies
.iter()
.filter(|dep| dep.is_optional())
.map(|dep| (dep.kind(), dep.platform(), dep.name_in_toml()))
.collect::<HashSet<_>>();
// Add clauses for package features
for (&feature_name, feature_values) in pkg_features { for (&feature_name, feature_values) in pkg_features {
for feature_value in feature_values { for feature_value in feature_values {
let pkg_feature_var = pkg_feature_var_map[&feature_name]; let pkg_feature_var = pkg_feature_var_map[&feature_name];
@ -105,39 +197,56 @@ fn process_pkg_features(
]); ]);
} }
FeatureValue::Dep { dep_name } => { FeatureValue::Dep { dep_name } => {
let dep_clause = compatible_dep_summaries_by_name_in_toml[&dep_name] // Add a clause for each dependency with the provided name (normal/build/dev with target)
.iter() for (&(dep_kind, _), &dep_var) in &var_for_is_dependencies_used[&dep_name] {
.map(|dep| var_for_is_packages_used[&dep.package_id()].positive()) if dep_kind == DepKind::Development && !check_dev_dependencies {
.chain([pkg_feature_var.negative()]) continue;
.collect::<Vec<_>>(); }
solver.add_clause(&[pkg_feature_var.negative(), dep_var.positive()]);
solver.add_clause(&dep_clause); }
} }
FeatureValue::DepFeature { FeatureValue::DepFeature {
dep_name, dep_name,
dep_feature: dep_feature_name, dep_feature: dep_feature_name,
weak, weak,
} => { } => {
for dep in &compatible_dep_summaries_by_name_in_toml[&dep_name] { // Behavior of the feature:
let dep_var = var_for_is_packages_used[&dep.package_id()]; // * if dependency `dep_name` is not optional, its feature `"dep_feature_name"` is activated.
let dep_feature_var = // * if dependency `dep_name` is optional:
var_for_is_packages_features_used[&dep.package_id()][&dep_feature_name]; // - if this is a weak dependency feature:
// - feature `"dep_feature_name"` of dependency `dep_name` is activated if `dep_name` has been activated via another feature.
// - if this is not a weak dependency feature:
// - feature `dep_name` is activated if it exists.
// - dependency `dep_name` is activated.
// - feature `"dep_feature_name"` of dependency `dep_name` is activated.
// Add clauses for each dependency with the provided name (normal/build/dev with target)
let dep_var_map = &var_for_is_dependencies_used[&dep_name];
for (&(dep_kind, dep_platform), &dep_var) in dep_var_map {
if dep_kind == DepKind::Development && !check_dev_dependencies {
continue;
}
let dep_feature_var = &var_for_is_dependencies_features_used[&dep_name]
[&(dep_kind, dep_platform)][&dep_feature_name];
solver.add_clause(&[ solver.add_clause(&[
pkg_feature_var.negative(), pkg_feature_var.negative(),
dep_var.negative(), dep_var.negative(),
dep_feature_var.positive(), dep_feature_var.positive(),
]); ]);
}
if !weak { let key = (dep_kind, dep_platform, dep_name);
let dep_clause = compatible_dep_summaries_by_name_in_toml[&dep_name] if !weak && optional_dependencies.contains(&key) {
.iter() solver.add_clause(&[pkg_feature_var.negative(), dep_var.positive()]);
.map(|dep| var_for_is_packages_used[&dep.package_id()].positive())
.chain([pkg_feature_var.negative()])
.collect::<Vec<_>>();
solver.add_clause(&dep_clause); if let Some(other_feature_var) = pkg_feature_var_map.get(&dep_name) {
solver.add_clause(&[
pkg_feature_var.negative(),
other_feature_var.positive(),
]);
}
}
} }
} }
} }
@ -145,48 +254,77 @@ fn process_pkg_features(
} }
} }
fn process_pkg_dependencies( fn process_compatible_dep_summaries(
solver: &mut varisat::Solver<'_>, solver: &mut varisat::Solver<'_>,
var_for_is_dependencies_used: &DependencyVarMap<'_>,
var_for_is_dependencies_features_used: &DependencyFeatureVarMap<'_>,
var_for_is_packages_used: &HashMap<PackageId, varisat::Var>, var_for_is_packages_used: &HashMap<PackageId, varisat::Var>,
var_for_is_packages_features_used: &HashMap<PackageId, HashMap<InternedString, varisat::Var>>, var_for_is_packages_features_used: &HashMap<PackageId, HashMap<InternedString, varisat::Var>>,
pkg_var: varisat::Var, by_name: &HashMap<InternedString, Vec<Summary>>,
pkg_dependencies: &[Dependency], pkg_dependencies: &[Dependency],
compatible_dep_summaries_by_name_in_toml: &HashMap<InternedString, Vec<Summary>>, check_dev_dependencies: bool,
) { ) {
for dep in pkg_dependencies { for dep in pkg_dependencies {
let compatible_dep_summaries = if dep.kind() == DepKind::Development && !check_dev_dependencies {
&compatible_dep_summaries_by_name_in_toml[&dep.name_in_toml()]; continue;
// add clauses for package dependency features
for dep_summary in compatible_dep_summaries {
let dep_package_id = dep_summary.package_id();
let default_feature = if dep.uses_default_features()
&& dep_summary.features().contains_key(&*INTERNED_DEFAULT)
{
Some(&INTERNED_DEFAULT)
} else {
None
};
for &feature_name in default_feature.into_iter().chain(dep.features()) {
solver.add_clause(&[
pkg_var.negative(),
var_for_is_packages_used[&dep_package_id].negative(),
var_for_is_packages_features_used[&dep_package_id][&feature_name].positive(),
]);
}
} }
// active packages need to activate each of their non-optional dependencies let (name, kind, platform) = (dep.name_in_toml(), dep.kind(), dep.platform());
if !dep.is_optional() { let dep_var_map = &var_for_is_dependencies_used[&name];
let dep_clause = compatible_dep_summaries let dep_var = dep_var_map[&(kind, platform)];
let dep_feature_var_map = &var_for_is_dependencies_features_used[&name][&(kind, platform)];
let compatible_summaries = by_name
.get(&dep.package_name())
.into_iter()
.flatten()
.filter(|s| dep.matches(s))
.filter(|s| dep.features().iter().all(|f| s.features().contains_key(f)))
.cloned()
.collect::<Vec<_>>();
// At least one compatible package should be activated
let dep_clause = compatible_summaries
.iter()
.map(|s| var_for_is_packages_used[&s.package_id()].positive())
.chain([dep_var.negative()])
.collect::<Vec<_>>();
solver.add_clause(&dep_clause);
for (&feature_name, &dep_feature_var) in dep_feature_var_map {
// At least one compatible package with the additional feature should be activated
let dep_feature_clause = compatible_summaries
.iter() .iter()
.map(|d| var_for_is_packages_used[&d.package_id()].positive()) .filter_map(|s| {
.chain([pkg_var.negative()]) var_for_is_packages_features_used[&s.package_id()].get(&feature_name)
})
.map(|var| var.positive())
.chain([dep_feature_var.negative()])
.collect::<Vec<_>>(); .collect::<Vec<_>>();
solver.add_clause(&dep_clause); solver.add_clause(&dep_feature_clause);
}
if dep.uses_default_features() {
// For the selected package for this dependency, the `"default"` feature should be activated if it exists
let mut dep_default_clause = vec![dep_var.negative()];
for s in &compatible_summaries {
let s_pkg_id = s.package_id();
let s_var = var_for_is_packages_used[&s_pkg_id];
let s_feature_var_map = &var_for_is_packages_features_used[&s_pkg_id];
if let Some(s_default_feature_var) = s_feature_var_map.get(&INTERNED_DEFAULT) {
dep_default_clause
.extend_from_slice(&[s_var.negative(), s_default_feature_var.positive()]);
} else {
dep_default_clause.push(s_var.positive());
}
}
solver.add_clause(&dep_default_clause);
} }
} }
} }
@ -209,44 +347,50 @@ pub struct SatResolver {
} }
impl SatResolver { impl SatResolver {
pub fn new(registry: &[Summary]) -> Self { pub fn new<'a>(registry: impl IntoIterator<Item = &'a Summary>) -> Self {
let check_dev_dependencies = false;
let mut by_name: HashMap<InternedString, Vec<Summary>> = HashMap::new();
for pkg in registry {
by_name.entry(pkg.name()).or_default().push(pkg.clone())
}
let mut solver = varisat::Solver::new(); let mut solver = varisat::Solver::new();
// That represents each package version which is set to "true" if the packages in the lock file and "false" if it is unused. // Create boolean variables for packages and packages features
let var_for_is_packages_used = registry let mut var_for_is_packages_used = HashMap::new();
.iter() let mut var_for_is_packages_features_used = HashMap::<_, HashMap<_, _>>::new();
.map(|s| (s.package_id(), solver.new_var()))
.collect::<HashMap<_, _>>();
// That represents each feature of each package version, which is set to "true" if the package feature is used. for pkg in by_name.values().flatten() {
let var_for_is_packages_features_used = registry let pkg_id = pkg.package_id();
.iter()
.map(|s| {
(
s.package_id(),
(s.features().keys().map(|&f| (f, solver.new_var()))).collect(),
)
})
.collect::<HashMap<_, HashMap<_, _>>>();
// if a package feature is used, then the package is used var_for_is_packages_used.insert(pkg_id, solver.new_var());
for (package, pkg_feature_var_map) in &var_for_is_packages_features_used {
for (_, package_feature_var) in pkg_feature_var_map { var_for_is_packages_features_used.insert(
let package_var = var_for_is_packages_used[package]; pkg_id,
solver.add_clause(&[package_feature_var.negative(), package_var.positive()]); (pkg.features().keys().map(|&f| (f, solver.new_var()))).collect(),
);
}
// If a package feature is used, then the package is used
for (&pkg_id, pkg_feature_var_map) in &var_for_is_packages_features_used {
for pkg_feature_var in pkg_feature_var_map.values() {
let pkg_var = var_for_is_packages_used[&pkg_id];
solver.add_clause(&[pkg_feature_var.negative(), pkg_var.positive()]);
} }
} }
// no two packages with the same links set // No two packages with the same links set
sat_at_most_one_by_key( sat_at_most_one_by_key(
&mut solver, &mut solver,
registry by_name
.iter() .values()
.flatten()
.map(|s| (s.links(), var_for_is_packages_used[&s.package_id()])) .map(|s| (s.links(), var_for_is_packages_used[&s.package_id()]))
.filter(|(l, _)| l.is_some()), .filter(|(l, _)| l.is_some()),
); );
// no two semver compatible versions of the same package // No two semver compatible versions of the same package
sat_at_most_one_by_key( sat_at_most_one_by_key(
&mut solver, &mut solver,
var_for_is_packages_used var_for_is_packages_used
@ -254,36 +398,43 @@ impl SatResolver {
.map(|(p, &v)| (p.as_activations_key(), v)), .map(|(p, &v)| (p.as_activations_key(), v)),
); );
let mut by_name: HashMap<InternedString, Vec<Summary>> = HashMap::new(); for pkg in by_name.values().flatten() {
for p in registry {
by_name.entry(p.name()).or_default().push(p.clone())
}
for pkg in registry {
let pkg_id = pkg.package_id(); let pkg_id = pkg.package_id();
let pkg_dependencies = pkg.dependencies(); let pkg_dependencies = pkg.dependencies();
let pkg_features = pkg.features(); let pkg_features = pkg.features();
let pkg_var = var_for_is_packages_used[&pkg_id];
let compatible_dep_summaries_by_name_in_toml = // Create boolean variables for dependencies and dependencies features
find_compatible_dep_summaries_by_name_in_toml(pkg_dependencies, &by_name); let (var_for_is_dependencies_used, var_for_is_dependencies_features_used) =
create_dependencies_vars(&mut solver, pkg_var, pkg_dependencies, pkg_features);
process_pkg_features(
&mut solver,
&var_for_is_packages_used,
&var_for_is_packages_features_used,
&var_for_is_packages_features_used[&pkg_id],
pkg_features,
&compatible_dep_summaries_by_name_in_toml,
);
process_pkg_dependencies( process_pkg_dependencies(
&mut solver, &mut solver,
&var_for_is_dependencies_used,
&var_for_is_dependencies_features_used,
pkg_var,
pkg_dependencies,
);
process_pkg_features(
&mut solver,
&var_for_is_dependencies_used,
&var_for_is_dependencies_features_used,
&var_for_is_packages_features_used[&pkg_id],
pkg_dependencies,
pkg_features,
check_dev_dependencies,
);
process_compatible_dep_summaries(
&mut solver,
&var_for_is_dependencies_used,
&var_for_is_dependencies_features_used,
&var_for_is_packages_used, &var_for_is_packages_used,
&var_for_is_packages_features_used, &var_for_is_packages_features_used,
var_for_is_packages_used[&pkg_id], &by_name,
pkg_dependencies, pkg_dependencies,
&compatible_dep_summaries_by_name_in_toml, check_dev_dependencies,
); );
} }
@ -313,8 +464,31 @@ impl SatResolver {
let root_var = solver.new_var(); let root_var = solver.new_var();
// root package is always used // Create boolean variables for dependencies and dependencies features
// root vars from previous runs are deactivated let (var_for_is_dependencies_used, var_for_is_dependencies_features_used) =
create_dependencies_vars(solver, root_var, root_dependencies, &FeatureMap::new());
process_pkg_dependencies(
solver,
&var_for_is_dependencies_used,
&var_for_is_dependencies_features_used,
root_var,
root_dependencies,
);
process_compatible_dep_summaries(
solver,
&var_for_is_dependencies_used,
&var_for_is_dependencies_features_used,
var_for_is_packages_used,
var_for_is_packages_features_used,
by_name,
root_dependencies,
true,
);
// Root package is always used.
// Root vars from previous runs are deactivated.
let assumption = old_root_vars let assumption = old_root_vars
.iter() .iter()
.map(|v| v.negative()) .map(|v| v.negative())
@ -323,18 +497,6 @@ impl SatResolver {
old_root_vars.push(root_var); old_root_vars.push(root_var);
let compatible_dep_summaries_by_name_in_toml =
find_compatible_dep_summaries_by_name_in_toml(root_dependencies, &by_name);
process_pkg_dependencies(
solver,
var_for_is_packages_used,
var_for_is_packages_features_used,
root_var,
root_dependencies,
&compatible_dep_summaries_by_name_in_toml,
);
solver.assume(&assumption); solver.assume(&assumption);
solver solver
@ -353,7 +515,7 @@ impl SatResolver {
} }
} }
// root vars from previous runs are deactivated // Root vars from previous runs are deactivated
let assumption = (self.old_root_vars.iter().map(|v| v.negative())) let assumption = (self.old_root_vars.iter().map(|v| v.negative()))
.chain( .chain(
self.var_for_is_packages_used self.var_for_is_packages_used

View File

@ -1,7 +1,10 @@
use cargo::core::Dependency; use cargo::core::{dependency::DepKind, Dependency};
use resolver_tests::{ use resolver_tests::{
helpers::{dep, dep_req, pkg, pkg_dep, pkg_dep_with, registry, ToDep}, helpers::{
dep, dep_kind, dep_platform, dep_req, dep_req_kind, dep_req_platform, pkg, pkg_dep,
pkg_dep_with, registry, ToDep,
},
pkg, resolve, resolve_and_validated, pkg, resolve, resolve_and_validated,
sat::SatResolver, sat::SatResolver,
}; };
@ -165,6 +168,59 @@ fn missing_feature() {
assert!(resolve_and_validated(vec!["a".with(&["f"])], &reg, &mut sat_resolver).is_err()); assert!(resolve_and_validated(vec!["a".with(&["f"])], &reg, &mut sat_resolver).is_err());
} }
#[test]
fn missing_dep_feature() {
let reg = registry(vec![
pkg("a"),
pkg_dep_with("dep", vec![dep("a")], &[("f", &["a/a"])]),
]);
let deps = vec![dep("dep").with(&["f"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
}
#[test]
fn missing_weak_dep_feature() {
let reg = registry(vec![
pkg("a"),
pkg_dep_with("dep1", vec![dep("a")], &[("f", &["a/a"])]),
pkg_dep_with("dep2", vec!["a".opt()], &[("f", &["a/a"])]),
pkg_dep_with("dep3", vec!["a".opt()], &[("f", &["a?/a"])]),
pkg_dep_with("dep4", vec!["x".opt()], &[("f", &["x?/a"])]),
]);
let deps = vec![dep("dep1").with(&["f"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
let deps = vec![dep("dep2").with(&["f"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
let deps = vec![dep("dep2").with(&["a", "f"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
// Weak dependencies are not supported yet in the dependency resolver
let deps = vec![dep("dep3").with(&["f"])];
assert!(resolve(deps.clone(), &reg).is_err());
assert!(SatResolver::new(&reg).sat_resolve(&deps));
let deps = vec![dep("dep3").with(&["a", "f"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
// Weak dependencies are not supported yet in the dependency resolver
let deps = vec![dep("dep4").with(&["f"])];
assert!(resolve(deps.clone(), &reg).is_err());
assert!(SatResolver::new(&reg).sat_resolve(&deps));
let deps = vec![dep("dep4").with(&["x", "f"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
}
#[test] #[test]
fn conflict_feature_and_sys() { fn conflict_feature_and_sys() {
let reg = registry(vec![ let reg = registry(vec![
@ -204,8 +260,269 @@ fn conflict_weak_features() {
let deps = vec![dep("dep").with(&["a1", "a2"])]; let deps = vec![dep("dep").with(&["a1", "a2"])];
// The following asserts should be updated when support for weak dependencies // Weak dependencies are not supported yet in the dependency resolver
// is added to the dependency resolver.
assert!(resolve(deps.clone(), &reg).is_err()); assert!(resolve(deps.clone(), &reg).is_err());
assert!(SatResolver::new(&reg).sat_resolve(&deps)); assert!(SatResolver::new(&reg).sat_resolve(&deps));
} }
#[test]
fn multiple_dep_kinds_and_targets() {
let reg = registry(vec![
pkg(("a-sys", "1.0.0")),
pkg(("a-sys", "2.0.0")),
pkg_dep_with(
"dep1",
vec![
dep_req_platform("a-sys", "1.0.0", "cfg(all())").opt(),
dep_req("a-sys", "1.0.0").opt(),
dep_req_kind("a-sys", "2.0.0", DepKind::Build).opt(),
],
&[("default", &["dep:a-sys"])],
),
pkg_dep_with(
"dep2",
vec![
dep_req_platform("a-sys", "1.0.0", "cfg(all())").opt(),
dep_req("a-sys", "1.0.0").opt(),
dep_req_kind("a-sys", "2.0.0", DepKind::Development).rename("a-sys-dev"),
],
&[("default", &["dep:a-sys", "a-sys-dev/bad"])],
),
]);
let deps = vec![dep("dep1")];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
let deps = vec![dep("dep2")];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
let deps = vec![
dep_req("a-sys", "1.0.0"),
dep_req_kind("a-sys", "2.0.0", DepKind::Build).rename("a2"),
];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
let deps = vec![
dep_req("a-sys", "1.0.0"),
dep_req_kind("a-sys", "2.0.0", DepKind::Development).rename("a2"),
];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
}
#[test]
fn multiple_dep_kinds_and_targets_with_different_packages() {
let reg = registry(vec![
pkg_dep_with("a", vec![], &[("f", &[])]),
pkg_dep_with("b", vec![], &[("f", &[])]),
pkg_dep_with("c", vec![], &[("g", &[])]),
pkg_dep_with(
"dep1",
vec![
"a".opt().rename("x").with(&["f"]),
dep_platform("a", "cfg(all())").opt().rename("x"),
dep_kind("b", DepKind::Build).opt().rename("x").with(&["f"]),
],
&[("default", &["x"])],
),
pkg_dep_with(
"dep2",
vec![
"a".opt().rename("x").with(&["f"]),
dep_platform("a", "cfg(all())").opt().rename("x"),
dep_kind("c", DepKind::Build).opt().rename("x").with(&["f"]),
],
&[("default", &["x"])],
),
]);
let deps = vec!["dep1".with(&["default"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
let deps = vec!["dep2".with(&["default"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
}
#[test]
fn dep_feature_with_shadowing_feature() {
let reg = registry(vec![
pkg_dep_with("a", vec![], &[("b", &[])]),
pkg_dep_with(
"dep",
vec!["a".opt().rename("aa"), "c".opt()],
&[("default", &["aa/b"]), ("aa", &["c"])],
),
]);
let deps = vec!["dep".with(&["default"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
}
#[test]
fn dep_feature_not_optional_with_shadowing_feature() {
let reg = registry(vec![
pkg_dep_with("a", vec![], &[("b", &[])]),
pkg_dep_with(
"dep",
vec!["a".rename("aa"), "c".opt()],
&[("default", &["aa/b"]), ("aa", &["c"])],
),
]);
let deps = vec!["dep".with(&["default"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
}
#[test]
fn dep_feature_weak_with_shadowing_feature() {
let reg = registry(vec![
pkg_dep_with("a", vec![], &[("b", &[])]),
pkg_dep_with(
"dep",
vec!["a".opt().rename("aa"), "c".opt()],
&[("default", &["aa?/b"]), ("aa", &["c"])],
),
]);
let deps = vec!["dep".with(&["default"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
}
#[test]
fn dep_feature_duplicate_with_shadowing_feature() {
let reg = registry(vec![
pkg_dep_with("a", vec![], &[("b", &[])]),
pkg_dep_with(
"dep",
vec![
"a".opt().rename("aa"),
dep_kind("a", DepKind::Build).rename("aa"),
"c".opt(),
],
&[("default", &["aa/b"]), ("aa", &["c"])],
),
]);
let deps = vec!["dep".with(&["default"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
}
#[test]
fn optional_dep_features() {
let reg = registry(vec![
pkg_dep("a", vec!["bad".opt()]),
pkg_dep("b", vec!["a".opt().with(&["bad"])]),
pkg_dep("dep", vec![dep("a"), dep("b")]),
]);
let deps = vec![dep("dep")];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
}
#[test]
fn optional_dep_features_with_rename() {
let reg = registry(vec![
pkg("x1"),
pkg_dep("a", vec!["x1".opt(), "x2".opt(), "x3".opt()]),
pkg_dep(
"dep1",
vec![
"a".opt().with(&["x1"]),
dep_kind("a", DepKind::Build).opt().with(&["x2"]),
],
),
pkg_dep(
"dep2",
vec![
"a".opt().with(&["x1"]),
"a".opt().rename("a2").with(&["x3"]),
],
),
]);
let deps = vec!["dep1".with(&["a"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_err());
let deps = vec!["dep2".with(&["a"])];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
}
#[test]
fn optional_weak_dep_features() {
let reg = registry(vec![
pkg_dep("a", vec!["bad".opt()]),
pkg_dep("b", vec![dep("a")]),
pkg_dep_with("dep", vec!["a".opt(), dep("b")], &[("f", &["a?/bad"])]),
]);
let deps = vec!["dep".with(&["f"])];
// Weak dependencies are not supported yet in the dependency resolver
assert!(resolve(deps.clone(), &reg).is_err());
assert!(SatResolver::new(&reg).sat_resolve(&deps));
}
#[test]
fn default_feature_multiple_major_versions() {
let reg = registry(vec![
pkg_dep_with(("a", "0.2.0"), vec![], &[("default", &[])]),
pkg(("a", "0.3.0")),
pkg_dep_with(("a", "0.4.0"), vec![], &[("default", &[])]),
pkg_dep(
"dep1",
vec![
dep_req("a", ">=0.2, <0.4").with_default(),
dep_req("a", "0.2").rename("a2").with(&[]),
],
),
pkg_dep(
"dep2",
vec![
dep_req("a", ">=0.2, <0.4").with_default(),
dep_req("a", "0.3").rename("a2").with(&[]),
],
),
pkg_dep(
"dep3",
vec![
dep_req("a", ">=0.2, <0.4").with_default(),
dep_req("a", "0.2").rename("a1").with(&[]),
dep_req("a", "0.3").rename("a2").with(&[]),
],
),
pkg_dep("dep4", vec![dep_req("a", ">=0.2, <0.4").with_default()]),
pkg_dep("dep5", vec![dep_req("a", ">=0.3, <0.5").with_default()]),
]);
let deps = vec![dep("dep1")];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
let deps = vec![dep("dep2")];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
let deps = vec![dep("dep3")];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
let deps = vec![dep("dep4")];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
let deps = vec![dep("dep5")];
let mut sat_resolver = SatResolver::new(&reg);
assert!(resolve_and_validated(deps, &reg, &mut sat_resolver).is_ok());
}