Skip to content

Build PHP 8.3.31

Build PHP 8.3.31 #146

Workflow file for this run

name: Build PHP SDK
run-name: "Build PHP ${{ inputs.php_version }}"
on:
workflow_dispatch:
inputs:
php_version:
description: "PHP version to build (e.g. 8.5.2, 8.4.7, 8.3.20)"
required: true
type: string
platform:
description: "Which platform(s) to build. Partial builds upload the
new tarball to the existing release; tarballs for unbuilt
platforms stay put."
required: false
type: choice
default: all
options:
- all
- linux-x86_64
- linux-aarch64
- macos-aarch64
- windows-x86_64
env:
SPC_VERSION: "v3.0.0-pgo-rc15"
# Linux + macOS extension set (sorted, including Unix-only IPC + process
# control extensions). Windows uses WIN_PHP_EXTENSIONS — keep the two
# in sync EXCEPT for the Unix-only entries (pcntl, posix, sysv*).
PHP_EXTENSIONS: "bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,gd,gettext,gmp,hash,iconv,intl,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pcre,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,session,simplexml,soap,sodium,sqlite3,sysvmsg,sysvsem,sysvshm,tokenizer,xml,xmlreader,xmlwriter,xsl,zip,zlib"
jobs:
# Compute which jobs / matrix entries actually run based on the `platform`
# input. Each build job below reads from these outputs so we can target a
# single OS/arch (or all of them) from one workflow_dispatch invocation.
# Empty matrices => the job's matrix expands to zero runs and is skipped.
setup:
name: Setup (resolve target platforms)
runs-on: ubuntu-latest
outputs:
linux-matrix: ${{ steps.compute.outputs.linux-matrix }}
build-macos: ${{ steps.compute.outputs.build-macos }}
build-windows: ${{ steps.compute.outputs.build-windows }}
steps:
- id: compute
env:
PLATFORM: ${{ inputs.platform }}
run: |
linux_all='[{"arch":"x86_64","runner":["self-hosted","linux","x64"],"spc_arch":"x86_64"},{"arch":"aarch64","runner":["self-hosted","linux","arm64"],"spc_arch":"aarch64"}]'
linux_x64='[{"arch":"x86_64","runner":["self-hosted","linux","x64"],"spc_arch":"x86_64"}]'
linux_arm='[{"arch":"aarch64","runner":["self-hosted","linux","arm64"],"spc_arch":"aarch64"}]'
case "$PLATFORM" in
all) lm=$linux_all; mac=true; win=true ;;
linux-x86_64) lm=$linux_x64; mac=false; win=false ;;
linux-aarch64) lm=$linux_arm; mac=false; win=false ;;
macos-aarch64) lm='[]'; mac=true; win=false ;;
windows-x86_64) lm='[]'; mac=false; win=true ;;
*) echo "::error::unknown platform $PLATFORM"; exit 1 ;;
esac
echo "linux-matrix=$lm" >> "$GITHUB_OUTPUT"
echo "build-macos=$mac" >> "$GITHUB_OUTPUT"
echo "build-windows=$win" >> "$GITHUB_OUTPUT"
build-linux:
name: Linux (${{ matrix.arch }})
needs: setup
if: needs.setup.outputs.linux-matrix != '[]'
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.setup.outputs.linux-matrix) }}
steps:
- name: Clean up previous builds
run: rm -rf output sdk *.tar.gz
- name: Build inside Alpine container
run: |
docker run --name spc-build \
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
-e SPC_VERSION="${{ env.SPC_VERSION }}" \
-e PHP_VERSION="${{ inputs.php_version }}" \
-e PHP_EXTENSIONS="${{ env.PHP_EXTENSIONS }}" \
docker.io/library/alpine:3.20 sh -c '
set -eux
apk add --no-cache \
bash curl git build-base autoconf automake bison cmake flex \
libtool make pkgconf re2c linux-headers tar xz
# Use native gcc instead of zig-cc - Alpine already has musl
export SPC_TOOLCHAIN="StaticPHP\\Toolchain\\GccNativeToolchain"
mkdir -p /usr/local/bin
curl -fsSL "https://github.com/luthermonson/static-php-cli/releases/download/${SPC_VERSION}/spc-linux-$(uname -m).tar.gz" \
| tar xz -C /usr/local/bin
mkdir /build && cd /build
# Retry spc download to tolerate transient upstream flakes
# (gnu.org libiconv mirror, pecl.php.net, GitHub API rate
# limits). Up to 6 attempts with 2-3s jitter between (~15s).
i=0
until spc download \
--with-php=${PHP_VERSION} \
--for-extensions=${PHP_EXTENSIONS} \
--prefer-pre-built; do
i=$((i + 1))
if [ "$i" -ge 6 ]; then
echo "spc download failed after $i attempts" >&2
exit 1
fi
s=$(awk "BEGIN{srand(); print 2+int(rand()*2)}")
echo "spc download flaked (attempt $i), retrying in ${s}s..."
sleep "$s"
done
spc doctor --auto-fix || true
# --dl-with-php pins the build-phase artifact downloader to the
# requested version. Without it, spc build re-checks php-src
# against the dl-with-php DEFAULT (8.5), treats the cached
# 8.4.x/8.3.x source as a mismatch, silently re-downloads
# latest 8.5.x, and builds the wrong PHP (shipped mislabeled
# tarballs until the version guard below caught it).
# NOTE: this comment lives inside a single-quoted sh -c
# block; apostrophes here would terminate the string.
spc build ${PHP_EXTENSIONS} \
--dl-with-php=${PHP_VERSION} \
--build-embed \
--enable-zts \
--no-strip \
--debug
# Stage artifacts inside the container before it stops.
# spc nests install output under buildroot/lib at varying
# depths (lib/lib/, sometimes deeper). Find .a files and the
# php header dir wherever they landed and copy them into a
# flat layout. cp -L dereferences symlinks
# (e.g. libcurses.a → libncurses.a).
mkdir -p /output/lib /output/include
find /build/buildroot/lib -name "*.a" -type f -exec cp -Lf {} /output/lib/ \;
PHP_INC_DIR=$(find /build/buildroot/include -name php -type d | head -n 1)
if [ -z "$PHP_INC_DIR" ]; then
echo "ERROR: no php header dir found under buildroot/include" >&2
exit 1
fi
cp -RLf "$PHP_INC_DIR" /output/include/php
# Bundle the musl-built libstdc++.a so downstream consumers
# can link against the same C++ runtime spc used to compile
# libicu*, libxml2, etc. PHPs intl extension references C++
# symbols (std::__throw_bad_alloc, __cxa_begin_catch, ICU
# UnicodeString ctors, ...). Ubuntu/Debian build hosts have
# a glibc-flavored libstdc++ that is ABI-incompatible with
# musl PHP — without bundling this, downstream binaries
# either fail to link or silently mismix C++ runtimes.
libstdcxx_path=$(c++ -print-file-name=libstdc++.a)
if [ -f "$libstdcxx_path" ]; then
cp "$libstdcxx_path" /output/lib/
echo "==> Staged libstdc++.a from $libstdcxx_path"
else
echo "::error::libstdc++.a not found (c++ -print-file-name returned: $libstdcxx_path)"
exit 1
fi
'
# Copy staged artifacts out of the stopped container.
# `docker cp container:/output ./` copies /output as ./output (src
# dir → dest as src-named subdir). Avoid the `/.` "copy contents"
# form — observed to leave the source dir nested inside the
# destination on some runner Docker versions, producing
# ./lib/lib/*.a in the final tarball.
docker cp spc-build:/output ./
docker rm spc-build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Package SDK
run: |
mkdir -p sdk
cp -r output/* sdk/
# Guard: the staged headers must be the PHP version this run was
# asked to build. spc has silently fallen back to "latest stable"
# before (--with-php=8.4.21 produced a PHP 8.5.7 tree, shipped as
# v8.4.21, and was only caught days later by ephpm's E2E version
# assertion). Fail here, loudly, instead of publishing a
# mislabeled SDK.
staged=$(sed -n 's/^#define PHP_VERSION "\(.*\)"/\1/p' sdk/include/php/main/php_version.h)
if [ "$staged" != "${{ inputs.php_version }}" ]; then
echo "::error::staged PHP is '${staged}' but this build was asked for '${{ inputs.php_version }}' - spc ignored --with-php"
exit 1
fi
echo "==> Version guard OK: staged PHP ${staged}"
tar czf php-sdk-${{ inputs.php_version }}-linux-${{ matrix.arch }}.tar.gz -C sdk .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: php-sdk-${{ inputs.php_version }}-linux-${{ matrix.arch }}
path: php-sdk-${{ inputs.php_version }}-linux-${{ matrix.arch }}.tar.gz
build-macos:
name: macOS (${{ matrix.arch }})
needs: setup
if: needs.setup.outputs.build-macos == 'true'
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: aarch64
runner: [self-hosted, macos, arm64]
spc_arch: aarch64
steps:
# spc on macOS is a self-contained micro-PHP binary (same as Windows
# spc.exe), so no host PHP install is needed. The runner image is bare,
# so we let `spc doctor --auto-fix` install Homebrew and the build deps
# it lists in MacOSToolCheckList::REQUIRED_COMMANDS (autoconf, automake,
# bison, cmake, flex, libtool, make, pkg-config, re2c, etc.).
- name: Download spc
run: |
mkdir -p "$HOME/bin"
curl -fsSL "https://github.com/luthermonson/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-${{ matrix.spc_arch }}.tar.gz" \
| tar xz -C "$HOME/bin"
echo "$HOME/bin" >> "$GITHUB_PATH"
# Pre-add Homebrew to PATH so spc doctor can use brew immediately
# after installing it (spc doesn't update PATH itself).
echo "/opt/homebrew/bin" >> "$GITHUB_PATH"
- name: Install Homebrew + build deps via spc doctor
# NONINTERACTIVE=1 is required so the Homebrew installer doesn't prompt
# for sudo confirmation (which would hang in CI).
env:
NONINTERACTIVE: "1"
run: spc doctor --auto-fix
# Apple clang crashes on PHP's JIT code (zend_jit_vm_helpers.c, exit 70),
# so use Homebrew LLVM instead. Doctor's REQUIRED_COMMANDS doesn't
# include llvm, so we install it explicitly after brew is up.
- name: Install LLVM and prepend to PATH
run: |
brew install llvm
echo "$(brew --prefix llvm)/bin" >> "$GITHUB_PATH"
- name: Download PHP sources
# Retry to tolerate transient upstream flakes (see linux build).
run: |
i=0
until spc download \
--with-php=${{ inputs.php_version }} \
--for-extensions=${{ env.PHP_EXTENSIONS }} \
--prefer-pre-built; do
i=$((i + 1))
if [ "$i" -ge 6 ]; then
echo "spc download failed after $i attempts" >&2
exit 1
fi
s=$((RANDOM % 2 + 2))
echo "spc download flaked (attempt $i), retrying in ${s}s..."
sleep "$s"
done
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build libphp.a
run: |
# PHP < 8.4 uses K&R function declarations removed in C23.
# Homebrew LLVM/clang defaults to -std=gnu23, so force gnu17.
php_minor=$(echo "${{ inputs.php_version }}" | cut -d. -f2)
if [ "$php_minor" -lt 4 ]; then
export CFLAGS="-std=gnu17"
fi
# --dl-with-php pins the build-phase downloader to the requested
# version (see linux build for the incident this prevents).
spc build ${{ env.PHP_EXTENSIONS }} \
--dl-with-php=${{ inputs.php_version }} \
--build-embed \
--enable-zts \
--no-strip \
--debug
- name: Package SDK
run: |
mkdir -p sdk/lib sdk/include
# spc nests install output at varying depths; discover by find
# (see linux build for rationale).
find buildroot/lib -name "*.a" -type f -exec cp -Lf {} sdk/lib/ \;
PHP_INC_DIR=$(find buildroot/include -name php -type d | head -n 1)
if [ -z "$PHP_INC_DIR" ]; then
echo "ERROR: no php header dir found under buildroot/include" >&2
exit 1
fi
cp -RLf "$PHP_INC_DIR" sdk/include/php
# Guard: staged headers must match the requested version (see the
# linux Package SDK step for the incident this prevents).
staged=$(sed -n 's/^#define PHP_VERSION "\(.*\)"/\1/p' sdk/include/php/main/php_version.h)
if [ "$staged" != "${{ inputs.php_version }}" ]; then
echo "::error::staged PHP is '${staged}' but this build was asked for '${{ inputs.php_version }}' - spc ignored --with-php"
exit 1
fi
echo "==> Version guard OK: staged PHP ${staged}"
tar czf php-sdk-${{ inputs.php_version }}-macos-${{ matrix.arch }}.tar.gz -C sdk .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: php-sdk-${{ inputs.php_version }}-macos-${{ matrix.arch }}
path: php-sdk-${{ inputs.php_version }}-macos-${{ matrix.arch }}.tar.gz
build-windows:
name: Windows (x86_64)
needs: setup
if: needs.setup.outputs.build-windows == 'true'
runs-on: [self-hosted, windows, x64]
# Windows-specific extension set: PHP_EXTENSIONS minus the Unix-only
# entries (pcntl, posix, sysvmsg, sysvsem, sysvshm) and any extension
# that won't build under static-php-cli's current Windows toolchain:
# - gmp → no static-libs@windows in v3 gmp.yml
# - pgsql, pdo_pgsql → spc uses pre-built EnterpriseDB import lib
# (links against LIBPQ.DLL) instead of a static build
# Re-add as spc grows Windows support for its underlying library.
env:
WIN_PHP_EXTENSIONS: "bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,gd,gettext,hash,iconv,intl,mbstring,mysqli,mysqlnd,opcache,openssl,pcre,pdo,pdo_mysql,pdo_sqlite,phar,session,simplexml,soap,sodium,sqlite3,tokenizer,xml,xmlreader,xmlwriter,xsl,zip,zlib"
steps:
- name: Clean up previous builds
shell: powershell
run: |
foreach ($dir in @('buildroot', 'source', 'downloads', 'sdk', 'php-sdk-binary-tools')) {
if (Test-Path $dir) { Remove-Item -Recurse -Force $dir }
}
Remove-Item -Force *.tar.gz -ErrorAction SilentlyContinue
- name: Install Git for Windows
shell: powershell
# Server Core has no git. spc needs:
# - git.exe: doctor's install-php-sdk auto-fix clones php-sdk-binary-tools
# - usr\bin\patch.exe: source patching during build
# Use PortableGit (self-extracting 7z) installed at the standard Git
# for Windows path so spc's expected layout matches.
run: |
$gitRoot = "C:\Program Files\Git"
if (-not (Test-Path "$gitRoot\cmd\git.exe")) {
$ver = "2.49.0"
$url = "https://github.com/git-for-windows/git/releases/download/v${ver}.windows.1/PortableGit-${ver}-64-bit.7z.exe"
Write-Host "Downloading PortableGit $ver..."
Invoke-WebRequest -Uri $url -OutFile portable-git.7z.exe
Write-Host "Extracting to $gitRoot..."
Start-Process -FilePath .\portable-git.7z.exe `
-ArgumentList @("-o`"$gitRoot`"", '-y') -Wait -NoNewWindow
Remove-Item portable-git.7z.exe
if (-not (Test-Path "$gitRoot\cmd\git.exe")) {
Write-Error "PortableGit extraction failed: $gitRoot\cmd\git.exe not found"
exit 1
}
}
"$gitRoot\cmd" | Out-File -Append -FilePath $env:GITHUB_PATH
"$gitRoot\usr\bin" | Out-File -Append -FilePath $env:GITHUB_PATH
- name: Install Visual Studio Build Tools
shell: powershell
# PHP requires MSVC to compile on Windows. Runner image is
# mcr.microsoft.com/windows/servercore:ltsc2025 with no compiler.
# Install Build Tools per-job (~10-15 min). Long-term: dedicated
# php-sdk runner image with VS pre-baked.
#
# Install path: C:\BuildTools (Microsoft's recommended path for
# Server Core containers; nested Program Files paths are flaky).
# spc's findVisualStudio() probes Community/Professional/Enterprise
# under standard Program Files locations, so we junction one of
# those to C:\BuildTools after install.
#
# The bootstrapper (vs_buildtools.exe) exits before the underlying
# installer engine actually finishes on Server Core, so we poll
# for MSBuild.exe rather than trusting its exit code.
run: |
$installPath = "C:\BuildTools"
$msbuild = "$installPath\MSBuild\Current\Bin\MSBuild.exe"
$communityPath = "C:\Program Files\Microsoft Visual Studio\2022\Community"
if (-not (Test-Path $msbuild)) {
Write-Host "Downloading VS Build Tools bootstrapper..."
Invoke-WebRequest -Uri "https://aka.ms/vs/17/release/vs_buildtools.exe" -OutFile vs_buildtools.exe
Write-Host "Launching install (bootstrapper may exit before install completes)..."
$proc = Start-Process -FilePath .\vs_buildtools.exe `
-ArgumentList @(
'--quiet', '--wait', '--norestart', '--nocache',
'--installPath', $installPath,
'--add', 'Microsoft.VisualStudio.Workload.VCTools',
'--includeRecommended'
) -Wait -PassThru -NoNewWindow
Write-Host "Bootstrapper exit code: $($proc.ExitCode)"
Remove-Item vs_buildtools.exe
# Poll for MSBuild.exe; bootstrapper exit is unreliable on Server Core
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$timeoutMin = 20
while (-not (Test-Path $msbuild) -and $sw.Elapsed.TotalMinutes -lt $timeoutMin) {
Start-Sleep -Seconds 30
Write-Host (" ...waiting for VS install ({0:F0}s)" -f $sw.Elapsed.TotalSeconds)
}
if (-not (Test-Path $msbuild)) {
Write-Host "::error::VS install timed out after $timeoutMin min - $msbuild never appeared"
Write-Host "==> Searching for MSBuild.exe anywhere on disk:"
Get-ChildItem -Path C:\ -Recurse -Filter MSBuild.exe -ErrorAction SilentlyContinue |
Select-Object -First 10 |
ForEach-Object { Write-Host " $($_.FullName)" }
Write-Host "==> Recent VS install logs in `$env:TEMP (dd_*.log):"
Get-ChildItem -Path $env:TEMP -Filter 'dd_*.log' -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 5 |
ForEach-Object {
Write-Host ('--- ' + $_.Name + ' ---')
Get-Content $_.FullName -Tail 80
}
exit 1
}
Write-Host ("MSBuild.exe found after {0:F0}s" -f $sw.Elapsed.TotalSeconds)
} else {
Write-Host "VS Build Tools already present at $installPath"
}
# Junction the path spc's hardcoded check expects -> our install path
if (-not (Test-Path $communityPath)) {
$parent = Split-Path $communityPath -Parent
if (-not (Test-Path $parent)) {
New-Item -ItemType Directory -Path $parent -Force | Out-Null
}
New-Item -ItemType Junction -Path $communityPath -Target $installPath | Out-Null
Write-Host "Junction $communityPath -> $installPath"
}
- name: Download spc
shell: powershell
run: |
Invoke-WebRequest -Uri "https://github.com/luthermonson/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-windows-x64.exe" -OutFile spc.exe
"$PWD" | Out-File -Append -FilePath $env:GITHUB_PATH
- name: Download PHP sources
shell: powershell
# Retry to tolerate transient upstream flakes (see linux build).
run: |
$max = 6
for ($i = 1; $i -le $max; $i++) {
spc download `
--with-php=${{ inputs.php_version }} `
--for-extensions=${{ env.WIN_PHP_EXTENSIONS }} `
--prefer-pre-built
if ($LASTEXITCODE -eq 0) { break }
if ($i -eq $max) {
Write-Error "spc download failed after $max attempts"
exit 1
}
$s = Get-Random -Minimum 2 -Maximum 4
Write-Host "spc download flaked (attempt $i), retrying in ${s}s..."
Start-Sleep -Seconds $s
}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run spc doctor
shell: powershell
# --auto-fix runs install-php-sdk (clones php-sdk-binary-tools, needed
# for 7za.exe at extraction time), install-nasm, install-perl.
run: |
spc doctor --auto-fix
exit 0
- name: Add Strawberry Perl to PATH
shell: powershell
# spc doctor installs Strawberry Perl into pkgroot but doesn't
# persist it in GITHUB_PATH. Git's perl is on PATH first and is
# missing modules openssl needs (Locale::Maketext::Simple).
# Prepend Strawberry Perl so it wins over Git's perl.
run: |
Write-Host "Searching for Strawberry Perl in: $PWD"
Get-ChildItem -Directory -Recurse -Filter "strawberry-perl*" -ErrorAction SilentlyContinue |
Select-Object -First 5 | ForEach-Object { Write-Host " Found dir: $($_.FullName)" }
$perlExe = Get-ChildItem -Recurse -Filter "perl.exe" -Path pkgroot -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "strawberry" } |
Select-Object -First 1
if ($perlExe) {
$perlBin = $perlExe.DirectoryName
Write-Host "Adding Strawberry Perl to PATH: $perlBin"
$perlBin | Out-File -Append -FilePath $env:GITHUB_PATH
} else {
Write-Host "::warning::Strawberry Perl not found in pkgroot - falling back to system perl"
}
- name: Build php8embed (patched spc with embed SAPI support)
shell: powershell
run: |
# Ensure Strawberry Perl precedes Git's perl in PATH for this step
$spDir = Get-ChildItem -Directory pkgroot -Recurse -Filter "strawberry-perl*" -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($spDir -and (Test-Path "$($spDir.FullName)\perl\bin\perl.exe")) {
$env:PATH = "$($spDir.FullName)\perl\bin;$env:PATH"
Write-Host "Strawberry Perl active: $(& perl.exe -v 2>&1 | Select-String 'MSWin32|version')"
}
# --dl-with-php pins the build-phase downloader to the requested
# version (see linux build for the incident this prevents).
spc build ${{ env.WIN_PHP_EXTENSIONS }} `
--dl-with-php=${{ inputs.php_version }} `
--build-embed `
--enable-zts `
--no-strip `
--debug
- name: Find build outputs
shell: powershell
run: |
Write-Host "==> Searching for php8embed files..."
Get-ChildItem -Recurse -Filter "php8embed.*" | ForEach-Object { Write-Host $_.FullName }
Write-Host "==> Searching for php8ts files..."
Get-ChildItem -Recurse -Filter "php8ts.*" | ForEach-Object { Write-Host $_.FullName }
Write-Host "==> Searching for libphp files..."
Get-ChildItem -Recurse -Filter "libphp.*" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_.FullName }
- name: Package SDK
shell: powershell
run: |
New-Item -ItemType Directory -Force -Path sdk/lib, sdk/include
# Find php8embed.lib (fat static library containing all PHP objects)
$lib = Get-ChildItem -Recurse -Filter "php8embed.lib" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($lib) {
Copy-Item $lib.FullName sdk/lib/
Write-Host "==> Found php8embed.lib at $($lib.FullName)"
} else {
Write-Error "php8embed.lib not found anywhere in build tree"
exit 1
}
# Copy any DLLs if present (not expected for spc static builds)
foreach ($name in @("php8embed.dll", "php8ts.dll")) {
$dll = Get-ChildItem -Recurse -Filter $name -ErrorAction SilentlyContinue | Select-Object -First 1
if ($dll) {
Copy-Item $dll.FullName sdk/lib/
Write-Host "==> Found $name at $($dll.FullName)"
}
}
Copy-Item -Recurse buildroot/include/php sdk/include/php
# spc's Windows build (nmake-driven) doesn't install the
# platform-specific PHP win32 headers into buildroot the way
# autoconf's `make install-headers` does on Linux/macOS. The
# generated headers (e.g. Zend/zend_stream.h:80) still expect
# `#include "win32/ioutil.h"` relative to the main/ dir, so
# without these consumers fail with `fatal error: 'win32/
# ioutil.h' file not found` at the first compile. Stage them
# from the PHP source tree directly so the published tarball
# is self-contained.
$win32SrcDir = Get-ChildItem -Directory source -ErrorAction SilentlyContinue |
Where-Object { Test-Path "$($_.FullName)/win32/ioutil.h" } |
Select-Object -First 1
if (-not $win32SrcDir) {
Write-Error "Could not find php-src/win32/ioutil.h under source/ - was the PHP source extracted?"
exit 1
}
$win32Dest = "sdk/include/php/main/win32"
New-Item -ItemType Directory -Force -Path $win32Dest | Out-Null
Copy-Item "$($win32SrcDir.FullName)/win32/*.h" $win32Dest
Write-Host "==> Staged win32 headers from $($win32SrcDir.FullName)/win32 -> $win32Dest"
Get-ChildItem $win32Dest | ForEach-Object { Write-Host " $($_.Name)" }
# main/php_streams.h includes streams/php_stream_context.h (plus
# several siblings: transport, filter_api, glob_wrapper, mmap,
# userspace, streams_int) unconditionally. On Linux and macOS,
# autoconf's `make install-headers` installs main/streams/*.h
# alongside main/*.h. spc's Windows build (nmake) doesn't run
# install-headers, so the streams/ subdirectory never makes it
# into buildroot/include and the published tarball ships without
# it - consumers fail to compile the first translation unit that
# pulls in main/php.h. Stage the same way we stage win32/*.h:
# copy the .h files directly from the extracted php-src tree.
$streamsSrcDir = Get-ChildItem -Directory source -ErrorAction SilentlyContinue |
Where-Object { Test-Path "$($_.FullName)/main/streams/php_stream_context.h" } |
Select-Object -First 1
if (-not $streamsSrcDir) {
Write-Error "Could not find php-src/main/streams/php_stream_context.h under source/ - was the PHP source extracted?"
exit 1
}
$streamsDest = "sdk/include/php/main/streams"
New-Item -ItemType Directory -Force -Path $streamsDest | Out-Null
Copy-Item "$($streamsSrcDir.FullName)/main/streams/*.h" $streamsDest
Write-Host "==> Staged streams headers from $($streamsSrcDir.FullName)/main/streams -> $streamsDest"
Get-ChildItem $streamsDest | ForEach-Object { Write-Host " $($_.Name)" }
# nmake install-headers on Windows doesn't recurse into ext/*
# subdirectories. PHP's own headers cross-reference each other
# with `#include "ext/standard/php_var.h"` from
# ext/session/php_session.h (and several others), so the entire
# ext/*/ header trees must be in the SDK tarball or downstream
# compiles fail at the first translation unit that pulls in an
# extension header.
#
# Recursive-by-default rather than per-subdir explicit list: we
# don't know in advance which ext/<name>/ headers each PHP
# release cross-references, and shipping more headers is cheap.
# Mirrors the main/streams/ pattern above.
$extSrcRoot = "$($streamsSrcDir.FullName)/ext"
if (-not (Test-Path "$extSrcRoot/standard/php_var.h")) {
Write-Error "Could not find php-src/ext/standard/php_var.h under $extSrcRoot"
exit 1
}
$extDest = "sdk/include/php/ext"
Get-ChildItem -Path $extSrcRoot -Recurse -Filter "*.h" |
ForEach-Object {
$rel = $_.FullName.Substring($extSrcRoot.Length).TrimStart('\','/')
$dest = Join-Path $extDest $rel
New-Item -ItemType Directory -Force -Path (Split-Path $dest -Parent) | Out-Null
Copy-Item $_.FullName $dest
}
$extHeaderCount = (Get-ChildItem -Path $extDest -Recurse -Filter "*.h").Count
Write-Host "==> Staged $extHeaderCount ext/*/*.h headers from $extSrcRoot -> $extDest"
Write-Host "==> SDK contents:"
Get-ChildItem -Recurse sdk/lib/
# Guard: staged headers must match the requested version (see the
# linux Package SDK step for the incident this prevents).
$versionHeader = "sdk/include/php/main/php_version.h"
$match = Select-String -Path $versionHeader -Pattern '#define PHP_VERSION "([^"]+)"'
if (-not $match) {
Write-Error "Could not read PHP_VERSION from $versionHeader"
exit 1
}
$staged = $match.Matches[0].Groups[1].Value
if ($staged -ne "${{ inputs.php_version }}") {
Write-Error "staged PHP is '$staged' but this build was asked for '${{ inputs.php_version }}' - spc ignored --with-php"
exit 1
}
Write-Host "==> Version guard OK: staged PHP $staged"
tar czf php-sdk-${{ inputs.php_version }}-windows-x86_64.tar.gz -C sdk .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: php-sdk-${{ inputs.php_version }}-windows-x86_64
path: php-sdk-${{ inputs.php_version }}-windows-x86_64.tar.gz
release:
name: Create Release
needs: [build-linux, build-macos, build-windows]
# Partial rebuilds skip the un-targeted build jobs (skipped != failure),
# but a real failure should still abort the release.
if: |
always()
&& needs.build-linux.result != 'failure'
&& needs.build-macos.result != 'failure'
&& needs.build-windows.result != 'failure'
runs-on: [self-hosted, linux, x64]
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: List artifacts
run: ls -lh artifacts/ || { echo "::error::no artifacts produced"; exit 1; }
- name: Create release
uses: softprops/action-gh-release@v2
# action-gh-release replaces only assets whose filenames match what
# we upload. Tarballs for platforms we didn't rebuild stay attached
# to the existing release tag untouched.
with:
tag_name: v${{ inputs.php_version }}
name: PHP ${{ inputs.php_version }}
body: |
Pre-built PHP embed SAPI SDK for ephpm.
**PHP version:** ${{ inputs.php_version }}
**Extensions:** ${{ env.PHP_EXTENSIONS }}
## Artifacts
| File | Platform |
|------|----------|
| `php-sdk-${{ inputs.php_version }}-linux-x86_64.tar.gz` | Linux x86_64 (musl static) |
| `php-sdk-${{ inputs.php_version }}-linux-aarch64.tar.gz` | Linux aarch64 (musl static) |
| `php-sdk-${{ inputs.php_version }}-macos-aarch64.tar.gz` | macOS Apple Silicon |
| `php-sdk-${{ inputs.php_version }}-windows-x86_64.tar.gz` | Windows x86_64 (ZTS) |
files: artifacts/*.tar.gz