Skip to content

Build FrankenPHP

Build FrankenPHP #14

name: Build FrankenPHP
# Builds custom static FrankenPHP binaries with the full extension set Burd
# needs (redis, mongodb, imagick, memcached, intl, …) that the official builds
# omit. Each build is a self-contained static binary published to a per-PHP-line
# GitHub Release with a checksum.
#
# These builds take ~45-60 min each, so this does NOT run on push — trigger it
# manually or let the monthly schedule refresh them.
on:
workflow_dispatch:
inputs:
php:
description: "PHP line to build ('all', or 8.3 / 8.4 / 8.5)"
type: string
default: "all"
arch:
description: "Architecture ('both', arm64, x86_64)"
type: string
default: "both"
schedule:
# Monthly: pick up new FrankenPHP / PHP patch releases.
- cron: "0 4 1 * *"
permissions:
contents: write
jobs:
matrix:
runs-on: ubuntu-latest
outputs:
combos: ${{ steps.gen.outputs.combos }}
steps:
- uses: actions/checkout@v4
- id: gen
env:
PHP_IN: ${{ inputs.php || 'all' }}
ARCH_IN: ${{ inputs.arch || 'both' }}
run: |
phps='["8.3","8.4","8.5"]'
[ "$PHP_IN" != "all" ] && [ -n "$PHP_IN" ] && phps="[\"$PHP_IN\"]"
arches='[{"arch":"arm64","runner":"macos-14"},{"arch":"x86_64","runner":"macos-15-intel"}]'
[ "$ARCH_IN" = "arm64" ] && arches='[{"arch":"arm64","runner":"macos-14"}]'
[ "$ARCH_IN" = "x86_64" ] && arches='[{"arch":"x86_64","runner":"macos-15-intel"}]'
combos=$(jq -cn --argjson p "$phps" --argjson a "$arches" \
'[$p[] as $php | $a[] | {php:$php, arch:.arch, runner:.runner}]')
echo "combos=$combos" >> "$GITHUB_OUTPUT"
build:
needs: matrix
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(needs.matrix.outputs.combos) }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: "1.26"
# static-php-cli / FrankenPHP's build tooling needs a host PHP + composer.
# Use 8.4 — its composer.lock isn't satisfiable on older host PHP.
- name: Set up host PHP + composer
uses: shivammathur/setup-php@v2
with:
php-version: "8.4"
tools: composer
- name: Install build dependencies
run: |
brew update
brew install pkg-config autoconf automake libtool bison re2c
- name: Build FrankenPHP (${{ matrix.php }}, ${{ matrix.arch }})
working-directory: frankenphp
# GITHUB_TOKEN authenticates static-php-cli's GitHub API source
# downloads — without it they hit the unauthenticated rate limit (403).
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./scripts/build-frankenphp.sh "${{ matrix.php }}" "${{ matrix.arch }}" darwin
# On a compile failure, surface static-php-cli's detailed log so the
# offending extension/error is visible without a --debug rebuild.
- name: Dump build log on failure
if: failure()
run: |
base="frankenphp/frankenphp/dist/static-php-cli/log"
# spc.shell.log holds the raw compiler/make output — grep it for the
# actual "error:" lines that identify the offending extension.
if [ -f "$base/spc.shell.log" ]; then
echo "===== compiler errors (spc.shell.log) ====="
grep -iE "error:|Error [0-9]|undefined|no member|has no member|conflicting|incompatible|In file included" "$base/spc.shell.log" | tail -60 || true
echo "===== spc.shell.log tail ====="
tail -80 "$base/spc.shell.log"
fi
if [ -f "$base/spc.output.log" ]; then
echo "===== spc.output.log tail ====="
tail -40 "$base/spc.output.log"
fi
- name: Package + checksum
id: pkg
working-directory: frankenphp
run: |
set -euo pipefail
bin="php-web-${{ matrix.php }}-darwin-${{ matrix.arch }}"
# Extract the FrankenPHP version from the binary and combine it with
# the PHP line, so the published version reads e.g. "8.4-1.12.4"
# instead of just the FrankenPHP "1.12.4" — the PHP line is what users
# care about when picking a version.
fp_ver="$(./"$bin" version 2>/dev/null | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -1 | tr -d v)"
if [ -z "$fp_ver" ]; then
echo "Could not determine FrankenPHP version from binary" >&2
exit 1
fi
combined="${{ matrix.php }}-${fp_ver}"
asset="frankenphp-${combined}-${{ matrix.arch }}"
cp "$bin" "$asset"
shasum -a 256 "$asset" | cut -d' ' -f1 > "$asset.sha256"
echo "asset=$asset" >> "$GITHUB_OUTPUT"
echo "combined=$combined" >> "$GITHUB_OUTPUT"
# Tag per combined version so the release name matches the version
# string, giving services.json a clean {version} URL substitution:
# .../releases/download/frankenphp-<version>/frankenphp-<version>-<arch>
echo "tag=frankenphp-${combined}" >> "$GITHUB_OUTPUT"
- name: Publish to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: frankenphp
run: |
set -euo pipefail
TAG="${{ steps.pkg.outputs.tag }}"
if ! gh release view "$TAG" >/dev/null 2>&1; then
gh release create "$TAG" \
--title "FrankenPHP (PHP ${{ matrix.php }})" \
--notes "Custom static FrankenPHP for PHP ${{ matrix.php }} with Burd's full extension set (redis, mongodb, imagick, memcached, intl, and more). Versions are published as <php>-<frankenphp>, e.g. ${{ steps.pkg.outputs.combined }}." \
|| true
fi
gh release upload "$TAG" \
"${{ steps.pkg.outputs.asset }}" \
"${{ steps.pkg.outputs.asset }}.sha256" \
--clobber