aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-registry/src/installer_executor/mod.rs
blob: fde4c4958e4e37c47938336618a5c57520bb505b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//! 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::lockfile::LockedPackage;

pub mod filesystem;

pub use filesystem::FilesystemExecutor;

/// 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.
    Update {
        from_version: &'a str,
        package: &'a LockedPackage,
    },
}

impl<'a> PackageOperation<'a> {
    pub fn package(&self) -> &'a LockedPackage {
        match self {
            PackageOperation::Install { package }
            | PackageOperation::Update { package, .. } => package,
        }
    }
}

/// 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.
    fn uninstall_package(&mut self, name: &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(())
    }
}