From 804b5b9a2a7759af24e41408c82dfc60c6092cf3 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 2 May 2026 19:02:30 +0900 Subject: fix(installer): match Composer's transaction order and uninstall label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../src/installer_executor/trace_recorder.rs | 5 ++--- crates/mozart-registry/src/lockfile.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) (limited to 'crates/mozart-registry') 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 ()` //! - Update (upgrade direction): `Upgrading ( => )` //! - Update (downgrade direction): `Downgrading ( => )` -//! - Uninstall: `Uninstalling ()` +//! - Uninstall: `Removing ()` 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, + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub provide: BTreeMap, + + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub replace: BTreeMap, + #[serde(skip_serializing_if = "Option::is_none")] pub suggest: Option>, @@ -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, -- cgit v1.3.1