Build PHP 8.5.7 #151
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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*). | |
| # | |
| # NOTE on mbregex: spc builds mbstring with --disable-mbregex UNLESS the | |
| # separate `mbregex` extension is also requested (it pulls in oniguruma). | |
| # Without it, mb_split()/mb_ereg*() are undefined — which breaks Laravel's | |
| # Str::studly() and any framework using the mbstring regex functions. Keep | |
| # `mbregex` in BOTH lists alongside `mbstring`. | |
| PHP_EXTENSIONS: "bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,gd,gettext,gmp,hash,iconv,intl,mbregex,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,mbregex,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 (the embed SAPI static library) | |
| $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 | |
| } | |
| # Ship php8embed.lib's static dependency libraries too. spc's | |
| # --build-embed is a static build: php8embed.lib carries PHP's | |
| # own objects, but the third-party deps it was compiled against | |
| # (openssl/libcrypto, zlib, libxml2, ICU, oniguruma, sqlite3, ...) | |
| # live as separate .lib files under spc's buildroot. ephpm links | |
| # the embed lib STATICALLY (like libphp.a on Linux/macOS), so those | |
| # dep libs must be in the tarball or the final MSVC link fails with | |
| # unresolved externals. The Linux/macOS Package SDK steps already | |
| # copy every *.a from buildroot/lib for the same reason; do the | |
| # equivalent here. | |
| # | |
| # Sources, in priority order: every buildroot*/lib directory spc | |
| # produced, plus any .lib sitting alongside php8embed.lib. We do NOT | |
| # do a tree-wide *.lib copy -- that would sweep in MSVC/Windows-SDK | |
| # import libs. Scope to spc's own output dirs only. | |
| $depDirs = @() | |
| Get-ChildItem -Recurse -Directory -ErrorAction SilentlyContinue | | |
| Where-Object { $_.Name -eq "lib" -and $_.Parent.Name -like "buildroot*" } | | |
| ForEach-Object { $depDirs += $_.FullName } | |
| $depDirs += (Split-Path $lib.FullName -Parent) | |
| $depDirs = $depDirs | Select-Object -Unique | |
| $copied = 0 | |
| foreach ($dir in $depDirs) { | |
| Get-ChildItem -Path $dir -Filter "*.lib" -File -ErrorAction SilentlyContinue | | |
| ForEach-Object { | |
| Copy-Item $_.FullName sdk/lib/ -Force | |
| $copied++ | |
| } | |
| } | |
| Write-Host "==> Staged $copied .lib file(s) from spc buildroot dirs: $($depDirs -join ', ')" | |
| # Diagnostic: full inventory of every .lib spc produced anywhere in | |
| # the tree (path + size). If the downstream static link still hits | |
| # unresolved externals, this log pinpoints which lib provides the | |
| # missing symbols and whether it shipped. | |
| Write-Host "==> Full .lib inventory in build tree:" | |
| Get-ChildItem -Recurse -Filter "*.lib" -File -ErrorAction SilentlyContinue | | |
| Sort-Object FullName | | |
| ForEach-Object { Write-Host (" {0,12:N0} {1}" -f $_.Length, $_.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 |