ref: remove universal fallback chains from buildmeta and platlatest

Arch and libc fallbacks are not universal — they depend on the OS,
the package, and even the version. ARM64 on macOS/Windows can run
x64 (Rosetta/emulation) but not on Linux. Musl can be static or
dynamically linked depending on the package version. Windows GNU
may or may not need mingw.

These rules belong in per-installer config, not in shared types.
platlatest stays as a simple fact store (triplet → version).
Resolution with fallbacks will be the caller's job.
This commit is contained in:
AJ ONeal
2026-03-09 21:50:10 -06:00
parent 34cfe32492
commit 1253fcd671
3 changed files with 0 additions and 180 deletions

View File

@@ -102,40 +102,3 @@ func (t Target) Triplet() string {
return string(t.OS) + "-" + string(t.Arch) + "-" + string(t.Libc)
}
// ArchFallbacks returns the architectures that a machine with the given
// arch can run, ordered from most specific to least. The input arch is
// always first. Returns nil for unknown architectures.
//
// For example, an amd64v4 machine can run v4, v3, v2, and baseline (v1)
// binaries. An armv7 machine can run armv7 and armv6 binaries.
func ArchFallbacks(arch Arch) []Arch {
switch arch {
case ArchAMD64v4:
return []Arch{ArchAMD64v4, ArchAMD64v3, ArchAMD64v2, ArchAMD64}
case ArchAMD64v3:
return []Arch{ArchAMD64v3, ArchAMD64v2, ArchAMD64}
case ArchAMD64v2:
return []Arch{ArchAMD64v2, ArchAMD64}
case ArchARMv7:
return []Arch{ArchARMv7, ArchARMv6}
default:
// No fallback chain — exact match only.
return []Arch{arch}
}
}
// LibcFallbacks returns the libc variants a machine can use, ordered
// by preference. A musl system can only run musl or static binaries.
// A glibc system can only run glibc or static binaries.
func LibcFallbacks(libc Libc) []Libc {
switch libc {
case LibcGNU:
return []Libc{LibcGNU, LibcNone}
case LibcMusl:
return []Libc{LibcMusl, LibcNone}
case LibcMSVC:
return []Libc{LibcMSVC, LibcNone}
default:
return []Libc{libc}
}
}

View File

@@ -22,7 +22,6 @@ import (
"sync"
"github.com/webinstall/webi-installers/internal/buildmeta"
"github.com/webinstall/webi-installers/internal/lexver"
)
// Index tracks the latest version for each build target of a package.
@@ -79,41 +78,6 @@ func (idx *Index) All() map[string]string {
return out
}
// Resolve finds the latest compatible version for a target by walking
// the arch and libc fallback chains. It prefers the newest version over
// the most specific arch match.
//
// For example, on an amd64v4 machine with glibc:
// - v2.0.0 has amd64 (baseline) → candidate "v2.0.0"
// - v1.0.0 has amd64v4 → candidate "v1.0.0"
// - Returns "v2.0.0" because it's newer, even though v1.0.0
// is a more specific arch match.
//
// Returns the best version and the exact target it matched, or "" if
// no compatible version exists.
func (idx *Index) Resolve(t buildmeta.Target) (version string, matched buildmeta.Target) {
idx.mu.RLock()
defer idx.mu.RUnlock()
arches := buildmeta.ArchFallbacks(t.Arch)
libcs := buildmeta.LibcFallbacks(t.Libc)
for _, arch := range arches {
for _, libc := range libcs {
candidate := buildmeta.Target{OS: t.OS, Arch: arch, Libc: libc}
v := idx.m[candidate.Triplet()]
if v == "" {
continue
}
if version == "" || lexver.Compare(lexver.Parse(v), lexver.Parse(version)) > 0 {
version = v
matched = candidate
}
}
}
return version, matched
}
// Save persists the index to disk (atomic write).
func (idx *Index) Save() error {
idx.mu.RLock()

View File

@@ -91,113 +91,6 @@ func TestAll(t *testing.T) {
}
}
func TestResolveArchFallback(t *testing.T) {
p := filepath.Join(t.TempDir(), "latest.json")
idx, err := platlatest.Open(p)
if err != nil {
t.Fatal(err)
}
// v1.0.0 had per-microarch builds.
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64v4, Libc: buildmeta.LibcGNU}, "v1.0.0")
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64v3, Libc: buildmeta.LibcGNU}, "v1.0.0")
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64v2, Libc: buildmeta.LibcGNU}, "v1.0.0")
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64, Libc: buildmeta.LibcGNU}, "v1.0.0")
// v2.0.0 dropped microarch, ships only baseline amd64.
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64, Libc: buildmeta.LibcGNU}, "v2.0.0")
// An amd64v4 machine should get v2.0.0 (latest) via baseline fallback,
// not v1.0.0 (which had a specific v4 build).
want := buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64v4, Libc: buildmeta.LibcGNU}
ver, matched := idx.Resolve(want)
if ver != "v2.0.0" {
t.Errorf("Resolve(amd64v4) version = %q, want v2.0.0", ver)
}
if matched.Arch != buildmeta.ArchAMD64 {
t.Errorf("Resolve(amd64v4) matched arch = %q, want %q", matched.Arch, buildmeta.ArchAMD64)
}
}
func TestResolveExactMatchPreferred(t *testing.T) {
p := filepath.Join(t.TempDir(), "latest.json")
idx, err := platlatest.Open(p)
if err != nil {
t.Fatal(err)
}
// Both amd64v3 and baseline have the same latest version.
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64v3, Libc: buildmeta.LibcGNU}, "v2.0.0")
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64, Libc: buildmeta.LibcGNU}, "v2.0.0")
// When versions are equal, the more specific arch should win
// (it appears first in the fallback chain).
want := buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64v3, Libc: buildmeta.LibcGNU}
ver, matched := idx.Resolve(want)
if ver != "v2.0.0" {
t.Errorf("version = %q, want v2.0.0", ver)
}
if matched.Arch != buildmeta.ArchAMD64v3 {
t.Errorf("matched arch = %q, want %q (more specific)", matched.Arch, buildmeta.ArchAMD64v3)
}
}
func TestResolveLibcFallback(t *testing.T) {
p := filepath.Join(t.TempDir(), "latest.json")
idx, err := platlatest.Open(p)
if err != nil {
t.Fatal(err)
}
// Only a static (LibcNone) build exists.
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64, Libc: buildmeta.LibcNone}, "v1.0.0")
// A glibc machine should find it via libc fallback.
want := buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64, Libc: buildmeta.LibcGNU}
ver, matched := idx.Resolve(want)
if ver != "v1.0.0" {
t.Errorf("version = %q, want v1.0.0", ver)
}
if matched.Libc != buildmeta.LibcNone {
t.Errorf("matched libc = %q, want %q", matched.Libc, buildmeta.LibcNone)
}
}
func TestResolveNoMatch(t *testing.T) {
p := filepath.Join(t.TempDir(), "latest.json")
idx, err := platlatest.Open(p)
if err != nil {
t.Fatal(err)
}
idx.Set(linuxAMD64, "v1.0.0")
// Darwin target should not match a Linux entry.
ver, _ := idx.Resolve(darwinARM64)
if ver != "" {
t.Errorf("Resolve(darwin) = %q, want empty (no match)", ver)
}
}
func TestResolveBaselineOnly(t *testing.T) {
p := filepath.Join(t.TempDir(), "latest.json")
idx, err := platlatest.Open(p)
if err != nil {
t.Fatal(err)
}
// amd64v1 machine can't run v2+ binaries.
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64v2, Libc: buildmeta.LibcGNU}, "v2.0.0")
idx.Set(buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64, Libc: buildmeta.LibcGNU}, "v1.0.0")
// Baseline machine gets v1.0.0 — it can't run v2's amd64v2 binary.
want := buildmeta.Target{OS: buildmeta.OSLinux, Arch: buildmeta.ArchAMD64, Libc: buildmeta.LibcGNU}
ver, _ := idx.Resolve(want)
if ver != "v1.0.0" {
t.Errorf("Resolve(amd64 baseline) = %q, want v1.0.0", ver)
}
}
func TestOpenNonexistent(t *testing.T) {
p := filepath.Join(t.TempDir(), "does-not-exist.json")
idx, err := platlatest.Open(p)