aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--crates/mozart-registry/src/lockfile.rs2
-rw-r--r--crates/mozart-registry/src/resolver.rs20
-rw-r--r--crates/mozart-sat-resolver/src/rule_set_generator.rs59
-rw-r--r--crates/mozart/src/commands/create_project.rs10
-rw-r--r--crates/mozart/src/commands/remove.rs24
-rw-r--r--crates/mozart/src/commands/require.rs14
-rw-r--r--crates/mozart/src/commands/update.rs12
-rw-r--r--crates/mozart/tests/installer.rs4
8 files changed, 138 insertions, 7 deletions
diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs
index 045b189..99e87c8 100644
--- a/crates/mozart-registry/src/lockfile.rs
+++ b/crates/mozart-registry/src/lockfile.rs
@@ -1400,6 +1400,8 @@ mod tests {
))),
temporary_constraints: HashMap::new(),
raw_repositories: vec![],
+ root_provide: HashMap::new(),
+ root_replace: HashMap::new(),
};
let resolved = resolve(&resolve_request)
diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs
index a83304f..e8076b4 100644
--- a/crates/mozart-registry/src/resolver.rs
+++ b/crates/mozart-registry/src/resolver.rs
@@ -569,6 +569,15 @@ pub struct ResolveRequest {
/// preload that still live in `resolve()` (Step B follow-up will move
/// these through `RepositorySet` too).
pub raw_repositories: Vec<RawRepository>,
+ /// Root composer.json's `provide` map (target → constraint string). Drives
+ /// the self-fulfilling-rule check in the SAT generator: when a root
+ /// `require` names something the root itself `provide`s with a matching
+ /// constraint, no install-one-of rule is emitted, mirroring Composer's
+ /// `RuleSetGenerator::createRequireRule` self-fulfillment branch.
+ pub root_provide: HashMap<String, String>,
+ /// Root composer.json's `replace` map. Same role as `root_provide` for the
+ /// `replace` link: a replaced target counts as fulfilled by the root.
+ pub root_replace: HashMap<String, String>,
}
/// A single package in the resolution output.
@@ -848,7 +857,12 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R
let mut generator = RuleSetGenerator::new(&mut pool);
generator.set_ignore_platform_reqs(ignore_set);
generator.set_ignore_all_platform_reqs(request.ignore_platform_reqs);
- let rules = generator.generate(&root_requires, &fixed_ids);
+ let rules = generator.generate(
+ &root_requires,
+ &fixed_ids,
+ &request.root_provide,
+ &request.root_replace,
+ );
// Create policy and solve
let policy = DefaultPolicy::new(request.prefer_stable, request.prefer_lowest);
@@ -1247,7 +1261,7 @@ mod tests {
requires.insert("foo/foo".to_string(), Some("^1.0".to_string()));
let generator = RuleSetGenerator::new(&mut pool);
- let rules = generator.generate(&requires, &[]);
+ let rules = generator.generate(&requires, &[], &HashMap::new(), &HashMap::new());
let policy = DefaultPolicy::default();
let solver = Solver::new(rules, &pool, policy, HashSet::new());
@@ -1282,6 +1296,8 @@ mod tests {
))),
temporary_constraints: HashMap::new(),
raw_repositories: vec![],
+ root_provide: HashMap::new(),
+ root_replace: HashMap::new(),
};
let result = resolve(&request).await;
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();
diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs
index 92081d0..af77ba6 100644
--- a/crates/mozart/src/commands/create_project.rs
+++ b/crates/mozart/src/commands/create_project.rs
@@ -424,6 +424,16 @@ pub async fn execute(
),
temporary_constraints: HashMap::new(),
raw_repositories: raw.repositories.clone(),
+ root_provide: raw
+ .provide
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
+ root_replace: raw
+ .replace
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
};
console.info("Resolving dependencies...");
diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs
index 58917e9..20cb6a2 100644
--- a/crates/mozart/src/commands/remove.rs
+++ b/crates/mozart/src/commands/remove.rs
@@ -258,6 +258,16 @@ pub async fn execute(
),
temporary_constraints: HashMap::new(),
raw_repositories: raw.repositories.clone(),
+ root_provide: raw
+ .provide
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
+ root_replace: raw
+ .replace
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
};
// Print header messages
@@ -518,6 +528,16 @@ async fn remove_unused(
),
temporary_constraints: HashMap::new(),
raw_repositories: raw.repositories.clone(),
+ root_provide: raw
+ .provide
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
+ root_replace: raw
+ .replace
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
};
console.info("Resolving dependencies to detect unused packages...");
@@ -866,6 +886,8 @@ mod tests {
),
temporary_constraints: HashMap::new(),
raw_repositories: vec![],
+ root_provide: HashMap::new(),
+ root_replace: HashMap::new(),
};
let resolved = resolve(&request)
.await
@@ -917,6 +939,8 @@ mod tests {
),
temporary_constraints: HashMap::new(),
raw_repositories: vec![],
+ root_provide: HashMap::new(),
+ root_replace: HashMap::new(),
};
let resolved2 = resolve(&request2)
.await
diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs
index 630d960..95b26ea 100644
--- a/crates/mozart/src/commands/require.rs
+++ b/crates/mozart/src/commands/require.rs
@@ -647,6 +647,16 @@ pub async fn execute(
),
temporary_constraints: HashMap::new(),
raw_repositories: raw.repositories.clone(),
+ root_provide: raw
+ .provide
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
+ root_replace: raw
+ .replace
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
};
// Print header messages
@@ -1042,6 +1052,8 @@ mod tests {
),
temporary_constraints: HashMap::new(),
raw_repositories: vec![],
+ root_provide: HashMap::new(),
+ root_replace: HashMap::new(),
};
let resolved = resolver::resolve(&request)
@@ -1110,6 +1122,8 @@ mod tests {
),
temporary_constraints: HashMap::new(),
raw_repositories: vec![],
+ root_provide: HashMap::new(),
+ root_replace: HashMap::new(),
};
let resolved = resolver::resolve(&request)
diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs
index 847ccf7..33b305a 100644
--- a/crates/mozart/src/commands/update.rs
+++ b/crates/mozart/src/commands/update.rs
@@ -887,6 +887,16 @@ pub async fn run(
repositories: repositories.clone(),
temporary_constraints,
raw_repositories: composer_json.repositories.clone(),
+ root_provide: composer_json
+ .provide
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
+ root_replace: composer_json
+ .replace
+ .iter()
+ .map(|(k, v)| (k.clone(), v.clone()))
+ .collect(),
};
// Step 6: Print header and run resolver
@@ -1994,6 +2004,8 @@ mod tests {
),
temporary_constraints: HashMap::new(),
raw_repositories: vec![],
+ root_provide: HashMap::new(),
+ root_replace: HashMap::new(),
};
let resolved = resolve(&request).await.expect("Resolution should succeed");
diff --git a/crates/mozart/tests/installer.rs b/crates/mozart/tests/installer.rs
index fcb29c1..99ef2d8 100644
--- a/crates/mozart/tests/installer.rs
+++ b/crates/mozart/tests/installer.rs
@@ -327,7 +327,7 @@ installer_fixture!(
provider_packages_can_not_be_installed_unless_selected,
ignore
);
-installer_fixture!(provider_satisfies_its_own_requirement, ignore);
+installer_fixture!(provider_satisfies_its_own_requirement);
installer_fixture!(remove_deletes_unused_deps);
installer_fixture!(
remove_does_nothing_if_removal_requires_update_of_dep,
@@ -342,7 +342,7 @@ installer_fixture!(
replaced_packages_should_not_be_installed_when_installing_from_lock,
ignore
);
-installer_fixture!(replacer_satisfies_its_own_requirement, ignore);
+installer_fixture!(replacer_satisfies_its_own_requirement);
installer_fixture!(repositories_priorities, ignore);
installer_fixture!(repositories_priorities2, ignore);
installer_fixture!(repositories_priorities3, ignore);