diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-23 11:38:42 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-23 11:38:42 +0900 |
| commit | 0080efea9386d46f65d1862fcb90eb44999d9761 (patch) | |
| tree | e9f7e17b3f12ff9b09b3df0848fd55e91003cd23 /crates/mozart | |
| parent | eb1e21c059d83f0af9786e4d3cace80afe8456a2 (diff) | |
| download | php-mozart-0080efea9386d46f65d1862fcb90eb44999d9761.tar.gz php-mozart-0080efea9386d46f65d1862fcb90eb44999d9761.tar.zst php-mozart-0080efea9386d46f65d1862fcb90eb44999d9761.zip | |
feat(vcs): add mozart-vcs crate for VCS repository support
Implement VCS driver/downloader infrastructure mirroring Composer's VCS
subsystem. Includes drivers for GitHub, GitLab, Bitbucket, Forgejo, Git,
Hg, and SVN with API-based metadata resolution, plus source downloaders
for Git/Hg/SVN. Integrates into mozart-registry via vcs_bridge module to
scan VCS repositories and feed discovered packages into the SAT resolver.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart')
| -rw-r--r-- | crates/mozart/Cargo.toml | 1 | ||||
| -rw-r--r-- | crates/mozart/src/commands/create_project.rs | 2 | ||||
| -rw-r--r-- | crates/mozart/src/commands/install.rs | 99 | ||||
| -rw-r--r-- | crates/mozart/src/commands/remove.rs | 6 | ||||
| -rw-r--r-- | crates/mozart/src/commands/require.rs | 4 | ||||
| -rw-r--r-- | crates/mozart/src/commands/update.rs | 10 |
6 files changed, 92 insertions, 30 deletions
diff --git a/crates/mozart/Cargo.toml b/crates/mozart/Cargo.toml index 915273b..3f1cd33 100644 --- a/crates/mozart/Cargo.toml +++ b/crates/mozart/Cargo.toml @@ -9,6 +9,7 @@ mozart-autoload.workspace = true mozart-core.workspace = true mozart-registry.workspace = true mozart-semver.workspace = true +mozart-vcs.workspace = true anyhow.workspace = true clap.workspace = true clap_complete.workspace = true diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs index 6a5b815..2a8ce4f 100644 --- a/crates/mozart/src/commands/create_project.rs +++ b/crates/mozart/src/commands/create_project.rs @@ -411,6 +411,7 @@ pub async fn execute( ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, temporary_constraints: HashMap::new(), + repositories: raw.repositories.clone(), }; console.info("Resolving dependencies..."); @@ -502,6 +503,7 @@ pub async fn execute( apcu_autoloader, apcu_autoloader_prefix: None, download_only: false, + prefer_source: args.prefer_source, }, ) .await?; diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index 24c79b3..bf35788 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -117,6 +117,8 @@ pub struct InstallConfig { pub apcu_autoloader_prefix: Option<String>, /// Only download packages, skip autoloader generation and installed.json write. pub download_only: bool, + /// Prefer installing from VCS source rather than dist archives. + pub prefer_source: bool, } impl Default for InstallConfig { @@ -133,6 +135,7 @@ impl Default for InstallConfig { apcu_autoloader: false, apcu_autoloader_prefix: None, download_only: false, + prefer_source: false, } } } @@ -300,20 +303,51 @@ fn make_progress(show: bool, pkg_name: &str, version: &str) -> downloader::Downl downloader::DownloadProgress::new(show, format!("{pkg_name} ({version})")) } -/// Install packages from a lock file into vendor/. -/// -/// Used by both the `install` and `update` commands. -/// -/// This function: -/// 1. Determines which packages to install (prod + optionally dev) -/// 2. Warns about platform requirements (unless ignored) -/// 3. Reads currently installed packages -/// 4. Computes install/update/skip/removal operations -/// 5. Prints a summary -/// 6. Executes downloads with optional progress bars (unless dry_run) -/// 7. Writes vendor/composer/installed.json -/// 8. Cleans up empty vendor directories -/// 9. Generates the autoloader (unless no_autoloader) +/// Install a package from VCS source (git/svn/hg). +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(()) +} + pub async fn install_from_lock( lock: &lockfile::LockFile, working_dir: &Path, @@ -405,11 +439,32 @@ pub async fn install_from_lock( } } + // Try source install if --prefer-source and source info is available + if config.prefer_source + && let Some(source) = &pkg.source + { + install_from_source( + &source.source_type, + &source.url, + source.reference.as_deref().unwrap_or("HEAD"), + vendor_dir, + &pkg.name, + )?; + continue; + } + let dist = pkg.dist.as_ref().ok_or_else(|| { - anyhow::anyhow!( - "Package {} has no dist information — source installs are not yet supported", - pkg.name - ) + if pkg.source.is_some() { + anyhow::anyhow!( + "Package {} has no dist information. Use --prefer-source to install from VCS.", + pkg.name, + ) + } else { + anyhow::anyhow!( + "Package {} has no dist or source information", + pkg.name, + ) + } })?; let mut progress = make_progress(!config.no_progress, &pkg.name, &pkg.version); @@ -604,18 +659,13 @@ pub async fn execute( } } - // Step 5: Warn about prefer-source (not yet supported) + // Step 5: Determine if prefer-source is enabled let prefer_source = args.prefer_source || args .prefer_install .as_deref() .map(|s| s.eq_ignore_ascii_case("source")) .unwrap_or(false); - if prefer_source { - console.info(&console_format!( - "<warning>Warning: Source installs are not yet supported. Falling back to dist.</warning>" - )); - } // Step 6: Determine dev mode and vendor directory let dev_mode = !args.no_dev; @@ -638,6 +688,7 @@ pub async fn execute( apcu_autoloader: args.apcu_autoloader || args.apcu_autoloader_prefix.is_some(), apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: args.download_only, + prefer_source, }, ) .await diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs index 88fa4da..6d3b5e2 100644 --- a/crates/mozart/src/commands/remove.rs +++ b/crates/mozart/src/commands/remove.rs @@ -251,6 +251,7 @@ pub async fn execute( ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, temporary_constraints: HashMap::new(), + repositories: raw.repositories.clone(), }; // Print header messages @@ -438,6 +439,7 @@ pub async fn execute( apcu_autoloader: args.apcu_autoloader || args.apcu_autoloader_prefix.is_some(), apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: false, + prefer_source: false, }, ) .await?; @@ -497,6 +499,7 @@ async fn remove_unused( ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, temporary_constraints: HashMap::new(), + repositories: raw.repositories.clone(), }; console.info("Resolving dependencies to detect unused packages..."); @@ -577,6 +580,7 @@ async fn remove_unused( apcu_autoloader: args.apcu_autoloader || args.apcu_autoloader_prefix.is_some(), apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: false, + prefer_source: false, }, ) .await?; @@ -826,6 +830,7 @@ mod tests { ignore_platform_req_list: vec![], repo_cache: None, temporary_constraints: HashMap::new(), + repositories: vec![], }; let resolved = resolve(&request) .await @@ -862,6 +867,7 @@ mod tests { ignore_platform_req_list: vec![], repo_cache: None, temporary_constraints: HashMap::new(), + repositories: vec![], }; let resolved2 = resolve(&request2) .await diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs index 4ea739d..15b5f1c 100644 --- a/crates/mozart/src/commands/require.rs +++ b/crates/mozart/src/commands/require.rs @@ -656,6 +656,7 @@ pub async fn execute( ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, temporary_constraints: HashMap::new(), + repositories: raw.repositories.clone(), }; // Print header messages @@ -870,6 +871,7 @@ pub async fn execute( || config_apcu, apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: false, + prefer_source: args.prefer_source, }, ) .await?; @@ -1028,6 +1030,7 @@ mod tests { ignore_platform_req_list: vec![], repo_cache: None, temporary_constraints: HashMap::new(), + repositories: vec![], }; let resolved = resolver::resolve(&request) @@ -1081,6 +1084,7 @@ mod tests { ignore_platform_req_list: vec![], repo_cache: None, temporary_constraints: HashMap::new(), + repositories: vec![], }; let resolved = resolver::resolve(&request) diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index 06e6b22..c1901cd 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -835,6 +835,7 @@ pub async fn execute( ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, temporary_constraints, + repositories: composer_json.repositories.clone(), }; // Step 6: Print header and run resolver @@ -1164,18 +1165,13 @@ pub async fn execute( // Step 12: Install packages (unless --no-install or --dry-run) if !args.no_install && !args.dry_run { - // Warn about prefer-source (not yet supported) + // Determine if prefer-source is enabled let prefer_source = args.prefer_source || args .prefer_install .as_deref() .map(|s| s.eq_ignore_ascii_case("source")) .unwrap_or(false); - if prefer_source { - console.info(&console_format!( - "<warning>Warning: Source installs are not yet supported. Falling back to dist.</warning>" - )); - } super::install::install_from_lock( &new_lock, @@ -1193,6 +1189,7 @@ pub async fn execute( apcu_autoloader: args.apcu_autoloader || args.apcu_autoloader_prefix.is_some(), apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: false, + prefer_source, }, ) .await?; @@ -1909,6 +1906,7 @@ mod tests { ignore_platform_req_list: vec![], repo_cache: None, temporary_constraints: HashMap::new(), + repositories: vec![], }; let resolved = resolve(&request).await.expect("Resolution should succeed"); |
