From 3649b4606de6760ea38855afc6cc6298a3829526 Mon Sep 17 00:00:00 2001 From: Norbert Orzechowicz Date: Wed, 29 Jul 2026 21:18:15 +0200 Subject: [PATCH] feat(flow-php/flow-php-ext): publish musl binaries for Alpin - build extension in Alpine PHP containers via apk toolchain - verify artifact loads in a clean runtime with libgcc only - gate release publishing on musl assets - document libgcc prerequisite for Alpine installs --- .../installation/packages/flow-php-ext.md | 24 +++- .../.github/workflows/release.yml | 113 +++++++++++++++++- 2 files changed, 134 insertions(+), 3 deletions(-) diff --git a/documentation/installation/packages/flow-php-ext.md b/documentation/installation/packages/flow-php-ext.md index c5905dcba..1f88be1c0 100644 --- a/documentation/installation/packages/flow-php-ext.md +++ b/documentation/installation/packages/flow-php-ext.md @@ -21,8 +21,8 @@ Binary names follow the [PIE naming convention](https://github.com/php/pie/blob/ Available platforms: - macOS ARM64 (Apple Silicon) -- Linux ARM64 -- Linux x86_64 +- Linux ARM64 — glibc (Debian, Ubuntu, …) and musl (Alpine) +- Linux x86_64 — glibc (Debian, Ubuntu, …) and musl (Alpine) PHP versions: 8.3, 8.4, 8.5 (each with NTS and ZTS variants). @@ -52,6 +52,26 @@ php -m | grep flow_php pie install flow-php/flow-php-ext ``` +### Alpine Linux (musl) + +Alpine images need `libgcc` before the extension will load: + +```bash +apk add --no-cache libgcc +pie install flow-php/flow-php-ext +``` + +`libgcc` (129 KiB) provides `libgcc_s.so.1`, which the Rust runtime links for stack unwinding. Debian and Ubuntu +ship it in the base system; Alpine does not. Without it PHP reports: + +``` +Unable to load dynamic library '.../flow_php.so' + (Error loading shared library libgcc_s.so.1: No such file or directory) +``` + +Installing `libgcc` is still dramatically cheaper than the alternative — without a musl binary PIE falls back to a +source build, which pulls the full Rust toolchain (`rust cargo clang-dev`, ~824 MB) into the image. + ## Build Prerequisites The Flow PHP extension is written in Rust and requires the following tools to compile: diff --git a/src/extension/flow-php-ext/.github/workflows/release.yml b/src/extension/flow-php-ext/.github/workflows/release.yml index 1cbd327f7..4e48ef0f1 100644 --- a/src/extension/flow-php-ext/.github/workflows/release.yml +++ b/src/extension/flow-php-ext/.github/workflows/release.yml @@ -152,6 +152,117 @@ jobs: --repo "$REPO" \ --clobber + build-linux-musl: + name: "Linux musl ${{ matrix.arch }} - PHP ${{ matrix.php }} - ${{ matrix.ts }}" + needs: create-release + runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} + permissions: + contents: write + strategy: + fail-fast: false + matrix: + php: ['8.3', '8.4', '8.5'] + arch: ['x86_64', 'arm64'] + ts: ['nts', 'zts'] + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.create-release.outputs.tag }} + persist-credentials: false + + - name: Set build variables + id: build-vars + env: + TS: ${{ matrix.ts }} + PHP: ${{ matrix.php }} + run: | + if [ "$TS" = "zts" ]; then + echo "php-image=${PHP}-zts-alpine" >> "$GITHUB_OUTPUT" + echo "ts-suffix=-zts" >> "$GITHUB_OUTPUT" + else + echo "php-image=${PHP}-cli-alpine" >> "$GITHUB_OUTPUT" + echo "ts-suffix=" >> "$GITHUB_OUTPUT" + fi + + - name: Create output directory + run: mkdir -p dist + + - name: Build in Docker container + env: + WORKSPACE: ${{ github.workspace }} + FLOW_PHP_EXT_VERSION: ${{ needs.create-release.outputs.tag }} + PHP_IMAGE: ${{ steps.build-vars.outputs.php-image }} + run: | + docker run --rm \ + -v "$WORKSPACE:/workspace" \ + -w /workspace \ + -e FLOW_PHP_EXT_VERSION="$FLOW_PHP_EXT_VERSION" \ + "php:$PHP_IMAGE" \ + sh -c ' + set -eu + + echo "::group::Install system dependencies" + apk add --no-cache build-base clang-dev rust cargo + echo "::endgroup::" + + echo "::group::Build extension" + cargo build --release + echo "::endgroup::" + + echo "::group::Package artifact" + cp /workspace/target/release/libflow_php.so /workspace/dist/flow_php.so + echo "::endgroup::" + ' + + - name: Create ZIP archive + env: + TAG: ${{ needs.create-release.outputs.tag }} + PHP: ${{ matrix.php }} + ARCH: ${{ matrix.arch }} + TS_SUFFIX: ${{ steps.build-vars.outputs.ts-suffix }} + run: | + cd dist + ARTIFACT_NAME="php_${EXTENSION_NAME}-${TAG}_php${PHP}-${ARCH}-linux-musl${TS_SUFFIX}.zip" + zip "$ARTIFACT_NAME" flow_php.so + echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> "$GITHUB_ENV" + echo "ARTIFACT_PATH=dist/$ARTIFACT_NAME" >> "$GITHUB_ENV" + + - name: Verify in clean runtime container + env: + WORKSPACE: ${{ github.workspace }} + PHP_IMAGE: ${{ steps.build-vars.outputs.php-image }} + run: | + docker run --rm \ + -v "$WORKSPACE/dist:/dist:ro" \ + -e ARTIFACT_NAME="$ARTIFACT_NAME" \ + "php:$PHP_IMAGE" \ + sh -c ' + set -eu + apk add --no-cache libgcc + if command -v cargo >/dev/null 2>&1; then + echo "ERROR: build toolchain present in clean room, verification is meaningless" + exit 1 + fi + unzip -o "/dist/$ARTIFACT_NAME" -d /tmp + php -n -d extension=/tmp/flow_php.so -m | grep -q flow_php || { + echo "ERROR: flow_php extension failed to load in a clean runtime" + exit 1 + } + echo "flow_php extension loaded successfully in clean runtime" + ' + + - name: Upload to release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ needs.create-release.outputs.tag }} + REPO: ${{ github.repository }} + run: | + gh release upload "$TAG" \ + "$ARTIFACT_PATH" \ + --repo "$REPO" \ + --clobber + build-macos: name: "macOS arm64 - PHP ${{ matrix.php }} - ${{ matrix.ts }}" needs: create-release @@ -234,7 +345,7 @@ jobs: publish-release: name: Publish Release - needs: [create-release, build-linux, build-macos] + needs: [create-release, build-linux, build-linux-musl, build-macos] runs-on: ubuntu-latest permissions: contents: write