From 870f6d31d708b3315a0b1ad0ca8f3ecc17a3b617 Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Sun, 29 Sep 2024 13:49:27 +0200 Subject: [PATCH] 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. --- Cargo.lock | 1 + crates/resolver-tests/Cargo.toml | 1 + crates/resolver-tests/src/sat.rs | 432 ++++++++++++++++------- crates/resolver-tests/tests/validated.rs | 325 ++++++++++++++++- 4 files changed, 620 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f83785bb..2b1f206ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2936,6 +2936,7 @@ name = "resolver-tests" version = "0.0.0" dependencies = [ "cargo", + "cargo-platform 0.1.8", "cargo-util", "cargo-util-schemas", "proptest", diff --git a/crates/resolver-tests/Cargo.toml b/crates/resolver-tests/Cargo.toml index 44f906900..b63e3ffce 100644 --- a/crates/resolver-tests/Cargo.toml +++ b/crates/resolver-tests/Cargo.toml @@ -6,6 +6,7 @@ publish = false [dependencies] cargo.workspace = true +cargo-platform.workspace = true cargo-util-schemas.workspace = true cargo-util.workspace = true proptest.workspace = true diff --git a/crates/resolver-tests/src/sat.rs b/crates/resolver-tests/src/sat.rs index c7f98bd91..60546e85d 100644 --- a/crates/resolver-tests/src/sat.rs +++ b/crates/resolver-tests/src/sat.rs @@ -1,8 +1,10 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::fmt::Write; +use cargo::core::dependency::DepKind; use cargo::core::{Dependency, FeatureMap, FeatureValue, PackageId, Summary}; use cargo::util::interning::{InternedString, INTERNED_DEFAULT}; +use cargo_platform::Platform; use varisat::ExtendFormula; const fn num_bits() -> 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. // - // 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 let bits: Vec = solver.new_var_iter(log_bits(vars.len())).collect(); for (i, p) in vars.iter().enumerate() { @@ -48,7 +50,7 @@ fn sat_at_most_one_by_key( solver: &mut varisat::Solver<'_>, data: impl Iterator, ) -> HashMap> { - // no two packages with the same keys set + // No two packages with the same keys set let mut by_keys: HashMap> = HashMap::new(); for (p, v) in data { by_keys.entry(p).or_default().push(v) @@ -59,40 +61,130 @@ fn sat_at_most_one_by_key( by_keys } -fn find_compatible_dep_summaries_by_name_in_toml( +type DependencyVarMap<'a> = + HashMap), varisat::Var>>; + +type DependencyFeatureVarMap<'a> = HashMap< + InternedString, + HashMap<(DepKind, Option<&'a Platform>), HashMap>, +>; + +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], - by_name: &HashMap>, -) -> HashMap> { - let empty_vec = vec![]; +) { + // Add clauses for package dependencies + 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 - .iter() - .map(|dep| { - let name_in_toml = dep.name_in_toml(); + if !dep.is_optional() { + solver.add_clause(&[pkg_var.negative(), dep_var.positive()]); + } - let compatible_summaries = by_name - .get(&dep.package_name()) - .unwrap_or(&empty_vec) - .iter() - .filter(|s| dep.matches_id(s.package_id())) - .filter(|s| dep.features().iter().all(|f| s.features().contains_key(f))) - .cloned() - .collect::>(); + for &feature_name in dep.features() { + let dep_feature_var = + &var_for_is_dependencies_features_used[&name][&(kind, platform)][&feature_name]; - (name_in_toml, compatible_summaries) - }) - .collect() + solver.add_clause(&[dep_var.negative(), dep_feature_var.positive()]); + } + } } fn process_pkg_features( solver: &mut varisat::Solver<'_>, - var_for_is_packages_used: &HashMap, - var_for_is_packages_features_used: &HashMap>, + var_for_is_dependencies_used: &DependencyVarMap<'_>, + var_for_is_dependencies_features_used: &DependencyFeatureVarMap<'_>, pkg_feature_var_map: &HashMap, + pkg_dependencies: &[Dependency], pkg_features: &FeatureMap, - compatible_dep_summaries_by_name_in_toml: &HashMap>, + 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::>(); + + // Add clauses for package features for (&feature_name, feature_values) in pkg_features { for feature_value in feature_values { let pkg_feature_var = pkg_feature_var_map[&feature_name]; @@ -105,39 +197,56 @@ fn process_pkg_features( ]); } FeatureValue::Dep { dep_name } => { - let dep_clause = compatible_dep_summaries_by_name_in_toml[&dep_name] - .iter() - .map(|dep| var_for_is_packages_used[&dep.package_id()].positive()) - .chain([pkg_feature_var.negative()]) - .collect::>(); - - solver.add_clause(&dep_clause); + // Add a clause for each dependency with the provided name (normal/build/dev with target) + for (&(dep_kind, _), &dep_var) in &var_for_is_dependencies_used[&dep_name] { + if dep_kind == DepKind::Development && !check_dev_dependencies { + continue; + } + solver.add_clause(&[pkg_feature_var.negative(), dep_var.positive()]); + } } FeatureValue::DepFeature { dep_name, dep_feature: dep_feature_name, weak, } => { - for dep in &compatible_dep_summaries_by_name_in_toml[&dep_name] { - let dep_var = var_for_is_packages_used[&dep.package_id()]; - let dep_feature_var = - var_for_is_packages_features_used[&dep.package_id()][&dep_feature_name]; + // Behavior of the feature: + // * if dependency `dep_name` is not optional, its feature `"dep_feature_name"` is activated. + // * if dependency `dep_name` is optional: + // - 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(&[ pkg_feature_var.negative(), dep_var.negative(), dep_feature_var.positive(), ]); - } - if !weak { - let dep_clause = compatible_dep_summaries_by_name_in_toml[&dep_name] - .iter() - .map(|dep| var_for_is_packages_used[&dep.package_id()].positive()) - .chain([pkg_feature_var.negative()]) - .collect::>(); + let key = (dep_kind, dep_platform, dep_name); + if !weak && optional_dependencies.contains(&key) { + solver.add_clause(&[pkg_feature_var.negative(), dep_var.positive()]); - 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<'_>, + var_for_is_dependencies_used: &DependencyVarMap<'_>, + var_for_is_dependencies_features_used: &DependencyFeatureVarMap<'_>, var_for_is_packages_used: &HashMap, var_for_is_packages_features_used: &HashMap>, - pkg_var: varisat::Var, + by_name: &HashMap>, pkg_dependencies: &[Dependency], - compatible_dep_summaries_by_name_in_toml: &HashMap>, + check_dev_dependencies: bool, ) { for dep in pkg_dependencies { - let compatible_dep_summaries = - &compatible_dep_summaries_by_name_in_toml[&dep.name_in_toml()]; - - // 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(), - ]); - } + if dep.kind() == DepKind::Development && !check_dev_dependencies { + continue; } - // active packages need to activate each of their non-optional dependencies - if !dep.is_optional() { - let dep_clause = compatible_dep_summaries + 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)]; + + 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::>(); + + // 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::>(); + + 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() - .map(|d| var_for_is_packages_used[&d.package_id()].positive()) - .chain([pkg_var.negative()]) + .filter_map(|s| { + var_for_is_packages_features_used[&s.package_id()].get(&feature_name) + }) + .map(|var| var.positive()) + .chain([dep_feature_var.negative()]) .collect::>(); - 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 { - pub fn new(registry: &[Summary]) -> Self { + pub fn new<'a>(registry: impl IntoIterator) -> Self { + let check_dev_dependencies = false; + + let mut by_name: HashMap> = HashMap::new(); + for pkg in registry { + by_name.entry(pkg.name()).or_default().push(pkg.clone()) + } + 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. - let var_for_is_packages_used = registry - .iter() - .map(|s| (s.package_id(), solver.new_var())) - .collect::>(); + // Create boolean variables for packages and packages features + let mut var_for_is_packages_used = HashMap::new(); + let mut var_for_is_packages_features_used = HashMap::<_, HashMap<_, _>>::new(); - // That represents each feature of each package version, which is set to "true" if the package feature is used. - let var_for_is_packages_features_used = registry - .iter() - .map(|s| { - ( - s.package_id(), - (s.features().keys().map(|&f| (f, solver.new_var()))).collect(), - ) - }) - .collect::>>(); + for pkg in by_name.values().flatten() { + let pkg_id = pkg.package_id(); - // if a package feature is used, then the package is used - for (package, pkg_feature_var_map) in &var_for_is_packages_features_used { - for (_, package_feature_var) in pkg_feature_var_map { - let package_var = var_for_is_packages_used[package]; - solver.add_clause(&[package_feature_var.negative(), package_var.positive()]); + var_for_is_packages_used.insert(pkg_id, solver.new_var()); + + var_for_is_packages_features_used.insert( + pkg_id, + (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( &mut solver, - registry - .iter() + by_name + .values() + .flatten() .map(|s| (s.links(), var_for_is_packages_used[&s.package_id()])) .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( &mut solver, var_for_is_packages_used @@ -254,36 +398,43 @@ impl SatResolver { .map(|(p, &v)| (p.as_activations_key(), v)), ); - let mut by_name: HashMap> = HashMap::new(); - - for p in registry { - by_name.entry(p.name()).or_default().push(p.clone()) - } - - for pkg in registry { + for pkg in by_name.values().flatten() { let pkg_id = pkg.package_id(); let pkg_dependencies = pkg.dependencies(); let pkg_features = pkg.features(); + let pkg_var = var_for_is_packages_used[&pkg_id]; - let compatible_dep_summaries_by_name_in_toml = - find_compatible_dep_summaries_by_name_in_toml(pkg_dependencies, &by_name); - - 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, - ); + // Create boolean variables for dependencies and dependencies features + 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_dependencies( &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_features_used, - var_for_is_packages_used[&pkg_id], + &by_name, pkg_dependencies, - &compatible_dep_summaries_by_name_in_toml, + check_dev_dependencies, ); } @@ -313,8 +464,31 @@ impl SatResolver { let root_var = solver.new_var(); - // root package is always used - // root vars from previous runs are deactivated + // Create boolean variables for dependencies and dependencies features + 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 .iter() .map(|v| v.negative()) @@ -323,18 +497,6 @@ impl SatResolver { 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 @@ -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())) .chain( self.var_for_is_packages_used diff --git a/crates/resolver-tests/tests/validated.rs b/crates/resolver-tests/tests/validated.rs index 68fc0bcd8..5eaccf292 100644 --- a/crates/resolver-tests/tests/validated.rs +++ b/crates/resolver-tests/tests/validated.rs @@ -1,7 +1,10 @@ -use cargo::core::Dependency; +use cargo::core::{dependency::DepKind, Dependency}; 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, sat::SatResolver, }; @@ -165,6 +168,59 @@ fn missing_feature() { assert!(resolve_and_validated(vec!["a".with(&["f"])], ®, &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(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); + + let deps = vec![dep("dep2").with(&["f"])]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); + + let deps = vec![dep("dep2").with(&["a", "f"])]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &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(), ®).is_err()); + assert!(SatResolver::new(®).sat_resolve(&deps)); + + let deps = vec![dep("dep3").with(&["a", "f"])]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &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(), ®).is_err()); + assert!(SatResolver::new(®).sat_resolve(&deps)); + + let deps = vec![dep("dep4").with(&["x", "f"])]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); +} + #[test] fn conflict_feature_and_sys() { let reg = registry(vec![ @@ -204,8 +260,269 @@ fn conflict_weak_features() { let deps = vec![dep("dep").with(&["a1", "a2"])]; - // The following asserts should be updated when support for weak dependencies - // is added to the dependency resolver. + // Weak dependencies are not supported yet in the dependency resolver assert!(resolve(deps.clone(), ®).is_err()); assert!(SatResolver::new(®).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(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); + + let deps = vec![dep("dep2")]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); + + let deps = vec!["dep2".with(&["default"])]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &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(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_err()); + + let deps = vec!["dep2".with(&["a"])]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &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(), ®).is_err()); + assert!(SatResolver::new(®).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(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); + + let deps = vec![dep("dep2")]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); + + let deps = vec![dep("dep3")]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); + + let deps = vec![dep("dep4")]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); + + let deps = vec![dep("dep5")]; + let mut sat_resolver = SatResolver::new(®); + assert!(resolve_and_validated(deps, ®, &mut sat_resolver).is_ok()); +}