diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-10 00:32:08 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-10 00:32:08 +0900 |
| commit | 8cc1ba8a02c0318b65658f1634de378c780392b9 (patch) | |
| tree | fdd5cb61e488018891a486b25991b87c84220bb8 /crates/mozart-registry/src/installer_executor/mod.rs | |
| parent | 72b2e877c01e67ba7edd37e34ac2eadb7a1c62c4 (diff) | |
| download | php-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.rs | 348 |
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(()) - } -} |
