diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-02 19:02:30 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-02 19:02:30 +0900 |
| commit | 804b5b9a2a7759af24e41408c82dfc60c6092cf3 (patch) | |
| tree | 71b060c186e14dc2e5174684848e84b3343a4a27 /crates/mozart-registry | |
| parent | 33fe16285acbed1f5146c2d746eba2295bd57688 (diff) | |
| download | php-mozart-804b5b9a2a7759af24e41408c82dfc60c6092cf3.tar.gz php-mozart-804b5b9a2a7759af24e41408c82dfc60c6092cf3.tar.zst php-mozart-804b5b9a2a7759af24e41408c82dfc60c6092cf3.zip | |
fix(installer): match Composer's transaction order and uninstall label
Three coupled changes that bring `compute_operations` + the in-process
trace recorder into byte-parity with Composer's `Transaction::__toString`
output:
- `TraceRecorderExecutor`: emit "Removing X (V)" instead of "Uninstalling
X (V)" — Composer's `UninstallOperation::__toString` uses "Removing".
- `install_from_lock`: run removals before installs/updates to mirror
`Transaction::moveUninstallsToFront`. Both dry-run and real-execution
branches now emit the same prefix order.
- `topological_sort`: replace recursive DFS with the stack-based DFS that
Composer uses in `Transaction::calculateOperations`. Roots are seeded
reverse-alphabetically (matching `setResultPackageMaps`'s uasort with
`strcmp(b, a)`), and `getProvidersInResult` is mirrored by treating a
package's `provide`/`replace` keys as additional name targets when
resolving a `require` link.
To make the third change work end-to-end, `LockedPackage` gains typed
`provide` and `replace` fields (Composer's lock preserves them; Mozart
was silently dropping them). `packagist_version_to_locked_package` now
copies them through.
Unignores 13 installer fixtures (10 newly green from the fix, 3 that
were already green-but-still-flagged): conflict_downgrade_nested,
install_from_lock_removes_package, install_security_advisory_matching_dependency,
load_replaced_package_if_replacer_dropped, partial_update_keeps_older_dep_*
(×2), partial_update_security_advisory_matching_locked_dep,
provider_packages_can_be_installed_together_with_provided_if_both_installable,
remove_deletes_unused_deps, replace_priorities,
update_allow_list_require_new_replace,
update_allow_list_with_dependencies_require_new_replace,
update_requiring_decision_reverts_and_learning_positive_literals.
Installer scoreboard: 75/187 → 88/187.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-registry')
| -rw-r--r-- | crates/mozart-registry/src/installer_executor/trace_recorder.rs | 5 | ||||
| -rw-r--r-- | crates/mozart-registry/src/lockfile.rs | 16 |
2 files changed, 18 insertions, 3 deletions
diff --git a/crates/mozart-registry/src/installer_executor/trace_recorder.rs b/crates/mozart-registry/src/installer_executor/trace_recorder.rs index 9fdc91b..c924d73 100644 --- a/crates/mozart-registry/src/installer_executor/trace_recorder.rs +++ b/crates/mozart-registry/src/installer_executor/trace_recorder.rs @@ -12,7 +12,7 @@ //! - Install: `Installing <name> (<version>)` //! - Update (upgrade direction): `Upgrading <name> (<oldVersion> => <newVersion>)` //! - Update (downgrade direction): `Downgrading <name> (<oldVersion> => <newVersion>)` -//! - Uninstall: `Uninstalling <name> (<version>)` +//! - Uninstall: `Removing <name> (<version>)` use mozart_semver::Version; @@ -85,8 +85,7 @@ impl InstallerExecutor for TraceRecorderExecutor { version: &str, _ctx: &ExecuteContext, ) -> anyhow::Result<()> { - self.trace - .push(format!("Uninstalling {} ({})", name, version)); + self.trace.push(format!("Removing {} ({})", name, version)); Ok(()) } } diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs index 8022f8b..075848f 100644 --- a/crates/mozart-registry/src/lockfile.rs +++ b/crates/mozart-registry/src/lockfile.rs @@ -85,6 +85,12 @@ pub struct LockedPackage { #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub conflict: BTreeMap<String, String>, + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub provide: BTreeMap<String, String>, + + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub replace: BTreeMap<String, String>, + #[serde(skip_serializing_if = "Option::is_none")] pub suggest: Option<BTreeMap<String, String>>, @@ -410,6 +416,8 @@ fn packagist_version_to_locked_package(name: &str, pv: &PackagistVersion) -> Loc require: pv.require.clone(), require_dev: pv.require_dev.clone(), conflict: pv.conflict.clone(), + provide: pv.provide.clone(), + replace: pv.replace.clone(), suggest: pv.suggest.clone(), package_type: pv.package_type.clone(), autoload: pv.autoload.clone(), @@ -671,6 +679,8 @@ mod tests { require: BTreeMap::new(), require_dev: BTreeMap::new(), conflict: BTreeMap::new(), + provide: BTreeMap::new(), + replace: BTreeMap::new(), suggest: None, package_type: Some("library".to_string()), autoload: None, @@ -1121,6 +1131,8 @@ mod tests { require: BTreeMap::new(), require_dev: BTreeMap::new(), conflict: BTreeMap::new(), + provide: BTreeMap::new(), + replace: BTreeMap::new(), suggest: None, package_type: None, autoload: None, @@ -1144,6 +1156,8 @@ mod tests { require: BTreeMap::new(), require_dev: BTreeMap::new(), conflict: BTreeMap::new(), + provide: BTreeMap::new(), + replace: BTreeMap::new(), suggest: None, package_type: None, autoload: None, @@ -1270,6 +1284,8 @@ mod tests { require: BTreeMap::new(), require_dev: BTreeMap::new(), conflict: BTreeMap::new(), + provide: BTreeMap::new(), + replace: BTreeMap::new(), suggest: None, package_type: Some("library".to_string()), autoload: None, |
