diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-03 16:34:10 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-03 16:34:10 +0900 |
| commit | 9eecfe303c944c80556b0b25fcd3ce6bbce3aeb8 (patch) | |
| tree | dc195bda8422447fe9cd762120a850ba726be529 /crates/mozart/src/commands/install.rs | |
| parent | d84024fb179e3ebb55573971a329cb6ff72d7fa0 (diff) | |
| download | php-mozart-9eecfe303c944c80556b0b25fcd3ce6bbce3aeb8.tar.gz php-mozart-9eecfe303c944c80556b0b25fcd3ce6bbce3aeb8.tar.zst php-mozart-9eecfe303c944c80556b0b25fcd3ce6bbce3aeb8.zip | |
fix(install): reject locks where two packages claim the same name
Mirror Composer's `RuleSetGenerator::addConflictRules` SAME_NAME pass on
the locked package set: a package's `getNames(false)` is its canonical
name plus the names it claims via `replace`, and any name with two
providers makes the lock-verify solve unsatisfiable. Mozart's `install`
skips that solve, so the conflict slipped through and both packages were
installed; surface it explicitly and exit DEPENDENCY_RESOLUTION_FAILED.
`provide` targets are deliberately excluded — `getNames(false)` excludes
them, since multiple providers of a virtual name may co-exist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart/src/commands/install.rs')
| -rw-r--r-- | crates/mozart/src/commands/install.rs | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index c7caff4..52e82ec 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -486,6 +486,55 @@ fn collect_install_platform_problems( ) } +/// Mirror Composer's `RuleSetGenerator::addConflictRules` SAME_NAME loop on +/// the locked package set. `getNames(false)` returns each package's +/// canonical name plus the names it claims via `replace`; when two distinct +/// locked packages claim the same name, only one of them can be installed. +/// During Composer's lock-verify solve every locked package is `fix`-locked, +/// so two providers of the same name make the rule unsatisfiable and the +/// solver throws `SolverProblemsException` → exit 2. +/// +/// `provide` is intentionally excluded — `getNames(false)` excludes it, and +/// virtual `provide` targets allow multiple co-installed providers. +fn collect_install_same_name_problems(lock: &lockfile::LockFile, dev_mode: bool) -> Vec<String> { + let mut providers: BTreeMap<String, Vec<String>> = BTreeMap::new(); + + let mut all_pkgs: Vec<&lockfile::LockedPackage> = lock.packages.iter().collect(); + if dev_mode { + all_pkgs.extend(lock.packages_dev.iter().flatten()); + } + + for p in all_pkgs { + let canonical = p.name.to_lowercase(); + providers + .entry(canonical.clone()) + .or_default() + .push(p.name.clone()); + for replace_target in p.replace.keys() { + let target_lower = replace_target.to_lowercase(); + if target_lower == canonical { + continue; + } + providers + .entry(target_lower) + .or_default() + .push(p.name.clone()); + } + } + + let mut problems = Vec::new(); + for (name, owners) in &providers { + if owners.len() > 1 { + problems.push(format!( + "- Conflict between locked packages on name {}: {}", + name, + owners.join(", ") + )); + } + } + problems +} + /// Merge platform requirements from the lock's `platform`/`platform-dev` /// fields and the root composer.json's `require`/`require-dev`. Root /// composer.json overrides the lock on duplicate keys (matching Composer's @@ -1043,6 +1092,21 @@ pub async fn run( mozart_core::exit_code::DEPENDENCY_RESOLUTION_FAILED, )); } + + let same_name_problems = collect_install_same_name_problems(&lock, dev_mode); + if !same_name_problems.is_empty() { + console.info( + "Your lock file does not contain a compatible set of packages. Please run composer update.", + ); + console.info(""); + for (i, msg) in same_name_problems.iter().enumerate() { + console.info(&format!(" Problem {}", i + 1)); + console.info(&format!(" {msg}")); + } + return Err(mozart_core::exit_code::bail_silent( + mozart_core::exit_code::DEPENDENCY_RESOLUTION_FAILED, + )); + } } // Step 6: Determine if prefer-source is enabled |
