From 8da98493daf5013585e07ec98ca6960a42924edf Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 2 May 2026 22:21:25 +0900 Subject: feat(resolver): add branch-alias support across the resolution pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plumb Composer's `extra.branch-alias` mechanism end-to-end so a dev branch (e.g. `dev-foobar`) can be installed alongside its numeric alias (e.g. `3.2.x-dev`) and resolve constraints written against the alias target. Concretely: - `mozart-semver`: stop treating pure-numeric `-dev` as a wildcard branch — `3.2.9999999.9999999-dev` (the form `normalizeBranch` emits) now parses as a classical version with `is_dev_branch=false`, so constraints like `3.2.*` match it. - `mozart-registry/composer_repo`: load `type: composer` repositories from `file://` URLs (legacy embedded `packages.json`). - `mozart-registry/resolver`: emit pool entries in pairs for dev branches with `extra.branch-alias`, link them via `is_alias_of`, and apply `@dev`/`@beta` etc. stability suffix flags from root requires. - `mozart-sat-resolver`: alias rules (`PackageAlias` / `PackageInverseAlias`) so alias and target install together; alias packages skipped from same-name conflict indexing. - `mozart-sat-resolver/policy`: `DefaultPolicy` now honors `prefer_stable` via Composer's stability-tier comparison. - `mozart-registry/lockfile`: split resolved set into real packages vs. alias entries; populate the `aliases[]` block. - `mozart-registry/installer_executor`: new `MarkAliasInstalled` operation; `format_full_pretty_version` mirroring `BasePackage::getFullPrettyVersion` (appends source ref[0..7] for dev/git packages). - Test harness rewrites fixture-relative `file://` URLs to absolute paths. Newly green fixtures: `install_branch_alias_composer_repo`, `alias_solver_problems`, `alias_solver_problems2`, `conflict_with_all_dependencies_option_dont_recommend_to_use_it`, `unbounded_conflict_does_not_match_default_branch_with_branch_alias`, `unbounded_conflict_does_not_match_default_branch_with_numeric_branch`. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/mozart-sat-resolver/src/pool.rs | 42 +++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) (limited to 'crates/mozart-sat-resolver/src/pool.rs') diff --git a/crates/mozart-sat-resolver/src/pool.rs b/crates/mozart-sat-resolver/src/pool.rs index 652fc60..0312c24 100644 --- a/crates/mozart-sat-resolver/src/pool.rs +++ b/crates/mozart-sat-resolver/src/pool.rs @@ -53,6 +53,13 @@ pub struct PoolPackage { pub conflicts: Vec, /// Whether this is a fixed/locked package. pub is_fixed: bool, + /// If `Some`, this package is an `AliasPackage` whose target is the + /// other pool entry with the given ID. Composer creates these for + /// `extra.branch-alias` entries (dev branch → numeric alias). When set, + /// the rule generator emits `PackageAlias`/`PackageInverseAlias` rules + /// instead of regular requires; same-name conflict rules also skip + /// alias packages. + pub is_alias_of: Option, } impl PoolPackage { @@ -99,6 +106,12 @@ pub struct PoolPackageInput { pub provides: Vec, pub conflicts: Vec, pub is_fixed: bool, + /// When `Some`, the value is the **normalized** version of another input + /// in this build batch with the same `name`; the pool will resolve it to + /// that input's [`PackageId`] in [`PoolPackage::is_alias_of`]. Used by + /// the registry layer to materialize Composer's `AliasPackage` for + /// `extra.branch-alias` entries. + pub is_alias_of: Option, } /// The package pool: contains all candidate packages for dependency resolution. @@ -119,11 +132,17 @@ pub struct Pool { impl Pool { /// Create a new pool from a list of package inputs. pub fn new(inputs: Vec, unacceptable_fixed_ids: Vec) -> Self { - let mut packages = Vec::with_capacity(inputs.len()); + let mut packages: Vec = Vec::with_capacity(inputs.len()); let mut package_by_name: HashMap> = HashMap::new(); + // Collect alias links (alias_idx, target_name, target_normalized) for + // a second pass once every input has a stable ID. + let mut pending_aliases: Vec<(usize, String, String)> = Vec::new(); for (idx, input) in inputs.into_iter().enumerate() { let id = (idx as PackageId) + 1; + if let Some(target) = input.is_alias_of.clone() { + pending_aliases.push((idx, input.name.clone(), target)); + } let pkg = PoolPackage { id, name: input.name, @@ -134,6 +153,7 @@ impl Pool { provides: input.provides, conflicts: input.conflicts, is_fixed: input.is_fixed, + is_alias_of: None, }; // Index by all names this package provides @@ -147,6 +167,25 @@ impl Pool { packages.push(pkg); } + // Resolve alias targets: for each alias input, find the matching + // (name, normalized version) entry and store its ID. Mirrors the + // post-construction wiring Composer does in + // `RepositorySet::createAliasPackage` / `addPackage`. + for (alias_idx, name, target_normalized) in pending_aliases { + if let Some(ids) = package_by_name.get(&name) { + let target_id = ids.iter().copied().find(|&id| { + let candidate = &packages[(id - 1) as usize]; + !candidate.name.is_empty() + && candidate.name == name + && candidate.version == target_normalized + && candidate.is_alias_of.is_none() + }); + if let Some(tid) = target_id { + packages[alias_idx].is_alias_of = Some(tid); + } + } + } + Pool { packages, package_by_name, @@ -317,6 +356,7 @@ mod tests { provides: vec![], conflicts: vec![], is_fixed: false, + is_alias_of: None, } } -- cgit v1.3.1