diff options
| -rw-r--r-- | crates/mozart-registry/src/lockfile.rs | 39 | ||||
| -rw-r--r-- | crates/mozart-registry/src/repository/packagist_repo.rs | 24 | ||||
| -rw-r--r-- | crates/mozart-registry/src/resolver.rs | 90 |
3 files changed, 99 insertions, 54 deletions
diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs index a99c921..edda3e9 100644 --- a/crates/mozart-registry/src/lockfile.rs +++ b/crates/mozart-registry/src/lockfile.rs @@ -1,5 +1,7 @@ use crate::cache::Cache; -use crate::packagist::{self, PackagistDist, PackagistSource, PackagistVersion}; +use crate::packagist::{PackagistDist, PackagistSource, PackagistVersion}; +use crate::repository::packagist_repo::PackagistRepository; +use crate::repository::{Repository, RepositorySet}; use crate::resolver::ResolvedPackage; use mozart_core::package::{RawPackageData, to_json_pretty}; use serde::{Deserialize, Serialize}; @@ -7,6 +9,18 @@ use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use std::fs; use std::path::Path; +/// Build a [`RepositorySet`] containing only [`PackagistRepository`]. +/// +/// Used by `generate_lock_file` to fetch full metadata for resolved packages +/// not already covered by inline `type: package` repositories. Step B routes +/// only Packagist queries through the trait; VCS/inline migration is a +/// follow-up. +fn build_packagist_repo_set(repo_cache: &Cache) -> RepositorySet { + let repos: Vec<Box<dyn Repository>> = + vec![Box::new(PackagistRepository::new(repo_cache.clone()))]; + RepositorySet::new(repos) +} + fn default_stability() -> String { "stable".to_string() } @@ -514,21 +528,28 @@ fn extract_platform_requirements(requirements: &BTreeMap<String, String>) -> ser /// 3. Computes the content-hash /// 4. Assembles the complete `LockFile` struct pub async fn generate_lock_file(request: &LockFileGenerationRequest) -> anyhow::Result<LockFile> { - // 1. Fetch full metadata for all resolved packages + // 1. Fetch full metadata for all resolved packages. + // + // Inline `type: package` repositories carry full metadata in composer.json + // — short-circuit those before hitting the network. Everything else goes + // through `RepositorySet`, which today contains only Packagist; future + // steps will move VCS / inline through the same set. let mut package_metadata: HashMap<String, PackagistVersion> = HashMap::new(); + let repo_set = build_packagist_repo_set(&request.repo_cache); for pkg in &request.resolved_packages { - // Inline `type: package` repositories carry full metadata in - // composer.json — use it directly instead of hitting Packagist. if let Some(inline) = request.inline_lookup(&pkg.name, &pkg.version_normalized) { package_metadata.insert(pkg.name.clone(), inline); continue; } - let versions = packagist::fetch_package_versions(&pkg.name, &request.repo_cache).await?; - // Find the exact version matching pkg.version_normalized - let matching = versions + let queries = [crate::repository::PackageQuery { + name: pkg.name.as_str(), + constraint: None, + }]; + let results = repo_set.load_packages(&queries).await?; + let matching = results .into_iter() - .find(|v| v.version_normalized == pkg.version_normalized) + .find(|r| r.version.version_normalized == pkg.version_normalized) .ok_or_else(|| { anyhow::anyhow!( "Could not find version {} for package {} in Packagist response", @@ -536,7 +557,7 @@ pub async fn generate_lock_file(request: &LockFileGenerationRequest) -> anyhow:: pkg.name ) })?; - package_metadata.insert(pkg.name.clone(), matching); + package_metadata.insert(pkg.name.clone(), matching.version); } // 2. Classify dev vs non-dev packages diff --git a/crates/mozart-registry/src/repository/packagist_repo.rs b/crates/mozart-registry/src/repository/packagist_repo.rs index 17208c1..a3bbf40 100644 --- a/crates/mozart-registry/src/repository/packagist_repo.rs +++ b/crates/mozart-registry/src/repository/packagist_repo.rs @@ -32,19 +32,19 @@ impl Repository for PackagistRepository { async fn load_packages(&self, queries: &[PackageQuery<'_>]) -> anyhow::Result<LoadResult> { let mut result = LoadResult::default(); for query in queries { - // Mirror the existing transitive-loop tolerance: a 404 / network - // failure for one name is not fatal — it just means this repo - // contributes nothing for that name. `RepositorySet` falls - // through, and the solver fails later if no repo knows it. + // Errors propagate to the caller. Composer's + // `ComposerRepository::loadAsyncPackages` distinguishes 404 + // (empty result, no error) from transport failures (exception); + // Mozart's underlying `fetch_package_versions` doesn't yet make + // that distinction, so for now both surface as `Err` and the + // caller decides whether the loop wants to continue (transitive + // exploration) or abort (seed-time fetch failure). let versions = - match packagist::fetch_package_versions(query.name, &self.cache).await { - Ok(v) => v, - Err(_) => continue, - }; - // `fetch_package_versions` returning Ok counts as "this repo - // authoritatively knows the name", even if the version list is - // empty (matches Composer `ArrayRepository::loadPackages` which - // adds the name to `namesFound` regardless of constraint match). + packagist::fetch_package_versions(query.name, &self.cache).await?; + // A successful fetch counts as "this repo authoritatively knows + // the name", even if the version list is empty — mirrors + // Composer's `ArrayRepository::loadPackages` which adds the + // name to `namesFound` regardless of constraint match. result.names_found.push(query.name.to_string()); for version in versions { result.packages.push(NamedPackagistVersion { diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs index 710a9c4..878dc02 100644 --- a/crates/mozart-registry/src/resolver.rs +++ b/crates/mozart-registry/src/resolver.rs @@ -9,6 +9,8 @@ use std::fmt; use crate::cache::Cache; use crate::packagist; +use crate::repository::packagist_repo::PackagistRepository; +use crate::repository::{PackageQuery, Repository, RepositorySet}; use crate::vcs_bridge; use mozart_core::package::{RawRepository, Stability}; use mozart_sat_resolver::{ @@ -367,6 +369,19 @@ pub struct ResolvedPackage { pub is_dev: bool, } +/// Build a [`RepositorySet`] containing only [`PackagistRepository`]. +/// +/// The resolver still preloads VCS and inline packages directly into the +/// pool builder (and tracks their names in skip-lists) — Step B routes +/// only Packagist queries through the trait. Migrating VCS/inline through +/// `RepositorySet` is a follow-up. The function returns a single-repo set +/// purely so the seed and transitive loops have a uniform call shape. +fn build_packagist_repo_set(repo_cache: &Cache) -> RepositorySet { + let repos: Vec<Box<dyn Repository>> = + vec![Box::new(PackagistRepository::new(repo_cache.clone()))]; + RepositorySet::new(repos) +} + // ───────────────────────────────────────────────────────────────────────────── // Public resolve() function // ───────────────────────────────────────────────────────────────────────────── @@ -481,38 +496,44 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R } } - // Seed the builder with packages for root requirements - for name in root_requires.keys() { - if PackageName(name.clone()).is_platform() { - continue; // platform packages already added - } + // Build the repository set used for Packagist queries (and, in future + // steps, inline + VCS too). Today only Packagist flows through the + // trait — VCS and inline packages above are still preloaded directly, + // and their names go into the skip lists so we don't double-load them + // through this set. + let repo_set: RepositorySet = build_packagist_repo_set(&request.repo_cache); - // Skip packages already provided by VCS or inline-package repositories - if vcs_package_names.contains(name) || inline_package_names.contains(name) { - continue; - } - - // Fetch available versions from Packagist - let versions = packagist::fetch_package_versions(name, &request.repo_cache) - .await - .map_err(|e| { - ResolveError::DependencyFetchError(format!("Failed to fetch {}: {}", name, e)) - })?; - - for pv in &versions { - let inputs = packagist_to_pool_inputs( - name, - pv, - request.minimum_stability, - &request.stability_flags, - ); - for input in inputs { - builder.add_package(input); - } + // Seed the builder with packages for root requirements. + let seed_names: Vec<String> = root_requires + .keys() + .filter(|name| !PackageName((*name).clone()).is_platform()) + .filter(|name| !vcs_package_names.contains(*name) && !inline_package_names.contains(*name)) + .cloned() + .collect(); + let seed_queries: Vec<PackageQuery<'_>> = seed_names + .iter() + .map(|n| PackageQuery { + name: n.as_str(), + constraint: root_requires.get(n).and_then(|c| c.as_deref()), + }) + .collect(); + let seed_results = repo_set + .load_packages(&seed_queries) + .await + .map_err(|e| ResolveError::DependencyFetchError(e.to_string()))?; + for r in &seed_results { + let inputs = packagist_to_pool_inputs( + &r.name, + &r.version, + request.minimum_stability, + &request.stability_flags, + ); + for input in inputs { + builder.add_package(input); } } - // Explore transitive dependencies + // Explore transitive dependencies. while let Some(name) = builder.next_pending() { if PackageName(name.clone()).is_platform() { continue; @@ -523,7 +544,11 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R continue; } - let versions = match packagist::fetch_package_versions(&name, &request.repo_cache).await { + let queries = [PackageQuery { + name: name.as_str(), + constraint: None, + }]; + let results = match repo_set.load_packages(&queries).await { Ok(v) => v, Err(_) => { // Virtual/meta packages (e.g. "psr/http-client-implementation") @@ -532,11 +557,10 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R continue; } }; - - for pv in &versions { + for r in &results { let inputs = packagist_to_pool_inputs( - &name, - pv, + &r.name, + &r.version, request.minimum_stability, &request.stability_flags, ); |
