aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-registry/src/installer_executor/mod.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-10 00:32:08 +0900
committernsfisis <nsfisis@gmail.com>2026-05-10 00:32:08 +0900
commit8cc1ba8a02c0318b65658f1634de378c780392b9 (patch)
treefdd5cb61e488018891a486b25991b87c84220bb8 /crates/mozart-registry/src/installer_executor/mod.rs
parent72b2e877c01e67ba7edd37e34ac2eadb7a1c62c4 (diff)
downloadphp-mozart-8cc1ba8a02c0318b65658f1634de378c780392b9.tar.gz
php-mozart-8cc1ba8a02c0318b65658f1634de378c780392b9.tar.zst
php-mozart-8cc1ba8a02c0318b65658f1634de378c780392b9.zip
refactor(workspace): consolidate crates into mozart-core
Merged mozart-archiver, mozart-autoload, mozart-registry, mozart-sat-resolver, and mozart-vcs into mozart-core to align the source layout with Composer's structure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-registry/src/installer_executor/mod.rs')
-rw-r--r--crates/mozart-registry/src/installer_executor/mod.rs348
1 files changed, 0 insertions, 348 deletions
diff --git a/crates/mozart-registry/src/installer_executor/mod.rs b/crates/mozart-registry/src/installer_executor/mod.rs
deleted file mode 100644
index 4ddad66..0000000
--- a/crates/mozart-registry/src/installer_executor/mod.rs
+++ /dev/null
@@ -1,348 +0,0 @@
-//! Installation execution abstraction.
-//!
-//! Mirrors `Composer\Installer\InstallationManager`: the per-operation
-//! side-effect surface (download, extract, remove from vendor/) lives behind
-//! a trait so test code can substitute a recording-only implementation
-//! (Composer's `InstallationManagerMock`) without going anywhere near the
-//! filesystem or the network.
-//!
-//! The orchestration loop (computing operations from lock vs installed,
-//! emitting console messages, writing `installed.json`, generating the
-//! autoloader) stays in the caller. The executor is purely the verb —
-//! "install this package" / "uninstall this package" — so test traces match
-//! Composer's `(string) $operation` byte-for-byte without the executor
-//! having to also reproduce console formatting.
-
-use std::path::PathBuf;
-
-use crate::installed::InstalledPackageEntry;
-use crate::lockfile::{LockAlias, LockedPackage};
-
-pub mod filesystem;
-pub mod trace_recorder;
-pub mod transaction;
-
-pub use filesystem::FilesystemExecutor;
-pub use trace_recorder::TraceRecorderExecutor;
-pub use transaction::{
- Action, StaleInstalledAlias, compute_operations, compute_stale_installed_aliases,
- locked_to_installed_entry, previously_installed_alias_versions,
-};
-
-/// One install or update operation handed to [`InstallerExecutor::install_package`].
-#[derive(Debug, Clone, Copy)]
-pub enum PackageOperation<'a> {
- /// First-time install. The whole package directory is created from
- /// `package.dist`/`package.source`.
- Install { package: &'a LockedPackage },
- /// Replace an existing install with a new version. `from_version` is the
- /// pretty version that was installed before (no reference suffix —
- /// drives the upgrade-vs-downgrade direction). `from_full_pretty` /
- /// `to_full_pretty` are the formatted display strings used verbatim in
- /// the trace output; the caller renders them via
- /// [`format_update_pretty_versions`] so the SOURCE_REF / DIST_REF mode
- /// switch from Composer's `UpdateOperation::format` lands on both sides.
- Update {
- from_version: &'a str,
- from_full_pretty: &'a str,
- to_full_pretty: &'a str,
- package: &'a LockedPackage,
- },
- /// Mark an alias of a real package as installed. No filesystem effects —
- /// only the trace recorder needs this. Mirrors Composer's
- /// `MarkAliasInstalledOperation`.
- MarkAliasInstalled {
- /// The alias entry from `composer.lock`'s `aliases[]` block. Carries
- /// pretty + normalized alias version and the target's pretty version.
- alias: &'a LockAlias,
- /// The target package the alias points at — used to source the
- /// reference suffix for the trace line.
- target: &'a LockedPackage,
- },
- /// Mark a previously-installed alias as uninstalled. No filesystem
- /// effects — only the trace recorder cares. Mirrors Composer's
- /// `MarkAliasUninstalledOperation`. Composer derives the AliasPackage
- /// from the previous installed.json entries (via `extra.branch-alias`),
- /// then emits this when the alias is no longer in the result. Caller
- /// pre-renders the display strings so this variant doesn't need to know
- /// how to spelunk the entry.
- MarkAliasUninstalled {
- /// Package name (e.g. `a/a`) used as both the alias's name and the
- /// target's name on the trace line.
- name: &'a str,
- /// Alias's full-pretty form (alias pretty version plus reference
- /// suffix), e.g. `1.0.x-dev master`.
- alias_full: &'a str,
- /// Target's full-pretty form, e.g. `dev-master master`.
- target_full: &'a str,
- },
-}
-
-impl<'a> PackageOperation<'a> {
- pub fn package(&self) -> Option<&'a LockedPackage> {
- match self {
- PackageOperation::Install { package } | PackageOperation::Update { package, .. } => {
- Some(package)
- }
- PackageOperation::MarkAliasInstalled { .. }
- | PackageOperation::MarkAliasUninstalled { .. } => None,
- }
- }
-}
-
-/// Mirror Composer's `BasePackage::getFullPrettyVersion()` for a `LockedPackage`.
-///
-/// For dev-stability versions backed by a git/hg source, append the reference
-/// (truncated to 7 chars when it looks like a 40-char sha1). Otherwise return
-/// the pretty version unchanged.
-pub fn format_full_pretty_version(pkg: &LockedPackage) -> String {
- format_full_pretty_with_pretty(&pkg.version, pkg)
-}
-
-/// Same as [`format_full_pretty_version`] but lets the caller supply an
-/// alternate pretty version (used by `MarkAliasInstalled` so the alias's
-/// `3.2.x-dev` text is rendered with the *target's* reference).
-pub fn format_full_pretty_with_pretty(pretty_version: &str, pkg: &LockedPackage) -> String {
- let source_ref = pkg.source.as_ref().and_then(|s| s.reference.as_deref());
- let dist_ref = pkg.dist.as_ref().and_then(|d| d.reference.as_deref());
- let source_type = pkg.source.as_ref().map(|s| s.source_type.as_str());
- format_full_pretty_with_refs(
- pretty_version,
- &pkg.version,
- source_ref,
- dist_ref,
- source_type,
- )
-}
-
-/// Render an alias's full pretty version: the alias's own pretty form for
-/// the visible text, the alias's *normalized* version for the dev-stability
-/// gate, and the target package's source/dist references for the suffix.
-/// Mirrors `AliasPackage::getFullPrettyVersion`, where the alias decides on
-/// its own whether to append a reference based on its own stability — so a
-/// stable alias like `1.0.0` skips the suffix even when the target is a dev
-/// branch.
-pub fn format_full_pretty_alias(
- alias_pretty: &str,
- alias_version: &str,
- target: &LockedPackage,
-) -> String {
- let source_ref = target.source.as_ref().and_then(|s| s.reference.as_deref());
- let dist_ref = target.dist.as_ref().and_then(|d| d.reference.as_deref());
- let source_type = target.source.as_ref().map(|s| s.source_type.as_str());
- format_full_pretty_with_refs(
- alias_pretty,
- alias_version,
- source_ref,
- dist_ref,
- source_type,
- )
-}
-
-/// Same as [`format_full_pretty_version_for_installed`] but lets the caller
-/// supply an alternate pretty version. Used when emitting
-/// `MarkAliasUninstalled`: the alias's `1.0.x-dev` text needs to be rendered
-/// with the *target installed entry's* reference suffix.
-pub fn format_full_pretty_with_pretty_for_installed(
- pretty_version: &str,
- entry: &InstalledPackageEntry,
-) -> String {
- let source_ref = entry
- .source
- .as_ref()
- .and_then(|v| v.get("reference"))
- .and_then(|v| v.as_str());
- let dist_ref = entry
- .dist
- .as_ref()
- .and_then(|v| v.get("reference"))
- .and_then(|v| v.as_str());
- let source_type = entry
- .source
- .as_ref()
- .and_then(|v| v.get("type"))
- .and_then(|v| v.as_str());
- format_full_pretty_with_refs(
- pretty_version,
- &entry.version,
- source_ref,
- dist_ref,
- source_type,
- )
-}
-
-/// Mirror Composer's `BasePackage::getFullPrettyVersion()` for an
-/// `InstalledPackageEntry`. Same display rules as
-/// [`format_full_pretty_version`] but pulls source/dist info out of the
-/// installed.json `source`/`dist` JSON values.
-pub fn format_full_pretty_version_for_installed(entry: &InstalledPackageEntry) -> String {
- format_full_pretty_with_pretty_for_installed(&entry.version, entry)
-}
-
-/// Render the from/to display strings for an update trace line, mirroring
-/// Composer's `UpdateOperation::format`. Defaults to `DISPLAY_SOURCE_REF_IF_DEV`,
-/// then if both sides render identically:
-///
-/// - source references differ → re-render in `DISPLAY_SOURCE_REF` mode,
-/// - else dist references differ → re-render in `DISPLAY_DIST_REF` mode.
-///
-/// Without the switch, two same-version-different-reference packages would
-/// produce a useless `pkg (X => X)` trace line.
-pub fn format_update_pretty_versions(
- from_entry: &InstalledPackageEntry,
- to_pkg: &LockedPackage,
-) -> (String, String) {
- let from_default = format_full_pretty_version_for_installed(from_entry);
- let to_default = format_full_pretty_version(to_pkg);
- if from_default != to_default {
- return (from_default, to_default);
- }
-
- let from_source_ref = from_entry
- .source
- .as_ref()
- .and_then(|v| v.get("reference"))
- .and_then(|v| v.as_str());
- let from_source_type = from_entry
- .source
- .as_ref()
- .and_then(|v| v.get("type"))
- .and_then(|v| v.as_str());
- let to_source_ref = to_pkg.source.as_ref().and_then(|s| s.reference.as_deref());
- let to_source_type = to_pkg.source.as_ref().map(|s| s.source_type.as_str());
-
- if from_source_ref != to_source_ref {
- return (
- format_with_explicit_reference(&from_entry.version, from_source_ref, from_source_type),
- format_with_explicit_reference(&to_pkg.version, to_source_ref, to_source_type),
- );
- }
-
- let from_dist_ref = from_entry
- .dist
- .as_ref()
- .and_then(|v| v.get("reference"))
- .and_then(|v| v.as_str());
- let to_dist_ref = to_pkg.dist.as_ref().and_then(|d| d.reference.as_deref());
-
- if from_dist_ref != to_dist_ref {
- return (
- format_with_explicit_reference(&from_entry.version, from_dist_ref, from_source_type),
- format_with_explicit_reference(&to_pkg.version, to_dist_ref, to_source_type),
- );
- }
-
- (from_default, to_default)
-}
-
-/// Render `pretty_version` with an explicitly chosen reference, mirroring
-/// Composer's `BasePackage::getFullPrettyVersion` with `DISPLAY_SOURCE_REF`
-/// or `DISPLAY_DIST_REF`: skip the dev-stability gate, just truncate sha1
-/// references and concatenate. A `None` reference falls back to the bare
-/// pretty version.
-fn format_with_explicit_reference(
- pretty_version: &str,
- reference: Option<&str>,
- source_type: Option<&str>,
-) -> String {
- let Some(reference) = reference else {
- return pretty_version.to_string();
- };
- if matches!(source_type, Some("svn")) {
- return format!("{} {}", pretty_version, reference);
- }
- if reference.len() == 40 {
- return format!("{} {}", pretty_version, &reference[..7]);
- }
- format!("{} {}", pretty_version, reference)
-}
-
-/// Core of `BasePackage::getFullPrettyVersion()` factored over raw
-/// fields so both [`LockedPackage`] and [`InstalledPackageEntry`] can share
-/// the rendering logic. `version` drives the dev-stability check; the result
-/// is `pretty_version` plus a reference suffix when the package is a dev
-/// branch backed by git/hg (with sha1 references truncated to 7 chars).
-fn format_full_pretty_with_refs(
- pretty_version: &str,
- version: &str,
- source_ref: Option<&str>,
- dist_ref: Option<&str>,
- source_type: Option<&str>,
-) -> String {
- let is_dev = mozart_semver::Version::parse(version)
- .map(|v| matches!(v.pre_release.as_deref(), Some("dev")) || v.is_dev_branch)
- .unwrap_or(false);
- if !is_dev {
- return pretty_version.to_string();
- }
- // Composer falls back to dist reference only when no source type is set
- // (or the package isn't git/hg — in which case the dev display is skipped
- // entirely above).
- let reference = source_ref.or(match source_type {
- Some("git") | Some("hg") => None,
- _ => dist_ref,
- });
- let Some(reference) = reference else {
- return pretty_version.to_string();
- };
- if matches!(source_type, Some("git") | Some("hg")) && reference.len() == 40 {
- format!("{} {}", pretty_version, &reference[..7])
- } else if matches!(source_type, Some("svn")) {
- // svn references are revision numbers, never truncated
- format!("{} {}", pretty_version, reference)
- } else if reference.len() == 40 {
- // dist-ref fallback (no git/hg source) — Composer truncates here too
- format!("{} {}", pretty_version, &reference[..7])
- } else {
- format!("{} {}", pretty_version, reference)
- }
-}
-
-/// Per-call configuration shared across executor methods. Owned by the
-/// caller (typically `install_from_lock`) so the executor sees a consistent
-/// view across an entire install/update run.
-#[derive(Debug, Clone)]
-pub struct ExecuteContext {
- pub vendor_dir: PathBuf,
- /// Suppress download progress bars.
- pub no_progress: bool,
- /// Prefer cloning from VCS source over downloading dist archives.
- pub prefer_source: bool,
-}
-
-/// Side-effect surface for install/update/uninstall operations.
-///
-/// Implementations are stateful — `&mut self` lets a recorder accumulate
-/// trace lines and lets the filesystem implementation hold long-lived
-/// handles (caches, progress bars). All methods return `anyhow::Result` so
-/// callers can short-circuit on the first failure, mirroring Composer's
-/// fail-fast `InstallationManager::execute`.
-#[async_trait::async_trait]
-pub trait InstallerExecutor: Send + Sync {
- /// Perform side effects for one install or update operation.
- async fn install_package(
- &mut self,
- op: PackageOperation<'_>,
- ctx: &ExecuteContext,
- ) -> anyhow::Result<()>;
-
- /// Perform side effects for one uninstall.
- ///
- /// `version` is the previously-installed version (from installed.json),
- /// passed so the trace recorder can format Composer's
- /// `Uninstalling pkg/name (version)` line. The filesystem implementation
- /// ignores it — `name` alone is enough to locate the vendor directory.
- fn uninstall_package(
- &mut self,
- name: &str,
- version: &str,
- ctx: &ExecuteContext,
- ) -> anyhow::Result<()>;
-
- /// Hook called once after every uninstall has run. Default no-op.
- /// Composer cleans up empty namespace directories here; the recorder
- /// has no work to do.
- fn cleanup_after_uninstalls(&mut self, _ctx: &ExecuteContext) -> anyhow::Result<()> {
- Ok(())
- }
-}