Build PHP 8.5.6 with Xdebug 3.5.1 #49
Workflow file for this run
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 CLI Binaries | |
| run-name: Build PHP ${{ inputs.php_version }} with Xdebug ${{ inputs.xdebug_version }} | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| php_version: | |
| description: PHP patch version to build | |
| required: true | |
| default: 8.4.20 | |
| xdebug_version: | |
| description: Xdebug version to package | |
| required: true | |
| default: 3.5.1 | |
| spc_ref: | |
| description: static-php-cli ref to use for the macOS build | |
| required: true | |
| default: 2.8.5 | |
| debug: | |
| description: Enable verbose build logs | |
| required: true | |
| type: boolean | |
| default: false | |
| upload_to_apps_cdn: | |
| description: Upload to Apps CDN | |
| required: true | |
| type: boolean | |
| default: false | |
| apps_cdn_visibility: | |
| description: Apps CDN visibility | |
| required: true | |
| type: choice | |
| options: | |
| - internal | |
| - external | |
| default: internal | |
| permissions: | |
| contents: read | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| jobs: | |
| build: | |
| name: Build ${{ matrix.target }} | |
| runs-on: ${{ matrix.runner }} | |
| timeout-minutes: 240 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: macos-aarch64 | |
| runner: macos-15 | |
| artifact_platform: macos | |
| artifact_arch: aarch64 | |
| extensions: &macos_extensions apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gettext,iconv,igbinary,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,readline,redis,session,shmop,simplexml,sockets,sodium,sqlite3,ssh2,tokenizer,xdebug,xml,xmlreader,xmlwriter,xsl,yaml,zip,zlib | |
| - target: macos-x86_64 | |
| runner: macos-15-intel | |
| artifact_platform: macos | |
| artifact_arch: x86_64 | |
| extensions: *macos_extensions | |
| - target: windows-x86_64 | |
| runner: windows-latest | |
| artifact_platform: windows | |
| artifact_arch: x86_64 | |
| extensions: apcu,bcmath,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,iconv,igbinary,intl,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pdo,pdo_mysql,pdo_sqlite,phar,redis,session,shmop,simplexml,sockets,sodium,sqlite3,ssh2,tokenizer,xdebug,xml,xmlreader,xmlwriter,yaml,zip,zlib | |
| env: | |
| PHP_VERSION: ${{ inputs.php_version }} | |
| XDEBUG_VERSION: ${{ inputs.xdebug_version }} | |
| SPC_REF: ${{ inputs.spc_ref }} | |
| PHP_CLI_EXTENSIONS: ${{ matrix.extensions }} | |
| PHP_CLI_ARTIFACT_PLATFORM: ${{ matrix.artifact_platform }} | |
| PHP_CLI_ARTIFACT_ARCH: ${{ matrix.artifact_arch }} | |
| BUILD_DEBUG: ${{ inputs.debug }} | |
| steps: | |
| - name: Checkout Studio | |
| uses: actions/checkout@v6 | |
| - name: Checkout static-php-cli | |
| if: runner.os == 'macOS' | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: crazywhalecc/static-php-cli | |
| ref: ${{ env.SPC_REF }} | |
| path: .cache/static-php-cli | |
| - name: Resolve PHP minor version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "$PHP_VERSION" =~ ^8\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "Unsupported PHP version: $PHP_VERSION (expected 8.x.y)" >&2 | |
| exit 1 | |
| fi | |
| php_minor="${PHP_VERSION%.*}" | |
| echo "PHP_MINOR=$php_minor" >> "$GITHUB_ENV" | |
| - name: Setup PHP for static-php-cli | |
| if: runner.os == 'macOS' | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| coverage: none | |
| tools: composer:v2 | |
| php-version: 8.4 | |
| ini-values: memory_limit=-1 | |
| extensions: curl, openssl, mbstring | |
| env: | |
| phpts: nts | |
| - name: Install static-php-cli dependencies | |
| if: runner.os == 'macOS' | |
| working-directory: .cache/static-php-cli | |
| run: composer install --no-dev --no-interaction --prefer-dist --classmap-authoritative | |
| - name: Build macOS PHP runtime with static-php-cli | |
| if: runner.os == 'macOS' | |
| working-directory: .cache/static-php-cli | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ "$BUILD_DEBUG" = "true" ]; then | |
| set -x | |
| fi | |
| # mbregex, readline, and zlib declare target: ["static"] in SPC's | |
| # config/ext.json and must be baked into PHP. mbstring is forced static | |
| # to work around a missing-space bug in SPC's mbregex validation | |
| # (src/SPC/builder/extension/mbregex.php:25 emits `php -n-d ...` when | |
| # mbstring is shared). Everything else — including Xdebug, which is | |
| # target: ["shared"] — is built as a loadable .so under buildroot/modules. | |
| forced_static_exts="mbregex mbstring readline zlib" | |
| static_list=() | |
| shared_list=() | |
| IFS=',' read -ra all_exts <<< "$PHP_CLI_EXTENSIONS" | |
| for ext in "${all_exts[@]}"; do | |
| ext_trimmed="${ext// /}" | |
| is_static=false | |
| for s in $forced_static_exts; do | |
| if [ "$ext_trimmed" = "$s" ]; then | |
| is_static=true | |
| break | |
| fi | |
| done | |
| if $is_static; then | |
| static_list+=("$ext_trimmed") | |
| else | |
| shared_list+=("$ext_trimmed") | |
| fi | |
| done | |
| static_extensions=$(IFS=','; echo "${static_list[*]}") | |
| shared_extensions=$(IFS=','; echo "${shared_list[*]}") | |
| download_args=( | |
| --with-php="$PHP_VERSION" | |
| --for-extensions="${PHP_CLI_EXTENSIONS}" | |
| --custom-url="xdebug:https://xdebug.org/files/xdebug-${XDEBUG_VERSION}.tgz" | |
| --ignore-cache-sources=php-src | |
| --prefer-pre-built | |
| ) | |
| build_args=( | |
| "$static_extensions" | |
| --build-shared="$shared_extensions" | |
| --build-cli | |
| ) | |
| if [ "$BUILD_DEBUG" = "true" ]; then | |
| download_args+=(--debug) | |
| build_args+=(--debug) | |
| fi | |
| php bin/spc doctor --auto-fix | |
| php bin/spc download "${download_args[@]}" | |
| php bin/spc build "${build_args[@]}" | |
| - name: Package macOS artifact | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| spc_root="$GITHUB_WORKSPACE/.cache/static-php-cli/buildroot" | |
| staging_dir="$RUNNER_TEMP/php-artifact" | |
| out_dir="$GITHUB_WORKSPACE/out/php-binaries" | |
| artifact="php-${PHP_VERSION}-cli-${PHP_CLI_ARTIFACT_PLATFORM}-${PHP_CLI_ARTIFACT_ARCH}.zip" | |
| rm -rf "$staging_dir" | |
| mkdir -p "$staging_dir/ext" "$out_dir" | |
| cp "$spc_root/bin/php" "$staging_dir/php" | |
| chmod +x "$staging_dir/php" | |
| cp "$spc_root/modules"/*.so "$staging_dir/ext/" | |
| extension_dir="$staging_dir/ext" | |
| find_macho_files() { | |
| find "$staging_dir" -type f \( -name '*.dylib' -o -name '*.so' -o -perm -111 \) -print | while read -r candidate; do | |
| if file "$candidate" | grep -q 'Mach-O'; then | |
| printf '%s\n' "$candidate" | |
| fi | |
| done | |
| } | |
| find_macho_files | while read -r file; do | |
| if otool -L "$file" | grep -E '/opt/homebrew/|/usr/local/'; then | |
| echo "Found non-portable Homebrew dependency in $file" >&2 | |
| exit 1 | |
| fi | |
| if otool -L "$file" | grep -F "$spc_root"; then | |
| echo "Found non-portable static-php-cli dependency in $file" >&2 | |
| exit 1 | |
| fi | |
| done | |
| xattr -cr "$staging_dir" | |
| find_macho_files | while read -r file; do | |
| codesign --force --sign - "$file" | |
| codesign --verify --strict "$file" | |
| done | |
| cat > "$staging_dir/runtime.json" <<JSON | |
| { | |
| "phpVersion": "$PHP_VERSION", | |
| "phpMinor": "$PHP_MINOR", | |
| "threadSafety": "nts", | |
| "platform": "$PHP_CLI_ARTIFACT_PLATFORM", | |
| "arch": "$PHP_CLI_ARTIFACT_ARCH", | |
| "binary": "php", | |
| "extensionDir": "ext", | |
| "xdebug": "ext/xdebug.so" | |
| } | |
| JSON | |
| "$staging_dir/php" -n \ | |
| -d "extension_dir=$extension_dir" \ | |
| -d "zend_extension=$extension_dir/xdebug.so" \ | |
| -d "xdebug.mode=debug" \ | |
| --ri xdebug | |
| smoke_extensions=(apcu curl gd gettext igbinary pdo pdo_sqlite redis sodium sqlite3 ssh2 xsl yaml) | |
| for extension in "${smoke_extensions[@]}"; do | |
| echo "Smoke testing extension: $extension" | |
| otool -L "$extension_dir/$extension.so" | |
| "$staging_dir/php" -n \ | |
| -d "extension_dir=$extension_dir" \ | |
| -d "extension=$extension" \ | |
| -r 'echo PHP_VERSION, PHP_EOL;' | |
| done | |
| smoke_extension_args=() | |
| loaded_count=0 | |
| for extension in "${smoke_extensions[@]}"; do | |
| smoke_extension_args+=(-d "extension=$extension") | |
| loaded_count=$((loaded_count + 1)) | |
| echo "Smoke testing extension set: ${smoke_extensions[*]:0:loaded_count}" | |
| "$staging_dir/php" -n \ | |
| -d "extension_dir=$extension_dir" \ | |
| "${smoke_extension_args[@]}" \ | |
| -r 'echo PHP_VERSION, PHP_EOL;' | |
| done | |
| ( cd "$staging_dir" && zip -qr "$out_dir/$artifact" . ) | |
| shasum -a 256 "$out_dir/$artifact" | awk '{print $1}' > "$out_dir/$artifact.sha256" | |
| - name: Resolve Windows runtime metadata | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $phpMinor = $env:PHP_MINOR | |
| switch ( $phpMinor ) { | |
| '8.0' { $vsVersion = 'vs16' } | |
| '8.1' { $vsVersion = 'vs16' } | |
| '8.2' { $vsVersion = 'vs16' } | |
| '8.3' { $vsVersion = 'vs16' } | |
| '8.4' { $vsVersion = 'vs17' } | |
| '8.5' { $vsVersion = 'vs17' } | |
| default { | |
| Write-Error "Unsupported PHP minor: $phpMinor" | |
| exit 1 | |
| } | |
| } | |
| "PHP_WINDOWS_VS_VERSION=$vsVersion" | Out-File -FilePath $env:GITHUB_ENV -Append | |
| - name: Assemble PHP runtime on Windows | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| if ( $env:BUILD_DEBUG -eq 'true' ) { | |
| Set-PSDebug -Trace 1 | |
| } | |
| $workDir = Join-Path $env:RUNNER_TEMP 'php-build' | |
| $stagingDir = Join-Path $env:RUNNER_TEMP 'php-artifact' | |
| $phpArchive = Join-Path $workDir 'php.zip' | |
| $phpReleaseUrl = "https://windows.php.net/downloads/releases/php-$env:PHP_VERSION-nts-Win32-$env:PHP_WINDOWS_VS_VERSION-x64.zip" | |
| $phpArchiveUrl = "https://windows.php.net/downloads/releases/archives/php-$env:PHP_VERSION-nts-Win32-$env:PHP_WINDOWS_VS_VERSION-x64.zip" | |
| $xdebugUrl = "https://xdebug.org/files/php_xdebug-$env:XDEBUG_VERSION-$env:PHP_MINOR-nts-$env:PHP_WINDOWS_VS_VERSION-x86_64.dll" | |
| Remove-Item -Recurse -Force @($workDir, $stagingDir) -ErrorAction SilentlyContinue | |
| New-Item -ItemType Directory -Force $workDir, $stagingDir | Out-Null | |
| try { | |
| Invoke-WebRequest -Uri $phpReleaseUrl -OutFile $phpArchive -ErrorAction Stop | |
| } catch { | |
| Invoke-WebRequest -Uri $phpArchiveUrl -OutFile $phpArchive -ErrorAction Stop | |
| } | |
| Expand-Archive -Path $phpArchive -DestinationPath $stagingDir | |
| $xdebugPath = Join-Path $stagingDir 'ext\php_xdebug.dll' | |
| Invoke-WebRequest -Uri $xdebugUrl -OutFile $xdebugPath | |
| # Find the newest PECL release of $Name that publishes a Windows build | |
| # for the current PHP minor + VS toolchain. Pins like "apcu 5.1.24" go | |
| # stale every time PHP releases a new minor, so resolve at build time | |
| # by walking the directory listing on windows.php.net. | |
| function Find-LatestPECLVersion { | |
| param([Parameter(Mandatory)][string]$Name) | |
| $indexUrl = "https://downloads.php.net/~windows/pecl/releases/$Name/" | |
| $response = Invoke-WebRequest -Uri $indexUrl -UseBasicParsing -ErrorAction Stop | |
| $versions = [regex]::Matches( | |
| $response.Content, | |
| 'href="(\d+\.\d+\.\d+(?:[A-Za-z0-9.\-]*)?)/"' | |
| ) | ForEach-Object { $_.Groups[1].Value } | Select-Object -Unique | |
| if ( -not $versions ) { | |
| throw "No versions listed at $indexUrl" | |
| } | |
| $sorted = $versions | Sort-Object -Descending { | |
| $stable = ($_ -split '[A-Za-z\-]')[0] | |
| try { [version]$stable } catch { [version]'0.0.0' } | |
| } | |
| foreach ($v in $sorted) { | |
| $zipUrl = "https://downloads.php.net/~windows/pecl/releases/$Name/$v/php_$Name-$v-$env:PHP_MINOR-nts-$env:PHP_WINDOWS_VS_VERSION-x64.zip" | |
| try { | |
| Invoke-WebRequest -Uri $zipUrl -Method Head -UseBasicParsing -ErrorAction Stop | Out-Null | |
| Write-Host "PECL $Name -> $v (matches PHP $env:PHP_MINOR / $env:PHP_WINDOWS_VS_VERSION)" | |
| return $v | |
| } catch { | |
| # No build at this version for the current PHP minor / VS; try the next one | |
| } | |
| } | |
| throw "No Windows PECL build of $Name found for PHP $env:PHP_MINOR / $env:PHP_WINDOWS_VS_VERSION" | |
| } | |
| function Add-PECLExtension { | |
| param([Parameter(Mandatory)][string]$Name) | |
| $version = Find-LatestPECLVersion -Name $Name | |
| Write-Host "::group::Fetch PECL $Name $version" | |
| $url = "https://downloads.php.net/~windows/pecl/releases/$Name/$version/php_$Name-$version-$env:PHP_MINOR-nts-$env:PHP_WINDOWS_VS_VERSION-x64.zip" | |
| $zipPath = Join-Path $workDir "pecl-$Name.zip" | |
| $extractDir = Join-Path $workDir "pecl-$Name" | |
| if ( Test-Path $extractDir ) { Remove-Item -Recurse -Force $extractDir } | |
| Invoke-WebRequest -Uri $url -OutFile $zipPath -ErrorAction Stop | |
| Expand-Archive -Path $zipPath -DestinationPath $extractDir | |
| $extDll = Get-ChildItem -Path $extractDir -Filter "php_$Name.dll" -Recurse | Select-Object -First 1 | |
| if ( $null -eq $extDll ) { | |
| throw "Expected php_$Name.dll in PECL package for $Name $version" | |
| } | |
| Copy-Item -Path $extDll.FullName -Destination (Join-Path $stagingDir "ext\php_$Name.dll") -Force | |
| Get-ChildItem -Path $extractDir -Filter '*.dll' -Recurse | | |
| Where-Object { $_.Name -ne "php_$Name.dll" } | | |
| ForEach-Object { Copy-Item -Path $_.FullName -Destination (Join-Path $stagingDir $_.Name) -Force } | |
| Write-Host "::endgroup::" | |
| } | |
| Add-PECLExtension -Name apcu | |
| Add-PECLExtension -Name igbinary | |
| Add-PECLExtension -Name redis | |
| Add-PECLExtension -Name ssh2 | |
| Add-PECLExtension -Name yaml | |
| $runtimeJson = @{ | |
| phpVersion = $env:PHP_VERSION | |
| phpMinor = $env:PHP_MINOR | |
| threadSafety = 'nts' | |
| platform = $env:PHP_CLI_ARTIFACT_PLATFORM | |
| arch = $env:PHP_CLI_ARTIFACT_ARCH | |
| binary = 'php.exe' | |
| extensionDir = 'ext' | |
| xdebug = 'ext/php_xdebug.dll' | |
| } | ConvertTo-Json -Depth 4 | |
| Set-Content -Path (Join-Path $stagingDir 'runtime.json') -Value $runtimeJson | |
| $env:PATH = "$stagingDir;$env:PATH" | |
| & (Join-Path $stagingDir 'php.exe') -n ` | |
| -d "extension_dir=$(Join-Path $stagingDir 'ext')" ` | |
| -d "zend_extension=$xdebugPath" ` | |
| -d "xdebug.mode=debug" ` | |
| --ri xdebug | |
| if ( $LASTEXITCODE -ne 0 ) { | |
| exit $LASTEXITCODE | |
| } | |
| & (Join-Path $stagingDir 'php.exe') -n ` | |
| -d "extension_dir=$(Join-Path $stagingDir 'ext')" ` | |
| -d "extension=apcu" ` | |
| -d "extension=curl" ` | |
| -d "extension=gd" ` | |
| -d "extension=igbinary" ` | |
| -d "extension=mbstring" ` | |
| -d "extension=pdo" ` | |
| -d "extension=pdo_sqlite" ` | |
| -d "extension=redis" ` | |
| -d "extension=sodium" ` | |
| -d "extension=sqlite3" ` | |
| -d "extension=ssh2" ` | |
| -d "extension=yaml" ` | |
| -r "echo PHP_VERSION, PHP_EOL;" | |
| if ( $LASTEXITCODE -ne 0 ) { | |
| exit $LASTEXITCODE | |
| } | |
| - name: Package Windows artifact | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $outDir = Join-Path $env:GITHUB_WORKSPACE 'out/php-binaries' | |
| $stagingDir = Join-Path $env:RUNNER_TEMP 'php-artifact' | |
| $artifact = "php-$env:PHP_VERSION-cli-$env:PHP_CLI_ARTIFACT_PLATFORM-$env:PHP_CLI_ARTIFACT_ARCH.zip" | |
| $archive = Join-Path $outDir $artifact | |
| New-Item -ItemType Directory -Force $outDir | Out-Null | |
| Compress-Archive -Force -Path (Join-Path $stagingDir '*') -DestinationPath $archive | |
| (Get-FileHash $archive -Algorithm SHA256).Hash.ToLower() | Set-Content -NoNewline "$archive.sha256" | |
| - name: Upload PHP CLI artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: php-${{ env.PHP_VERSION }}-cli-${{ matrix.target }} | |
| path: out/php-binaries/* | |
| - name: Upload static-php-cli logs | |
| if: ${{ failure() && runner.os == 'macOS' }} | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: spc-logs-${{ matrix.target }} | |
| path: .cache/static-php-cli/log/*.log | |
| if-no-files-found: ignore | |
| publish-apps-cdn: | |
| name: Publish Apps CDN artifacts | |
| needs: build | |
| runs-on: ubuntu-latest | |
| env: | |
| PHP_VERSION: ${{ inputs.php_version }} | |
| PHP_CLI_VISIBILITY: ${{ inputs.apps_cdn_visibility }} | |
| DRY_RUN: ${{ inputs.upload_to_apps_cdn && 'false' || 'true' }} | |
| WPCOM_API_TOKEN: ${{ secrets.WPCOM_API_TOKEN }} | |
| steps: | |
| - name: Checkout Studio | |
| uses: actions/checkout@v6 | |
| - name: Setup Ruby | |
| uses: ruby/setup-ruby@v1 | |
| with: | |
| bundler-cache: true | |
| - name: Download PHP CLI artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: php-${{ env.PHP_VERSION }}-cli-* | |
| path: out/php-binaries | |
| merge-multiple: true | |
| - name: Publish PHP CLI artifacts to Apps CDN | |
| run: | | |
| set -euo pipefail | |
| if [ "$DRY_RUN" != "true" ] && [ -z "${WPCOM_API_TOKEN:-}" ]; then | |
| echo "WPCOM_API_TOKEN secret is required to upload PHP CLI artifacts to Apps CDN." | |
| exit 1 | |
| fi | |
| upload_results_file="${PWD}/out/php-binaries/apps-cdn-upload-results.json" | |
| bundle exec fastlane publish_php_cli_binaries \ | |
| version:"${PHP_VERSION}" \ | |
| artifacts_dir:"${PWD}/out/php-binaries" \ | |
| visibility:"${PHP_CLI_VISIBILITY}" \ | |
| output_file:"${upload_results_file}" | |
| echo "### Apps CDN PHP CLI upload" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| if [ "$DRY_RUN" = "true" ]; then | |
| echo "Dry run completed for PHP ${PHP_VERSION} CLI artifacts with ${PHP_CLI_VISIBILITY} visibility." >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "Uploaded PHP ${PHP_VERSION} CLI artifacts with ${PHP_CLI_VISIBILITY} visibility." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| PHP_CLI_UPLOAD_RESULTS_FILE="$upload_results_file" ruby -rjson -e ' | |
| results = JSON.parse(File.read(ENV.fetch("PHP_CLI_UPLOAD_RESULTS_FILE"))) | |
| File.open(ENV.fetch("GITHUB_STEP_SUMMARY"), "a") do |summary| | |
| summary.puts "| File | Apps CDN URL |" | |
| summary.puts "| --- | --- |" | |
| results.each do |file_name, result| | |
| url = result["cdn_url"].to_s | |
| url_output = url.empty? ? "Dry run (not uploaded)" : "<#{url}>" | |
| summary.puts "| `#{file_name}` | #{url_output} |" | |
| end | |
| end | |
| ' |