diff options
Diffstat (limited to 'crates/mozart-sat-resolver/src/rule_set_generator.rs')
| -rw-r--r-- | crates/mozart-sat-resolver/src/rule_set_generator.rs | 59 |
1 files changed, 56 insertions, 3 deletions
diff --git a/crates/mozart-sat-resolver/src/rule_set_generator.rs b/crates/mozart-sat-resolver/src/rule_set_generator.rs index 92b9f77..8f754e5 100644 --- a/crates/mozart-sat-resolver/src/rule_set_generator.rs +++ b/crates/mozart-sat-resolver/src/rule_set_generator.rs @@ -1,6 +1,7 @@ use crate::pool::{Literal, PackageId, Pool, PoolLink}; use crate::rule::{ReasonData, Rule, RuleReason, RuleType}; use crate::rule_set::RuleSet; +use mozart_semver::VersionConstraint; use std::collections::{HashMap, HashSet, VecDeque}; /// Generates SAT rules from the pool and request. @@ -56,10 +57,22 @@ impl<'a> RuleSetGenerator<'a> { /// Generate rules for a set of requirements and fixed packages. /// /// Port of Composer's RuleSetGenerator::getRulesFor. + /// + /// `root_provides` / `root_replaces` map a target package name to the + /// constraint declared in the root composer.json's `provide` / `replace` + /// section. They mirror the "self-fulfilling rule" check in Composer's + /// `RuleSetGenerator::createRequireRule`: when the root package itself + /// provides or replaces a name it requires, no install-one-of rule is + /// emitted for that root require — root is implicitly already installed, + /// so the requirement is trivially satisfied without forcing a real + /// provider. Without this, Mozart picks up an inline `provided/pkg` from + /// the repository even though the root claims to fulfill it itself. pub fn generate( mut self, requires: &HashMap<String, Option<String>>, fixed_packages: &[PackageId], + root_provides: &HashMap<String, String>, + root_replaces: &HashMap<String, String>, ) -> RuleSet { // Process fixed packages for &pkg_id in fixed_packages { @@ -84,6 +97,21 @@ impl<'a> RuleSetGenerator<'a> { continue; } + // Self-fulfilling root require: if the root composer.json declares + // `provide` / `replace` for this name and the link constraint + // intersects the require constraint, drop the install-one-of rule + // entirely. Mirrors Composer's `createRequireRule` returning null + // when a provider IS the package itself: there, the root is in the + // pool as a fixed package and `whatProvides` includes it, so the + // resulting rule is trivially satisfied. Mozart does not yet add + // the root to the pool, so we make the same decision here based + // on the explicit root provide/replace tables. + if root_self_fulfills(name, constraint.as_deref(), root_provides) + || root_self_fulfills(name, constraint.as_deref(), root_replaces) + { + continue; + } + let providers = self.pool.what_provides(name, constraint.as_deref()); if !providers.is_empty() { @@ -305,6 +333,31 @@ impl<'a> RuleSetGenerator<'a> { } } +/// True when the root composer.json's `provide` / `replace` map declares +/// `target` with a constraint that intersects the require's constraint. A +/// missing require constraint is treated as `*` (matches anything), and a +/// missing/unparsable link constraint conservatively does NOT match — the +/// fixture fails closed back to the regular install-one-of path. +fn root_self_fulfills( + target: &str, + require_constraint: Option<&str>, + root_links: &HashMap<String, String>, +) -> bool { + let Some(link_constraint_str) = root_links.get(target) else { + return false; + }; + let Ok(link_vc) = VersionConstraint::parse(link_constraint_str) else { + return false; + }; + match require_constraint { + None => true, + Some(req) => match VersionConstraint::parse(req) { + Ok(req_vc) => req_vc.intersects(&link_vc), + Err(_) => false, + }, + } +} + #[cfg(test)] mod tests { use super::*; @@ -335,7 +388,7 @@ mod tests { requires.insert("a/a".to_string(), None); let generator = RuleSetGenerator::new(&mut pool); - let rules = generator.generate(&requires, &[]); + let rules = generator.generate(&requires, &[], &HashMap::new(), &HashMap::new()); // Should have a request rule: (1 | 2) let request_count = rules.iter_type(RuleType::Request).count(); @@ -375,7 +428,7 @@ mod tests { requires.insert("a/a".to_string(), None); let generator = RuleSetGenerator::new(&mut pool); - let rules = generator.generate(&requires, &[]); + let rules = generator.generate(&requires, &[], &HashMap::new(), &HashMap::new()); // Should have: // 1. Request rule: (1) — root requires a/a @@ -388,7 +441,7 @@ mod tests { let mut pool = Pool::new(vec![make_input("php", "8.2.0.0")], vec![]); let generator = RuleSetGenerator::new(&mut pool); - let rules = generator.generate(&HashMap::new(), &[1]); + let rules = generator.generate(&HashMap::new(), &[1], &HashMap::new(), &HashMap::new()); // Should have an assertion rule: (1) let request_rules: Vec<_> = rules.iter_type(RuleType::Request).collect(); |
