Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions e2e/lockfile/test_lockfile_aqua_version_prefix_override
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env bash

export MISE_LOCKFILE=1
export MISE_AQUA_BAKED_REGISTRY=0

# An HTTP registry entry has no release-tag lookup. Exercise the same fallback
# used when a pinned GitHub release is absent from the release listing.
ASSET="codex-package-x86_64-pc-windows-msvc.tar.gz"
mkdir -p server/rust-v0.154.0
printf 'archive\n' >"server/rust-v0.154.0/$ASSET"
printf 'old archive\n' >server/old.zip
printf 'new archive\n' >server/new.zip
start_http_server --dir server

REGISTRY_DIR="$PWD/aqua-registry-local"
mkdir -p "$REGISTRY_DIR"
cat <<EOF >"$REGISTRY_DIR/registry.yaml"
packages:
- type: http
name: example/codex-package
version_prefix: rust-
version_constraint: "false"
version_overrides:
- version_constraint: semver("<= 0.133.0-alpha.1")
url: http://127.0.0.1:$HTTP_PORT/legacy.zst
format: zst
- version_constraint: "true"
url: http://127.0.0.1:$HTTP_PORT/{{.Version}}/codex-package-{{.Arch}}-{{.OS}}.{{.Format}}
format: tar.gz
replacements:
amd64: x86_64
windows: pc-windows-msvc
- type: http
name: example/ambiguous
version_constraint: "false"
version_overrides:
- version_constraint: "true"
version_prefix: old-v
url: http://127.0.0.1:$HTTP_PORT/old.zip
- version_constraint: "true"
version_prefix: new-v
url: http://127.0.0.1:$HTTP_PORT/new.zip
EOF
export MISE_AQUA_REGISTRY_URL="file://$REGISTRY_DIR"

cat <<'EOF' >mise.toml
[tools]
"aqua:example/codex-package" = "0.154.0"
EOF

mise lock --platform windows-x64

assert_contains "cat mise.lock" '"platforms.windows-x64"'
assert_contains "cat mise.lock" "url = \"http://127.0.0.1:$HTTP_PORT/rust-v0.154.0/$ASSET\""

# Both prefix families have reachable archives. A missing tag must not let
# registry order choose which one gets recorded in the lockfile.
mkdir ambiguous
cd ambiguous
cat <<'EOF' >mise.toml
[tools]
"aqua:example/ambiguous" = "1.0.0"
EOF

assert_contains "mise lock aqua:example/ambiguous --platform windows-x64 2>&1" "ambiguous aqua version prefixes for 1.0.0: old-v, new-v"
Comment thread
jdx marked this conversation as resolved.
Resolved
if [[ -f mise.lock ]]; then
assert_not_contains "cat mise.lock" "/old.zip"
assert_not_contains "cat mise.lock" "/new.zip"
fi
207 changes: 193 additions & 14 deletions src/backend/aqua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,21 +770,23 @@ impl Backend for AquaBackend {
self.id, tv.version
);
}
let mut v = tag.unwrap_or_else(|| tv.version.clone());
let mut v_prefixed = (tag_is_none && !tv.version.starts_with('v')).then(|| format!("v{v}"));
let versions = match &v_prefixed {
Some(v_prefixed) => vec![v.as_str(), v_prefixed.as_str()],
None => vec![v.as_str()],
};

// Get package and apply version/overrides directly for the target platform.
// Using package_with_version() here would apply overrides for the current host
// platform first, which can leak host-specific overrides into cross-platform lock.
let pkg = AQUA_REGISTRY.package(&self.id).await?;
let mut v = tag.clone().unwrap_or_else(|| tv.version.clone());
let mut v_prefixed = (tag_is_none && !tv.version.starts_with('v')).then(|| format!("v{v}"));
let raw_opts = tv.request.options();
let opts = AquaOptions::new(&raw_opts);
let target_libc = Self::target_variant_libc(target, self.tool_libc);
let pkg = pkg.with_version_libc(&versions, target_os, target_arch, target_libc.as_deref());
let pkg = lock_package_for_version(
pkg,
&tv.version,
tag.as_deref(),
target_os,
target_arch,
target_libc.as_deref(),
)?;
let pkg = Self::apply_aqua_libc_replacement(
pkg,
target_os,
Expand Down Expand Up @@ -1073,7 +1075,7 @@ impl AquaBackend {
let mut v_prefixed =
(tag.is_none() && !tv.version.starts_with('v')).then(|| format!("v{v}"));
let pkg = AQUA_REGISTRY.package(&self.id).await?;
let versions = install_package_version_candidates(&tv.version, tag.as_deref(), &pkg);
let versions = resolved_package_version_candidates(&tv.version, tag.as_deref(), &pkg);
let versions = versions.iter().map(|v| v.as_ref()).collect_vec();
let pkg = Self::package_with_options_for_pkg(tv, pkg, &versions)?;
if let Some(prefix) = &pkg.version_prefix
Expand Down Expand Up @@ -3910,21 +3912,64 @@ fn package_version_candidates<'a>(version: &'a str, pkg: &AquaPackage) -> Vec<Co
candidates.into_iter().unique().collect()
}

fn install_package_version_candidates<'a>(
/// Keep an exact release tag authoritative, otherwise include registry prefixes.
fn resolved_package_version_candidates<'a>(
version: &'a str,
tag: Option<&'a str>,
pkg: &AquaPackage,
) -> Vec<Cow<'a, str>> {
if let Some(tag) = tag {
vec![Cow::Borrowed(tag)]
} else {
// Locked HTTP package URLs do not contain a GitHub release tag. Include the
// registry's prefixes so prefix-scoped overrides still provide metadata such
// as the archive format.
// Tag lookup can miss a pinned release, and locked HTTP URLs have no release
// tag. Include registry prefixes before selecting version overrides, otherwise
// packages such as openai/codex keep their empty top-level asset and format.
package_version_candidates(version, pkg)
}
}

/// Select lock metadata only when the version candidates agree on a prefix family.
fn lock_package_for_version(
pkg: AquaPackage,
version: &str,
tag: Option<&str>,
os: &str,
arch: &str,
libc: Option<&str>,
) -> Result<AquaPackage> {
let candidates = resolved_package_version_candidates(version, tag, &pkg);
let versions = candidates.iter().map(|v| v.as_ref()).collect_vec();
// Without a prefix on any override every candidate resolves to the root prefix,
// so there is nothing to disambiguate and no reason to clone the package.
let has_override_prefix = pkg
.version_overrides
.iter()
.any(|vo| vo.version_prefix.is_some());
if tag.is_none() && has_override_prefix {
let prefixes = versions
.iter()
.filter(|v| pkg.version_constraint_ok(&[v]))
.map(|v| {
pkg.clone()
.with_version_libc(&[v], os, arch, libc)
.version_prefix
.unwrap_or_default()
})
.unique()
.collect_vec();
if prefixes.len() > 1 {
bail!(
"ambiguous aqua version prefixes for {version}: {}; no exact release tag matched",
prefixes
.iter()
.map(|prefix| if prefix.is_empty() { "(none)" } else { prefix })
.join(", ")
);
}
}
Ok(pkg.with_version_libc(&versions, os, arch, libc))
}

fn github_release_tag_from_url(url: &str) -> Option<String> {
let url = Url::parse(url).ok()?;
if url.host_str()? != "github.com" {
Expand Down Expand Up @@ -4630,14 +4675,148 @@ packages:
let request =
ToolRequest::new(backend, "3.16.1.0", crate::toolset::ToolSource::Unknown).unwrap();
let tv = ToolVersion::new(request, "3.16.1.0".to_string());
let versions = install_package_version_candidates("3.16.1.0", None, &pkg);
let versions = resolved_package_version_candidates("3.16.1.0", None, &pkg);
let versions = versions.iter().map(|v| v.as_ref()).collect_vec();
let pkg = AquaBackend::package_with_options_for_pkg(&tv, pkg, &versions).unwrap();

assert_eq!(pkg.version_prefix.as_deref(), Some("cabal-install-v"));
assert_eq!(pkg.format, "tar.xz");
}

#[test]
fn test_resolved_package_without_tag_selects_codex_windows_archive() {
let pkg: AquaPackage = serde_yaml::from_str(
r#"
type: github_release
repo_owner: openai
repo_name: codex
version_prefix: rust-
version_constraint: "false"
version_overrides:
- version_constraint: semver("<= 0.133.0-alpha.1")
asset: codex-{{.Arch}}-{{.OS}}.exe.{{.Format}}
format: zst
- version_constraint: "true"
asset: codex-package-{{.Arch}}-{{.OS}}.{{.Format}}
format: tar.gz
files:
- name: codex
src: bin/codex
replacements:
amd64: x86_64
windows: pc-windows-msvc
"#,
)
.unwrap();

// A pinned release may be absent from GitHub's release listing. Its registry
// prefix must still be considered before applying the version overrides.
let pkg = lock_package_for_version(pkg, "0.154.0", None, "windows", "amd64", None).unwrap();

assert_eq!(
pkg.asset("rust-v0.154.0", "windows", "amd64").unwrap(),
"codex-package-x86_64-pc-windows-msvc.tar.gz"
);
assert_eq!(pkg.format, "tar.gz");
assert_eq!(pkg.files[0].src.as_deref(), Some("bin/codex"));
}

#[test]
fn test_resolved_package_known_tag_preserves_override_prefix() {
let pkg: AquaPackage = serde_yaml::from_str(
r#"
version_constraint: "false"
version_overrides:
- version_constraint: "true"
version_prefix: old-v
asset: old-layout.zip
- version_constraint: "true"
version_prefix: new-v
asset: new-layout.zip
"#,
)
.unwrap();
let pkg =
lock_package_for_version(pkg, "1.0.0", Some("new-v1.0.0"), "windows", "amd64", None)
.unwrap();

assert_eq!(
pkg.asset("new-v1.0.0", "windows", "amd64").unwrap(),
"new-layout.zip"
);
}

#[test]
fn test_lock_package_without_tag_rejects_ambiguous_override_prefixes() {
let pkg: AquaPackage = serde_yaml::from_str(
r#"
version_constraint: "false"
version_overrides:
- version_constraint: "true"
version_prefix: old-v
asset: old-layout.zip
- version_constraint: "true"
version_prefix: new-v
asset: new-layout.zip
"#,
)
.unwrap();
let err =
lock_package_for_version(pkg, "1.0.0", None, "windows", "amd64", None).unwrap_err();
assert!(err.to_string().contains("ambiguous aqua version prefixes"));
assert!(err.to_string().contains("old-v, new-v"));
}

#[test]
fn test_lock_package_prefixes_with_disjoint_constraints() {
let pkg: AquaPackage = serde_yaml::from_str(
r#"
version_constraint: "false"
version_overrides:
- version_constraint: semver("< 1.0.0")
version_prefix: old-v
asset: old-layout.zip
- version_constraint: semver(">= 1.0.0")
version_prefix: new-v
asset: new-layout.zip
"#,
)
.unwrap();

for (version, expected) in [("0.9.0", "old-layout.zip"), ("1.0.0", "new-layout.zip")] {
let resolved =
lock_package_for_version(pkg.clone(), version, None, "windows", "amd64", None)
.unwrap();
assert_eq!(
resolved.asset(version, "windows", "amd64").unwrap(),
expected
);
}
}

#[test]
fn test_lock_package_ignores_inactive_override_prefixes() {
let pkg: AquaPackage = serde_yaml::from_str(
r#"
asset: default.zip
version_overrides:
- version_constraint: "true"
version_prefix: old-v
asset: old-layout.zip
- version_constraint: "true"
version_prefix: new-v
asset: new-layout.zip
"#,
)
.unwrap();

let pkg = lock_package_for_version(pkg, "1.0.0", None, "windows", "amd64", None).unwrap();
assert_eq!(
pkg.asset("1.0.0", "windows", "amd64").unwrap(),
"default.zip"
);
}

#[test]
fn test_candidate_bin_paths_use_v_prefixed_package_override() {
let registry = ParsedRegistry::parse_yaml(
Expand Down
Loading