Skip to content

STF-1287: Build and publish PIE precompiled binaries on tag - #2

Draft
claude[bot] wants to merge 8 commits into
mainfrom
stf-1287-pie-release-workflow
Draft

STF-1287: Build and publish PIE precompiled binaries on tag#2
claude[bot] wants to merge 8 commits into
mainfrom
stf-1287-pie-release-workflow

Conversation

@claude

@claude claude Bot commented Jul 31, 2026

Copy link
Copy Markdown

Requested by Greg Oschwald · Slack thread

Today pie install maxmind-db/reader-ext always compiles from source, so a user
needs a toolchain and libmaxminddb-dev headers. After this, PIE downloads a
prebuilt maxminddb.so on Linux (x86_64/arm64) and macOS (arm64) for PHP
8.2–8.5, NTS and ZTS, plus Windows — and still falls back to building from
source everywhere else, unchanged.

The one line that enables it is "download-url-method": ["pre-packaged-binary", "pre-packaged-source"]. PIE tries the methods in order, so an uncovered
platform misses the binary and falls through to the tarball.

.github/workflows/release.yml runs on v* tags (or workflow_dispatch with a
tag input, to re-cut assets without re-tagging). It stages everything onto a
draft release — assets cannot be added to a published, immutable release — and
only un-drafts at the end. Each Linux/macOS build is gated before it is staged:
no libmaxminddb in NEEDED, no RUNPATH/RPATH, no undefined MMDB_ symbols, a
measured glibc floor within the documented maximum, and a load-and-query of a
real database in a clean container.

The source tarball PIE falls back to is built from an explicit member list: the
extension's own sources and LICENSE, plus, once ext/libmaxminddb exists,
only the libmaxminddb sources config.m4 compiles, the headers it puts on the
include path, and that library's LICENSE and NOTICE, which Apache-2.0 §4(d)
requires a redistribution to carry. A bare glob would publish the vendored
checkout's gitfile, CI configuration, test suite and nested test-data
submodules — 101 entries where 16 are needed — and the verification step now
asserts that. The layout stays flat, with config.m4 at the archive root,
because that is where PIE looks; the extension's LICENSE lives a level above
ext/, so tar appends it with -C .. and it lands flat there too.

Every binary lane builds from the submodule tree, so a source-build job
builds the staged tarball itself: extracted into an empty directory, in a
container with only that directory mounted and no system libmaxminddb to stand
in for a source the tarball failed to ship, then loaded for a real lookup.
Without it, a libmaxminddb reshuffle would break source-installing users while
every other gate stayed green. It runs alongside the binary lanes, and
publish waits for it.

publish asserts the exact expected asset count and refuses to publish if it
does not match. That assertion is deliberate: the Python repo once shipped a
release with an sdist and zero wheels because an artifact glob silently matched
nothing.

The build, gate, load-and-query and staging shell is shared by both lanes from
dev-bin/, rather than duplicated per-OS in the workflow, so there is one copy
of each and it can be run outside Actions.

Linux binaries are built in digest-pinned php:{version}-{cli,zts}-bookworm
containers rather than on the runner, so the glibc floor (2.36) is a reviewable
choice that cannot drift with runner images. The 2.38 floor reported for the
bundled sources is what building outside those containers costs: glibc 2.38
headers redirect the strtol libmaxminddb calls to
__isoc23_strtol@GLIBC_2.38 and PHP compiles with -D_GNU_SOURCE, which
enables the redirect. Inside bookworm that symbol does not exist, and the gate
fails closed if a build ever escapes. arm64 comes from a native
ubuntu-24.04-arm runner, not qemu. macOS has no container equivalent, so the
floor there is a pinned MACOSX_DEPLOYMENT_TARGET, asserted against
LC_BUILD_VERSION.

Dependencies, both in maxmind/MaxMind-DB-Reader-php:

  • The Linux and macOS lanes need the --with-maxminddb-bundled configure flag
    (PR in flight, branch stf-1287-…), which compiles libmaxminddb's vendored
    sources into the extension so the .so needs only libc. Until the submodule
    points at a tag that has it, those jobs fail fast with an explicit message.
  • The Windows lane needs #263, which makes config.w32 accept the lib-prefixed
    name that PHP's libmaxminddb-1.5.0-vs16-*.zip actually ships. Windows keeps
    the system-library path and does not use the bundled flag.

dev-bin/release.sh there must also stop building the tarball and creating the
release itself and just push the tag, or the two will collide. That is a
follow-up PR in the other repo, not this one.

This workflow is unexercised — it cannot run until a tag is pushed. The
cheapest way to smoke it before relying on it is a workflow_dispatch run
against an existing tag. What was verified statically: actionlint and zizmor
(auditor persona) are both clean; every pinned action SHA was checked against
the tag named in its comment; composer.json is valid JSON and the php-ext
block matches PIE's documented schema; all eight pinned image digests were
confirmed to exist as multi-arch indexes with amd64 and arm64; the tarball was
reproduced in both submodule states, before and after the bundled-sources PR,
with LICENSE asserted at the archive root in each, and the source-build job
was run against both — building and querying in the post-PR state, skipping
with a warning in today's, and failing as intended when a header is withheld
from the tarball; the glibc claim was measured rather than assumed, compiling
libmaxminddb with and without -D_GNU_SOURCE (strtol@GLIBC_2.2.5 versus
__isoc23_strtol@GLIBC_2.38, the only symbol in the built object above 2.36)
and against a bookworm glibc 2.36 sysroot, where the floor drops to 2.33; and
the asset-count assertion (including the zero-binaries case), the tag
sanitisation, the staging cross-checks and the lookup assertion were each
exercised locally, negative cases included.


Generated by Claude Code

claude added 3 commits July 31, 2026 22:45
Once ext/libmaxminddb exists, a bare glob sweeps the whole vendored
checkout -- its gitfile, CI configuration, test suite and nested
test-data submodules -- into a published asset, so list the members
explicitly and assert the layout.
The Linux and macOS lanes ran near-identical build, gate, verify and
staging shell as per-OS heredocs. Sharing one copy of each cuts the
workflow by a third and makes them runnable outside Actions.
claude added 5 commits July 31, 2026 23:15
Apache-2.0 4(d) requires a redistribution to carry the NOTICE content
when the original work publishes one, and libmaxminddb does: a single
NOTICE at its repository root, present at the pinned 1.13.3.
The one existing release is tagged v1.13.1 and titled 1.13.1, matching
what dev-bin/release.sh passed. Asset filenames still use the tag
verbatim, since PIE matches on Composer's v-prefixed pretty version.
Every binary lane builds from the submodule tree, so a libmaxminddb
reshuffle -- headers moved into a subdirectory, a new source file added
-- would produce a tarball that fails only for source-installing users
while every gate stayed green. This builds what those users download,
in a container where the extraction directory is the only thing mounted
and no system libmaxminddb can stand in for a source the tarball failed
to ship.

publish waits for it so a tarball that cannot be built is not published.
The tarball carries no libmaxminddb sources until ext/libmaxminddb is
part of the submodule tree, so until then the build is skipped with a
warning rather than failed.
The tarball is built from inside ext/, so it carried libmaxminddb's
LICENSE and NOTICE but not the extension's own licence, which lives a
level up at the repository root. Append it with tar's -C so it lands
flat at the archive root next to config.m4.
The bundled sources measure a 2.38 floor when built on Ubuntu 24.04,
which reads as a contradiction of the 2.36 gate. It is not: glibc 2.38
headers redirect strtol to __isoc23_strtol and PHP's -D_GNU_SOURCE turns
that redirect on, so the number to keep is bookworm's, not the number a
failing gate reports.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant