repomatic.release.binary module¶
Binary build targets and verification utilities.
Defines the Nuitka compilation targets for all supported platforms and provides native binary verification: architecture and minimum-OS floors are parsed straight from the executables’ ELF, Mach-O and PE headers, so no external tool is needed on runners or inside build containers.
- repomatic.release.binary.BINARY_ASSET_SUFFIXES = ('.bin', '.exe')¶
File extensions identifying compiled binaries among release assets.
The one definition of “a compiled release asset”:
scan-virustotaluploads this set, the release workflow downloads it (--patternflags in_release-engine.yaml),docs/binaries.mdlists it, and the dev-release asset globs derive from it.
- repomatic.release.binary.PYTHON_DIST_SUFFIXES = ('.tar.gz', '.whl')¶
File extensions identifying Python distributions among release assets.
The counterpart of
BINARY_ASSET_SUFFIXES, and the same kind of single definition:pack_binary_assets()excludes this set from the binary upload list (create-releasealready attached those), and the dev-release asset globs add it on top of the compiled binaries.
- repomatic.release.binary.MACHO_MAGIC_64: Final[int] = 4277009103¶
Magic of a 64-bit Mach-O header, in the file’s own (little) endianness.
- repomatic.release.binary.MACHO_FAT_MAGICS: Final[frozenset[int]] = frozenset({3405691582, 3405691583})¶
Big-endian magics of universal (fat) Mach-O containers, 32- and 64-bit.
- class repomatic.release.binary.BinaryFormat(label, extension, floor_label)[source]¶
Bases:
EnumExecutable container format a compiled binary uses.
Each member owns what varies by format: the name messages print, the file extension its executables take, what a target’s floor measures, and the header parser
read_info()dispatches to. The floor label doubles as the enforceability flag, since a format recording no measurable floor has nothing to label.Note
Members carry no magic bytes of their own:
detect()needs to read them in a fixed order (a fat Mach-O and a PE both fail an equality test on the first four bytes), so the probe stays one method rather than a table.- ELF = ('elf', 'bin', 'glibc')¶
- MACHO = ('macho', 'bin', 'macOS')¶
- PE = ('pe', 'exe', None)¶
- label¶
Lowercase format name, as printed in verification messages.
- extension¶
File extension executables of this format take.
- floor_label¶
What a target’s floor measures for this format, or
None.Nonemarks a format whose headers record nothing a scan can check, soBuildTarget.enforced_floorreports no floor to enforce. Only PE is in that case: its version headers are nominal, and the Windows floor is CPython’s own support policy, not a linker artifact.
- read_info(path)[source]¶
Parse a binary’s headers into its machine set and measured floor.
The machine element type follows the format (ELF machine names, Mach-O CPU type integers, the PE machine id or
Nonewhen unparsable), matching whatARCH_MACHINESrecords as the expectation. The floor is the measured requirementverify_binary_floor()compares against the declared one: alwaysNoneon PE, whose headers record nothing enforceable.
- repomatic.release.binary.PLATFORM_FORMATS: Final[dict[Platform | Group, BinaryFormat]] = {Group(id='linux', name='Linux distributions'): BinaryFormat.ELF, Platform(id='macos', name='macOS'): BinaryFormat.MACHO, Platform(id='windows', name='Windows'): BinaryFormat.PE}¶
Executable format each build platform compiles to.
Read through
BuildTarget.binary_format, which is how every caller reaches it.
- repomatic.release.binary.MACHINE_IDS: Final[dict[tuple[BinaryFormat, Architecture], str | int]] = {(BinaryFormat.ELF, Architecture(id='aarch64', name='ARM64 (AArch64)')): 'EM_AARCH64', (BinaryFormat.ELF, Architecture(id='x86_64', name='x86-64 (AMD64)')): 'EM_X86_64', (BinaryFormat.MACHO, Architecture(id='aarch64', name='ARM64 (AArch64)')): 16777228, (BinaryFormat.MACHO, Architecture(id='x86_64', name='x86-64 (AMD64)')): 16777223, (BinaryFormat.PE, Architecture(id='aarch64', name='ARM64 (AArch64)')): 43620, (BinaryFormat.PE, Architecture(id='x86_64', name='x86-64 (AMD64)')): 34404}¶
Machine identifier a binary’s header carries, per format and architecture.
One table rather than three, keyed the way
PlatformKeykeys the tool registry: the value is a pyelftools machine name on ELF and a raw header integer on Mach-O and PE, so a caller compares it against whatever the matching parser returns.
- class repomatic.release.binary.BuildTarget(id, runner, platform, arch, floor, container=None)[source]¶
Bases:
objectOne Nuitka compile target: a runner image, a platform and an architecture.
Carries the metadata every consumer branches on, and the methods that interpret it, so a caller asks the target what its binaries must look like instead of re-deriving it from a platform name.
Platform and architecture are Extra Platforms traits rather than free strings, which is the same vocabulary
PlatformKeykeys the downloaded-binary registry on.as_matrix_entry()renders both back to their ids for the workflow matrix.Every field is irreducible: anything a format decides (the file extension, what the floor measures, which header field to read) lives on
BinaryFormatinstead.- id: str¶
Short target identifier, chosen for user-friendliness.
It names the published release asset, so it must stay stable: download URLs,
docs/install.mdand thebinaries.csvcatalog all match on it.Note
It is deliberately not derived from
platformandarch, even though all six targets currently read{platform}-{short arch}. The asset names froze on the shortx64andarm64spellings while those fields carry the canonical extra-platforms ids, and a future target splitting an existing pair (a musl Linux, say) would need a name the derivation cannot produce.tests/test_binary.pypins the pairing.
- runner: str¶
Runner image, as named in GitHub-hosted runners.
Named for what it holds, not for the
osmatrix key it renders to:ubuntu-26.04-armis an image, andplatformis the operating system.Hint
One compile job per target, each on one of the six runners the test matrix already covers, so a published binary is built on an image the suite is validated against. The targets are exactly
KNOWN_RUNNERS, not a separate selection: an image is added here by widening the test axes, never on its own.
- platform: Platform | Group¶
Operating system the binary runs on.
- arch: Architecture¶
CPU architecture the binary is compiled for.
- floor: str¶
Oldest runtime the binary is built to run on.
What the version counts is the format’s business, named by
BinaryFormat.floor_label: a glibc symbol version on ELF, a macOS deployment target on Mach-O, a Windows release on PE. The first two are measured out of the compiled files byverify_binary_floor(); the third is documentation, since PE version headers record nothing to measure.On macOS the release workflow also exports it as
MACOSX_DEPLOYMENT_TARGETat compile time. Without it, compiled objects and processed dylibs inherit the build runner’s own macOS version.
- container: str | None = None¶
OCI image the Linux compile and self-test jobs run in.
Passed to the
container:key of the release workflow. Compiling insidemanylinux_2_28caps the toolchain at glibc 2.28, so binaries stop inheriting the floor of whatever glibc the current runner image ships.Declared per target rather than derived from
platform, which would assume every Linux target is a manylinux one. Absent on macOS and Windows: GitHub Actions containers do not exist for those runners.
- property binary_format: BinaryFormat¶
Executable format the compiler emits for this target.
- property expected_machine: str | int¶
Machine identifier this target’s binaries carry in their header.
- as_matrix_entry()[source]¶
Flat, JSON-safe mapping of this target, for GitHub matrix inclusion.
Renders
platformandarchback to their extra-platforms ids, the spelling the workflow expressions (matrix.platform_id) andtests.yaml’s runner check compare against, andrunnerback to theoskeyruns-on:reads. A target with no container omits the key, so an entry carries only what applies to it.
- repomatic.release.binary.NUITKA_BUILD_TARGETS: Final[dict[str, BuildTarget]] = {'linux-arm64': BuildTarget(id='linux-arm64', runner='ubuntu-26.04-arm', platform=Group(id='linux', name='Linux distributions'), arch=Architecture(id='aarch64', name='ARM64 (AArch64)'), floor='2.28', container='quay.io/pypa/manylinux_2_28_aarch64@sha256:e7035406e58d96b7407246af1f6514a3cbd753a0025b42b9adfbeadd3b29ba80'), 'linux-x64': BuildTarget(id='linux-x64', runner='ubuntu-26.04', platform=Group(id='linux', name='Linux distributions'), arch=Architecture(id='x86_64', name='x86-64 (AMD64)'), floor='2.28', container='quay.io/pypa/manylinux_2_28_x86_64@sha256:fdb9a9c223b215604dc7b6f7e8fff4b39bfea5fbaa7777a2e5544a60dfa437f8'), 'macos-arm64': BuildTarget(id='macos-arm64', runner='macos-26', platform=Platform(id='macos', name='macOS'), arch=Architecture(id='aarch64', name='ARM64 (AArch64)'), floor='11.0', container=None), 'macos-x64': BuildTarget(id='macos-x64', runner='macos-26-intel', platform=Platform(id='macos', name='macOS'), arch=Architecture(id='x86_64', name='x86-64 (AMD64)'), floor='10.15', container=None), 'windows-arm64': BuildTarget(id='windows-arm64', runner='windows-11-arm', platform=Platform(id='windows', name='Windows'), arch=Architecture(id='aarch64', name='ARM64 (AArch64)'), floor='11', container=None), 'windows-x64': BuildTarget(id='windows-x64', runner='windows-2025', platform=Platform(id='windows', name='Windows'), arch=Architecture(id='x86_64', name='x86-64 (AMD64)'), floor='10', container=None)}¶
GitHub-hosted runner matrix for Nuitka builds, keyed by target name.
The roster is closed: every entry compiles on a runner the test matrix already covers, and the key doubles as the compiled binary’s published identifier. See
BuildTargetfor what each field means and which of them are frozen.
- repomatic.release.binary.FLAT_BUILD_TARGETS: Final[list[dict[str, str]]] = [{'arch': 'aarch64', 'container': 'quay.io/pypa/manylinux_2_28_aarch64@sha256:e7035406e58d96b7407246af1f6514a3cbd753a0025b42b9adfbeadd3b29ba80', 'extension': 'bin', 'floor': '2.28', 'os': 'ubuntu-26.04-arm', 'platform_id': 'linux', 'target': 'linux-arm64'}, {'arch': 'x86_64', 'container': 'quay.io/pypa/manylinux_2_28_x86_64@sha256:fdb9a9c223b215604dc7b6f7e8fff4b39bfea5fbaa7777a2e5544a60dfa437f8', 'extension': 'bin', 'floor': '2.28', 'os': 'ubuntu-26.04', 'platform_id': 'linux', 'target': 'linux-x64'}, {'arch': 'aarch64', 'extension': 'bin', 'floor': '11.0', 'os': 'macos-26', 'platform_id': 'macos', 'target': 'macos-arm64'}, {'arch': 'x86_64', 'extension': 'bin', 'floor': '10.15', 'os': 'macos-26-intel', 'platform_id': 'macos', 'target': 'macos-x64'}, {'arch': 'aarch64', 'extension': 'exe', 'floor': '11', 'os': 'windows-11-arm', 'platform_id': 'windows', 'target': 'windows-arm64'}, {'arch': 'x86_64', 'extension': 'exe', 'floor': '10', 'os': 'windows-2025', 'platform_id': 'windows', 'target': 'windows-x64'}]¶
List of build targets in a flat format, suitable for matrix inclusion.
- repomatic.release.binary.binary_name(package, target, version=None)[source]¶
Compose a compiled binary’s release-asset filename.
The one definition of the naming convention:
{package}-{version}-{target}.{ext}for the versioned upload, and with no version the stable alias ({package}-{target}.{ext}) backing thereleases/latest/downloadURLs. The extension comes fromNUITKA_BUILD_TARGETS.- Return type:
- repomatic.release.binary.versionless_alias(filename, version)[source]¶
Map a versioned binary filename to its stable alias, or
None.Strips the
-{version}-segment (papaya-1.2.3-linux-arm64.binbecomespapaya-linux-arm64.bin). ReturnsNonefor filenames that carry no such segment or are not compiled binaries, so callers can filter and map in one pass.
- repomatic.release.binary.binary_filename_re(package)[source]¶
Match a package binary filename, versioned or versionless.
Captures
targetandext, both alternations derived fromNUITKA_BUILD_TARGETSso a new build target extends the pattern without anyone editing a regex. The release freeze rewrites both spellings onto the versioned form through this;tests/test_platform_keys.pypins the pattern against every target.
- repomatic.release.binary.pack_binary_assets(dist_dir, version)[source]¶
Pack a release’s upload list, materializing the versionless aliases.
Mirrors what the release engine’s upload step needs: every file in dist_dir except the Python distributions (
create-releasealready uploaded those), plus a byte-identical versionless alias copied beside each versioned binary so the stablereleases/latest/downloadURLs always resolve. Aliases share their sibling’s digest, which is what lets artifact attestations verify them unchanged and the binaries catalog collapse them (seebinaries_page._binary_assets).Idempotent: re-running overwrites the same aliases with the same bytes.
- repomatic.release.binary.BINARY_AFFECTING_PATHS: Final[tuple[str, ...]] = ('.github/workflows/_release-engine.yaml', '.github/workflows/release.yaml', 'pyproject.toml', 'tests/', 'uv.lock')¶
Path prefixes that always affect compiled binaries, regardless of the project.
Project-specific source directories (derived from
[project.scripts]inpyproject.toml) are added dynamically bybinary_affecting_paths.The release workflow entries cover both layouts: upstream keeps the
_release-engine.yamllane (which defines the Nuitka compile and binary self-test jobs) in-repo, while downstream repos call the engine cross-repo from their generatedrelease.yaml, so a pin bump there rightly triggers a rebuild.
- repomatic.release.binary.SKIP_BINARY_BUILD_BRANCHES: Final[frozenset[str]] = frozenset({'format-images', 'format-json', 'format-markdown', 'format-shell', 'sync-gitignore', 'sync-mailmap', 'update-dep-graph'})¶
Autofix branches whose changes cannot affect compiled binaries.
Members are PR branch names produced by autofix jobs that touch only repository housekeeping (
.mailmap,.gitignore, JSON, Markdown, images, shell scripts, dependency graph). The binary output is unchanged, soskip_binary_buildreturnsTruewhen the PR head branch matches a member, saving an expensive Nuitka compilation.Note
This set is intentionally disjoint from
repomatic.git_ops.VERSION_BUMP_BRANCHES: version-bump branches do change binaries (they rewrite the version string baked into the build), so they belong to a different policy.
- repomatic.release.binary.LC_VERSION_MIN_MACOSX: Final[int] = 36¶
Mach-O load command carrying the minimum macOS version (pre-10.14 SDKs).
- repomatic.release.binary.LC_BUILD_VERSION: Final[int] = 50¶
Mach-O load command carrying the platform and minimum OS (10.14+ SDKs).
- repomatic.release.binary.MACHO_PLATFORM_MACOS: Final[int] = 1¶
platformfield value naming macOS inside anLC_BUILD_VERSIONcommand.
- repomatic.release.binary.verify_binary_arch(target, binary_path)[source]¶
Verify that a binary matches the expected architecture for a target.
Parses the executable’s own headers, so it needs no external tool and behaves identically on runner VMs and inside build containers.
- Parameters:
- Raises:
ValueError – If target is unknown.
ValueError – If binary format or architecture does not match.
- Return type:
- repomatic.release.binary.verify_binary_floor(target, binary_path, dist_dirs=())[source]¶
Verify the binary and its dist tree stay within the target’s OS floor.
Scans the onefile binary itself plus every native library of the given Nuitka dist directories (whose content the onefile payload repacks), and compares each file’s measured requirement to the target’s declared floor:
What the floor counts comes from the format, per
BinaryFormat.floor_label:ELF: the highest
GLIBC_x.yversion requirement of each file. A higher requirement means a compiled object picked up symbols newer than the build container provides for, and the binary would die at load time on the distributions the floor promises.Mach-O: the
minosof each file, against the deployment target the build exports asMACOSX_DEPLOYMENT_TARGET.PE: nothing. Its version headers are nominal, so
BuildTarget.enforced_floorreports no floor and this returns early; the Windows floor is CPython’s own support policy, tracked in the docs.
- Parameters:
- Raises:
ValueError – If target is unknown.
ValueError – If any scanned file exceeds the declared floor.
- Return type: