diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-02 22:21:25 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-02 22:21:25 +0900 |
| commit | 8da98493daf5013585e07ec98ca6960a42924edf (patch) | |
| tree | be57603fec29a4bf1e5f546b1ba2e14778595cb3 /crates/mozart | |
| parent | 804b5b9a2a7759af24e41408c82dfc60c6092cf3 (diff) | |
| download | php-mozart-8da98493daf5013585e07ec98ca6960a42924edf.tar.gz php-mozart-8da98493daf5013585e07ec98ca6960a42924edf.tar.zst php-mozart-8da98493daf5013585e07ec98ca6960a42924edf.zip | |
feat(resolver): add branch-alias support across the resolution pipeline
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) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart')
| -rw-r--r-- | crates/mozart/src/commands/install.rs | 16 | ||||
| -rw-r--r-- | crates/mozart/src/commands/update.rs | 1 | ||||
| -rw-r--r-- | crates/mozart/tests/installer.rs | 67 |
3 files changed, 67 insertions, 17 deletions
diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index 356d622..9555ba7 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -670,6 +670,22 @@ pub async fn install_from_lock( } }; executor.install_package(op, &exec_ctx).await?; + + // After the target install/update, emit MarkAliasInstalled for any + // aliases whose `package`+`version` (the target's pretty version) + // match. Mirrors Composer's `Transaction::calculateOperations` DFS + // which pushes alias targets first and emits MarkAliasInstalled + // when the alias itself is processed. + for alias in &lock.aliases { + if alias.package.eq_ignore_ascii_case(&pkg.name) && alias.version == pkg.version { + executor + .install_package( + PackageOperation::MarkAliasInstalled { alias, target: pkg }, + &exec_ctx, + ) + .await?; + } + } } // Step 8: Write updated vendor/composer/installed.json (unless download_only) diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index 9ac2664..847ccf7 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -1370,6 +1370,7 @@ mod tests { version: version.to_string(), version_normalized: format!("{}.0", version), is_dev: false, + alias_of_normalized: None, } } diff --git a/crates/mozart/tests/installer.rs b/crates/mozart/tests/installer.rs index 173418a..d674485 100644 --- a/crates/mozart/tests/installer.rs +++ b/crates/mozart/tests/installer.rs @@ -25,6 +25,47 @@ fn fixtures_dir() -> PathBuf { .join("../../composer/tests/Composer/Test/Fixtures/installer") } +/// Rewrite `file://foobar` URLs in COMPOSER content to absolute fixture +/// paths. Mirrors `composer/tests/Composer/Test/InstallerTest.php:540-542`: +/// when a fixture's repository entry uses a relative `file://` URL, anchor +/// it to the fixtures directory so the on-disk `packages.json` is reachable. +fn rewrite_fixture_file_urls(input: &str) -> String { + let fixtures = fixtures_dir(); + let canonical = fixtures + .canonicalize() + .unwrap_or(fixtures) + .display() + .to_string() + .replace('\\', "/"); + // Match `"file://X"` where X does not start with `/` — those are the + // fixture-relative form. Absolute URLs (`file:///abs/...`) are passed + // through. + let mut out = String::with_capacity(input.len()); + let mut rest = input; + while let Some(idx) = rest.find("file://") { + out.push_str(&rest[..idx]); + let after = &rest[idx + "file://".len()..]; + let first_byte = after.as_bytes().first().copied(); + if first_byte == Some(b'/') { + out.push_str("file://"); + rest = after; + continue; + } + // Read the rest of the URL until a `"` or whitespace. + let end = after + .find(|c: char| c == '"' || c.is_whitespace()) + .unwrap_or(after.len()); + let target = &after[..end]; + out.push_str("file://"); + out.push_str(&canonical); + out.push('/'); + out.push_str(target); + rest = &after[end..]; + } + out.push_str(rest); + out +} + struct InProcessRunResult { _working_dir: TempDir, trace: Vec<String>, @@ -37,7 +78,8 @@ async fn run_fixture_in_process(test: &ParsedTest) -> anyhow::Result<InProcessRu let working_dir = TempDir::new()?; let root = working_dir.path(); - std::fs::write(root.join("composer.json"), &test.composer)?; + let composer_json = rewrite_fixture_file_urls(&test.composer); + std::fs::write(root.join("composer.json"), &composer_json)?; if let Some(lock) = &test.lock { std::fs::write(root.join("composer.lock"), lock)?; } @@ -178,8 +220,8 @@ installer_fixture!(alias_in_complex_constraints, ignore); installer_fixture!(alias_in_lock, ignore); installer_fixture!(alias_in_lock2, ignore); installer_fixture!(alias_on_unloadable_package, ignore); -installer_fixture!(alias_solver_problems, ignore); -installer_fixture!(alias_solver_problems2, ignore); +installer_fixture!(alias_solver_problems); +installer_fixture!(alias_solver_problems2); installer_fixture!(alias_with_reference, ignore); installer_fixture!(aliased_priority, ignore); installer_fixture!(aliased_priority_conflicting, ignore); @@ -203,10 +245,7 @@ installer_fixture!( installer_fixture!(conflict_with_alias_in_lock_does_prevents_install, ignore); installer_fixture!(conflict_with_alias_prevents_update, ignore); installer_fixture!(conflict_with_alias_prevents_update_if_not_required, ignore); -installer_fixture!( - conflict_with_all_dependencies_option_dont_recommend_to_use_it, - ignore -); +installer_fixture!(conflict_with_all_dependencies_option_dont_recommend_to_use_it); installer_fixture!(deduplicate_solver_problems); installer_fixture!(disjunctive_multi_constraints); installer_fixture!(full_update_minimal_changes, ignore); @@ -220,7 +259,7 @@ installer_fixture!(github_issues_9012, ignore); installer_fixture!(github_issues_9290, ignore); installer_fixture!(hint_main_rename, ignore); installer_fixture!(install_aliased_alias, ignore); -installer_fixture!(install_branch_alias_composer_repo, ignore); +installer_fixture!(install_branch_alias_composer_repo); installer_fixture!(install_dev); installer_fixture!(install_dev_using_dist, ignore); installer_fixture!(install_forces_reinstall_if_abandon_changes, ignore); @@ -320,14 +359,8 @@ installer_fixture!(suggest_prod); installer_fixture!(suggest_prod_nolock); installer_fixture!(suggest_replaced); installer_fixture!(suggest_uninstalled); -installer_fixture!( - unbounded_conflict_does_not_match_default_branch_with_branch_alias, - ignore -); -installer_fixture!( - unbounded_conflict_does_not_match_default_branch_with_numeric_branch, - ignore -); +installer_fixture!(unbounded_conflict_does_not_match_default_branch_with_branch_alias); +installer_fixture!(unbounded_conflict_does_not_match_default_branch_with_numeric_branch); installer_fixture!(unbounded_conflict_matches_default_branch, ignore); installer_fixture!( update_abandoned_package_required_but_blocked_via_audit_config, @@ -369,7 +402,7 @@ installer_fixture!(update_ignore_platform_package_requirement_list); installer_fixture!(update_ignore_platform_package_requirement_list_upper_bounds); installer_fixture!(update_ignore_platform_package_requirement_wildcard); installer_fixture!(update_ignore_platform_package_requirements); -installer_fixture!(update_installed_alias, ignore); +installer_fixture!(update_installed_alias); installer_fixture!(update_installed_alias_dry_run); installer_fixture!(update_installed_reference, ignore); installer_fixture!(update_installed_reference_dry_run); |
