tags: accept newer platforms than declared, any-libc linux targets, exported arch lists - #48
Merged
Merged
Conversation
linuxArchs, macosArchs and windowsArchs were package-private with no accessor, so a caller validating an operator-supplied "os/arch" string could only construct a Target and inspect Compile's ErrUnsupportedTarget. That is enough to decline a value and not enough to explain it: an error message cannot list what would have been accepted, and generated documentation cannot be derived from the source of truth. The spellings are neither uniform nor guessable, which is exactly when a good message matters. windows spells it "amd64" where linux spells it "x86_64"; macOS spells it "arm64" where linux spells it "aarch64". Archs returns a copy, so a caller cannot widen what Compile accepts by mutating it, and TestArchs_ReturnsACopy pins that. TestArchs_AgreesWith- Validate pins the property that motivates exporting at all: every arch Archs reports compiles, and a value it does not report does not. Without that test the exported view could drift from validate(), which is the failure this replaces. OSes() ships alongside because a caller validating "os/arch" needs both halves and would otherwise hardcode the OS list instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#45) The platform walks run DOWNWARD from the declared version to a floor, so a Matcher answers "can the host I described run this wheel" -- correct for an installer. A mirror or an offline bundle asks a different question: "should a durable declaration of that host collect this wheel". The two diverge over time, because a set compiled once silently stops matching wheels built for platforms released since. Accepting a too-new platform costs bytes; rejecting one costs availability, and for an air-gapped mirror a wheel not collected is one the client cannot obtain at all. IsCompatibleOrNewer also accepts a same-family, same-arch platform tag whose version is ABOVE the declared one: manylinux/musllinux by libc version, macosx by macOS version. Windows platform tags carry no version axis, so there it degrades to IsCompatible. The interpreter and ABI axes are NOT relaxed. Matcher now retains the set of (Interpreter, ABI) pairs it accepts on any platform, and a candidate qualifies as newer only if its pair is already in that set. Without this a cp314 wheel would ride in on a newer platform tag. Covered for the plain case and for the abi3/abi3t substitution, where a free-threaded target must not gain abi3 wheels at any platform version. No rank is returned, deliberately: these tags fall outside the ordered set the Matcher generated, so there is no defensible priority for them. Legacy aliases are judged by the glibc version they name rather than by being old, so manylinux2014 is newer than a 2.12 declaration and merely compatible with a 2.28 one. I had asserted the opposite first and the test caught it; the rule is "requires more platform than declared", not "is recent". parseMacosPlatformTag is new because distro.go's parseLibcPlatformTag has no macOS counterpart. It is tested against the macosx_10_9_x86_64 shape that defeats a naive "_x86_64" suffix test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
) Compile requires a Libc for a linux Target and linuxPlatformTags emits manylinux XOR musllinux depending on it, so a caller expressing "linux x86_64, either libc" has to compile two Targets and union them by hand. Omitting the musl one is silent: every musllinux wheel stops matching with nothing to indicate that a whole wheel family was dropped. TestCompileAnyLibc_SingleFamilyDropsTheOther pins that failure so the helper's value is executable rather than argued in prose. The union is deduplicated, because the overlap is real: the bare linux_<arch> tag and the entire compatible tier are emitted by both families. glibc tiers rank ahead of musl ones, and Rank is part of the published contract, so that ordering is asserted. Two things worth review attention. muslMajor/muslMinor are stored on the Matcher separately from target.LibcMajor/Minor rather than reusing them. My first version reused them and TestCompileAnyLibc_NewerWorksForBothFamilies failed: comparing musllinux_2_0 against a glibc 2.28 floor reads as "older" and rejects the wheel. musl and glibc version numbers have no correspondence, and collapsing them is a category error. glibcToMusl does not try to invent a correspondence either. It returns the newest musl version this package generates, because musllinux tags are floors and the newest value therefore accepts the widest set. A caller needing an exact musl floor should compile that Target itself; the doc comment says so. t.Libc is ignored rather than honoured, so the call cannot half-apply, and TestCompileAnyLibc_IgnoresLibcField pins it. Validation is not bypassed: a bad arch or a missing libc version still returns ErrUnsupportedTarget. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jonyoder
added a commit
that referenced
this pull request
Aug 28, 2026
Dates [Unreleased] as 0.9.0 (2026-08-28) and adds its compare link. CHANGELOG only. A minor bump under the 0.x policy because two changes alter observable behaviour: reqtxt now recognizes --all-releases/--only-final/--use-feature (an unrecognized option was assumed boolean, so its argument became a fabricated requirement), and a standalone --hash line is accepted rather than rejected, matching pip. Contents since v0.8.0: tags manylinux floor fix (#43), marker EvaluateUndecidable/Variables (#47), tags IsCompatibleOrNewer/CompileAnyLibc/Archs (#48), reqtxt Source and pip-parity fixes (#49), dependency bumps (#50).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses #45, items 1-3. Item 4 is withdrawn — see below.
Three additive changes to
tags, one commit each.Matcher.IsCompatibleOrNewer(the one that earns its keep)The platform walks run downward from the declared version to a floor, so a
Matcheranswers "can the host I described run this wheel". A mirror or an offline bundle asks whether a durable declaration of that host should collect the wheel, and the two diverge over time: a set compiled once quietly stops matching wheels built for platforms released since. Accepting a too-new platform costs bytes; rejecting one costs availability, and for an air-gapped mirror a wheel not collected cannot be obtained at all.Consumers currently work around this by baking their own libc and macOS constants and adding a drift test that goes red whenever PyPI publishes a new platform tag — a test that fails for reasons unrelated to whatever change is under review. This removes the need for all three.
The interpreter and ABI axes are not relaxed.
Matchernow retains the set of(Interpreter, ABI)pairs it accepts on any platform, and a candidate qualifies as newer only if its pair is already in that set. Without it acp314wheel would ride in on a newer platform tag. Covered for the plain case and for theabi3/abi3tsubstitution, where a free-threaded target must not gainabi3wheels at any platform version.No rank is returned, deliberately: these tags fall outside the ordered set the
Matchergenerated, so there is no defensible priority.parseMacosPlatformTagis new, sincedistro.go'sparseLibcPlatformTaghas no macOS counterpart. It is tested against themacosx_10_9_x86_64shape that defeats a naive_x86_64suffix test.Two things the tests corrected while writing this, both worth a reviewer's eye:
manylinux2014is newer than a 2.12 declaration and merely compatible with a 2.28 one. I asserted the opposite first; the rule is "requires more platform than declared", not "is recent".muslMajor/muslMinorare stored separately fromtarget.LibcMajor/Minor. My first version reused them and a test failed: comparingmusllinux_2_0against a glibc 2.28 floor reads as "older" and rejects the wheel. musl and glibc version numbers have no correspondence.Target.CompileAnyLibcCompilerequires aLibcand platform tags are manylinux xor musllinux depending on it, so "linux x86_64, either libc" meant compiling two targets and unioning them by hand. Omitting the musl one is silent: everymusllinuxwheel stops matching with nothing to indicate a whole family was dropped.TestCompileAnyLibc_SingleFamilyDropsTheOtherpins that failure so the helper's value is executable rather than argued.The union is deduplicated, because the overlap is real: the bare
linux_<arch>tag and the entire compatible tier are emitted by both families.glibcToMusldoes not invent a glibc/musl correspondence; it returns the newest musl version this package generates, becausemusllinuxtags are floors so the newest value accepts the widest set. A caller needing an exact musl floor should compile that target itself, and the doc says so.Archs(os)andOSes()Previously package-private, so a caller validating an operator-supplied
os/archstring could reject a value but not report what it would have accepted, and generated documentation could not derive from the source of truth. The spellings are neither uniform nor guessable: windows usesamd64where linux usesx86_64, macOS usesarm64where linux usesaarch64.Returns copies.
TestArchs_ReturnsACopypins that a caller cannot widen whatCompileaccepts, andTestArchs_AgreesWithValidatepins the property that motivates exporting at all: every arch reported compiles, and a value not reported does not.Item 4 withdrawn: PyPy targets without an implementation version
The issue proposed rejecting
Implementation: "pp"with noImplMajor/ImplMinor, on the grounds that such a target silently collects no PyPy extension wheels.I implemented it and three tests failed, one of them
TestGolden/pp310_noabi_windows_amd64.json— a fixture generated bytestdata/gen_goldens.pyfrom pypa/packaging, recording that packaging produces exactlypp310-none-win_amd64plus the compatible tier for that input. So the current behaviour is faithful to packaging, documented inTarget's own doc comment, and pinned by both that fixture andTestPyPy_UnknownImplVersion.Rejecting the input would therefore be a deliberate divergence from packaging and would delete a golden fixture, which is the opposite of this package's design posture. The behaviour is correct; the hazard belongs to the consumer, which should require an implementation version in its own declaration syntax rather than passing an under-specified target. Reverted, and I'll note it on the issue.
Verification
gofmtclean,go vet ./...clean, full module suite green including the packaging-generated golden fixtures.🤖 Generated with Claude Code