aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--crates/mozart-registry/src/lockfile.rs39
-rw-r--r--crates/mozart-registry/src/repository/packagist_repo.rs24
-rw-r--r--crates/mozart-registry/src/resolver.rs90
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,
);