diff options
Diffstat (limited to 'crates/mozart-registry/src/installer_executor/filesystem.rs')
| -rw-r--r-- | crates/mozart-registry/src/installer_executor/filesystem.rs | 232 |
1 files changed, 0 insertions, 232 deletions
diff --git a/crates/mozart-registry/src/installer_executor/filesystem.rs b/crates/mozart-registry/src/installer_executor/filesystem.rs deleted file mode 100644 index cb1a2cc..0000000 --- a/crates/mozart-registry/src/installer_executor/filesystem.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! Production [`InstallerExecutor`] that touches the real filesystem. -//! -//! This is the verb behind `mozart install` / `mozart update` — it pulls -//! dist archives via [`crate::downloader`], clones VCS sources via -//! [`mozart_vcs`], and removes vendor directories. Test code substitutes a -//! recording-only executor instead (added in a later step). - -use std::path::Path; - -use crate::cache::Cache; -use crate::downloader; - -use super::{ExecuteContext, InstallerExecutor, PackageOperation}; - -pub struct FilesystemExecutor { - files_cache: Cache, -} - -impl FilesystemExecutor { - pub fn new(files_cache: Cache) -> Self { - Self { files_cache } - } -} - -#[async_trait::async_trait] -impl InstallerExecutor for FilesystemExecutor { - async fn install_package( - &mut self, - op: PackageOperation<'_>, - ctx: &ExecuteContext, - ) -> anyhow::Result<()> { - // Marking an alias as installed/uninstalled has no filesystem side - // effects — the target package's files are already in vendor/. - // Mirrors Composer's `MarkAlias{,Un}installedOperation` which the - // installation manager only uses to update the in-memory installed - // repository. - let Some(pkg) = op.package() else { - return Ok(()); - }; - - // Try source install if --prefer-source and source info is available. - if ctx.prefer_source - && let Some(source) = &pkg.source - { - return install_from_source( - &source.source_type, - &source.url, - source.reference.as_deref().unwrap_or("HEAD"), - &ctx.vendor_dir, - &pkg.name, - ); - } - - // A package with neither dist nor source has no install action. - // This covers Composer's `type: metapackage` (modeled explicitly as - // "no installer") and inline `type: package` definitions used in - // test fixtures that intentionally omit download metadata. Mozart - // records the operation and the installed.json entry but performs - // no filesystem work, mirroring Composer's MetapackageInstaller. - if pkg.dist.is_none() && pkg.source.is_none() { - return Ok(()); - } - - let dist = pkg.dist.as_ref().ok_or_else(|| { - anyhow::anyhow!( - "Package {} has no dist information. Use --prefer-source to install from VCS.", - pkg.name, - ) - })?; - - let mut progress = downloader::DownloadProgress::new( - !ctx.no_progress, - format!("{} ({})", pkg.name, pkg.version), - ); - - downloader::install_package( - &dist.url, - &dist.dist_type, - dist.shasum.as_deref(), - &ctx.vendor_dir, - &pkg.name, - Some(&mut progress), - &self.files_cache, - ) - .await?; - - progress.finish(); - Ok(()) - } - - fn uninstall_package( - &mut self, - name: &str, - _version: &str, - ctx: &ExecuteContext, - ) -> anyhow::Result<()> { - let pkg_dir = ctx.vendor_dir.join(name); - if pkg_dir.exists() { - std::fs::remove_dir_all(&pkg_dir)?; - } - Ok(()) - } - - fn cleanup_after_uninstalls(&mut self, ctx: &ExecuteContext) -> anyhow::Result<()> { - cleanup_empty_vendor_dirs(&ctx.vendor_dir) - } -} - -/// Remove empty vendor namespace directories left behind after package -/// removals. Skips the `composer/` and `bin/` directories. Mirrors the -/// post-uninstall cleanup Composer does in `LibraryInstaller::removeCode`. -fn cleanup_empty_vendor_dirs(vendor_dir: &Path) -> anyhow::Result<()> { - if let Ok(entries) = std::fs::read_dir(vendor_dir) { - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - let name = entry.file_name().to_string_lossy().to_string(); - if name == "composer" || name == "bin" { - continue; - } - if std::fs::read_dir(&path)?.next().is_none() { - std::fs::remove_dir(&path)?; - } - } - } - } - Ok(()) -} - -/// Install a package from VCS source (git/svn/hg). Lifted from the previous -/// `commands/install.rs::install_from_source`. Mirrors the per-driver -/// dispatch in `Composer\Downloader\VcsDownloader::install`. -fn install_from_source( - source_type: &str, - url: &str, - reference: &str, - vendor_dir: &Path, - package_name: &str, -) -> anyhow::Result<()> { - let target = vendor_dir.join(package_name); - if target.exists() { - std::fs::remove_dir_all(&target)?; - } - - match source_type { - "git" => { - let process = mozart_vcs::process::ProcessExecutor::new(); - let git_util = - mozart_vcs::util::git::GitUtil::new(process, vendor_dir.join(".cache").join("git")); - let downloader = mozart_vcs::downloader::git::GitDownloader::new(git_util); - use mozart_vcs::downloader::VcsDownloader; - downloader.download(url, reference, &target)?; - downloader.install(url, reference, &target)?; - } - "svn" => { - let process = mozart_vcs::process::ProcessExecutor::new(); - let svn_util = mozart_vcs::util::svn::SvnUtil::new(process); - let downloader = mozart_vcs::downloader::svn::SvnDownloader::new(svn_util); - use mozart_vcs::downloader::VcsDownloader; - downloader.install(url, reference, &target)?; - } - "hg" => { - let process = mozart_vcs::process::ProcessExecutor::new(); - let hg_util = mozart_vcs::util::hg::HgUtil::new(process); - let downloader = mozart_vcs::downloader::hg::HgDownloader::new(hg_util); - use mozart_vcs::downloader::VcsDownloader; - downloader.install(url, reference, &target)?; - } - _ => { - anyhow::bail!("Unsupported source type for VCS install: {}", source_type); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use tempfile::tempdir; - - fn make_executor() -> FilesystemExecutor { - FilesystemExecutor::new(Cache::new(std::env::temp_dir().join("__no_cache"), false)) - } - - #[test] - fn cleanup_after_uninstalls_removes_empty_namespace_dirs() { - let dir = tempdir().unwrap(); - let vendor_dir = dir.path().join("vendor"); - std::fs::create_dir_all(&vendor_dir).unwrap(); - - let empty_ns = vendor_dir.join("old-vendor"); - std::fs::create_dir_all(&empty_ns).unwrap(); - - let nonempty_ns = vendor_dir.join("psr"); - std::fs::create_dir_all(nonempty_ns.join("log")).unwrap(); - - std::fs::create_dir_all(vendor_dir.join("composer")).unwrap(); - - let mut exec = make_executor(); - exec.cleanup_after_uninstalls(&ExecuteContext { - vendor_dir: vendor_dir.clone(), - no_progress: true, - prefer_source: false, - }) - .unwrap(); - - assert!(!empty_ns.exists()); - assert!(vendor_dir.join("psr").exists()); - assert!(vendor_dir.join("composer").exists()); - } - - #[test] - fn cleanup_after_uninstalls_preserves_bin_dir() { - let dir = tempdir().unwrap(); - let vendor_dir = dir.path().join("vendor"); - std::fs::create_dir_all(&vendor_dir).unwrap(); - - let bin_dir = vendor_dir.join("bin"); - std::fs::create_dir_all(&bin_dir).unwrap(); - - let mut exec = make_executor(); - exec.cleanup_after_uninstalls(&ExecuteContext { - vendor_dir: vendor_dir.clone(), - no_progress: true, - prefer_source: false, - }) - .unwrap(); - - assert!(bin_dir.exists()); - } -} |
