Build PHP CLI Binaries #37
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 | |
| 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 macOS dependency builds | |
| required: true | |
| default: 2.8.5 | |
| debug: | |
| description: Enable verbose build logs | |
| required: true | |
| type: boolean | |
| default: false | |
| 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 | |
| archive_extension: tar.gz | |
| binary_name: php | |
| - target: macos-x86_64 | |
| runner: macos-15-intel | |
| artifact_platform: macos | |
| artifact_arch: x86_64 | |
| archive_extension: tar.gz | |
| binary_name: php | |
| - target: windows-x86_64 | |
| runner: windows-latest | |
| artifact_platform: windows | |
| artifact_arch: x86_64 | |
| archive_extension: zip | |
| binary_name: php.exe | |
| env: | |
| PHP_VERSION: ${{ inputs.php_version }} | |
| XDEBUG_VERSION: ${{ inputs.xdebug_version }} | |
| SPC_REF: ${{ inputs.spc_ref }} | |
| PHP_CLI_ARTIFACT_PLATFORM: ${{ matrix.artifact_platform }} | |
| PHP_CLI_ARTIFACT_ARCH: ${{ matrix.artifact_arch }} | |
| PHP_CLI_ARCHIVE_EXTENSION: ${{ matrix.archive_extension }} | |
| PHP_CLI_BINARY_NAME: ${{ matrix.binary_name }} | |
| 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: Validate PHP version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "$PHP_VERSION" =~ ^8\.(2|3|4|5)\.[0-9]+$ ]]; then | |
| echo "Unsupported PHP version: $PHP_VERSION" >&2 | |
| echo "Supported PHP minors: 8.2, 8.3, 8.4, 8.5" >&2 | |
| exit 1 | |
| fi | |
| php_minor="${PHP_VERSION%.*}" | |
| echo "PHP_MINOR=$php_minor" >> "$GITHUB_ENV" | |
| - name: Install macOS build dependencies | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| brew update | |
| brew install \ | |
| autoconf \ | |
| automake \ | |
| bison \ | |
| cmake \ | |
| libtool \ | |
| ninja \ | |
| pkg-config \ | |
| re2c | |
| - 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 dependencies 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 | |
| php_extensions="bz2,curl,dom,gd,iconv,intl,mbstring,mysqli,openssl,pdo_mysql,pdo_sqlite,simplexml,sqlite3,xml,xmlreader,xmlwriter,zip,zlib" | |
| libraries="bzip2,curl,freetype,icu,libiconv,libjpeg,libpng,libxml2,libzip,onig,openssl,sqlite,zlib" | |
| php bin/spc doctor --auto-fix | |
| php bin/spc download \ | |
| --with-php="$PHP_VERSION" \ | |
| --for-extensions="$php_extensions" \ | |
| --for-libs="$libraries" \ | |
| --ignore-cache-sources=php-src | |
| php bin/spc build:libs "$libraries" | |
| - name: Build PHP runtime on macOS | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ "$BUILD_DEBUG" = "true" ]; then | |
| set -x | |
| fi | |
| source_url="https://www.php.net/distributions/php-${PHP_VERSION}.tar.gz" | |
| xdebug_url="https://xdebug.org/files/xdebug-${XDEBUG_VERSION}.tgz" | |
| work_dir="$RUNNER_TEMP/php-build" | |
| staging_dir="$RUNNER_TEMP/php-artifact" | |
| spc_root="$GITHUB_WORKSPACE/.cache/static-php-cli/buildroot" | |
| rm -rf "$work_dir" "$staging_dir" | |
| mkdir -p "$work_dir" "$staging_dir" | |
| curl --fail --location --retry 3 "$source_url" --output "$work_dir/php.tar.gz" | |
| tar -xzf "$work_dir/php.tar.gz" -C "$work_dir" | |
| spc_pkg_config="$(find "$GITHUB_WORKSPACE/.cache/static-php-cli/pkgroot" -path '*/bin/pkg-config' -type f -perm -111 | head -n 1)" | |
| if [ -z "$spc_pkg_config" ]; then | |
| echo "static-php-cli pkg-config was not found in pkgroot" >&2 | |
| exit 1 | |
| fi | |
| pkg_config_static="$work_dir/pkg-config-static" | |
| cat > "$pkg_config_static" <<SH | |
| #!/bin/sh | |
| exec "$spc_pkg_config" --static "\$@" | |
| SH | |
| chmod +x "$pkg_config_static" | |
| export PATH="$(brew --prefix bison)/bin:$spc_root/bin:$PATH" | |
| export PKG_CONFIG="$pkg_config_static" | |
| export PKG_CONFIG_LIBDIR="$spc_root/lib/pkgconfig" | |
| export PKG_CONFIG_PATH="$spc_root/lib/pkgconfig" | |
| export CPPFLAGS="-I$spc_root/include ${CPPFLAGS:-}" | |
| export LDFLAGS="-L$spc_root/lib ${LDFLAGS:-}" | |
| export LIBRARY_PATH="$spc_root/lib:${LIBRARY_PATH:-}" | |
| "$PKG_CONFIG" --version | |
| "$PKG_CONFIG" --modversion libxml-2.0 | |
| curl_frameworks="-framework CoreFoundation -framework CoreServices -framework SystemConfiguration" | |
| export CURL_CFLAGS="$("$PKG_CONFIG" --cflags libcurl)" | |
| export CURL_LIBS="$("$PKG_CONFIG" --libs libcurl) $curl_frameworks" | |
| export LIBS="$curl_frameworks ${LIBS:-}" | |
| cd "$work_dir/php-${PHP_VERSION}" | |
| curl_config="$PWD/ext/curl/config.m4" | |
| perl -0pi -e 's/(PHP_CHECK_LIBRARY\(\[curl\],\s*\[curl_easy_perform\],)/save_LIBS="\$LIBS"\n LIBS="\$LIBS \$CURL_LIBS"\n\1/s' "$curl_config" | |
| perl -0pi -e 's/(PHP_CHECK_LIBRARY\(\[curl\],\s*\[curl_easy_perform\],.*?\)\n)/\1 LIBS="\$save_LIBS"\n/s' "$curl_config" | |
| grep -q 'LIBS="$LIBS $CURL_LIBS"' "$curl_config" | |
| ./buildconf --force | |
| curl_static_link="$spc_root/lib/libcurl.a $curl_frameworks" | |
| CURL_STATIC_LINK="$curl_static_link" perl -0pi -e 's/-lcurl/$ENV{CURL_STATIC_LINK}/g' configure | |
| grep -Fq "$spc_root/lib/libcurl.a" configure | |
| ./configure \ | |
| --prefix="$staging_dir" \ | |
| --with-config-file-path="$staging_dir/etc" \ | |
| --with-config-file-scan-dir="$staging_dir/etc/conf.d" \ | |
| --disable-cgi \ | |
| --disable-debug \ | |
| --disable-phpdbg \ | |
| --enable-bcmath=shared \ | |
| --enable-calendar=shared \ | |
| --enable-cli \ | |
| --enable-ctype=shared \ | |
| --enable-dom=shared \ | |
| --enable-exif=shared \ | |
| --enable-fileinfo=shared \ | |
| --enable-filter=shared \ | |
| --enable-gd=shared \ | |
| --enable-intl=shared \ | |
| --enable-mbstring=shared \ | |
| --enable-opcache=shared \ | |
| --enable-pdo=shared \ | |
| --enable-phar=shared \ | |
| --enable-session=shared \ | |
| --enable-simplexml=shared \ | |
| --enable-tokenizer=shared \ | |
| --enable-xml=shared \ | |
| --enable-xmlreader=shared \ | |
| --enable-xmlwriter=shared \ | |
| --with-bz2=shared,$spc_root \ | |
| --with-curl \ | |
| --with-freetype \ | |
| --with-iconv=shared,$spc_root \ | |
| --with-jpeg \ | |
| --with-mysqli=shared,mysqlnd \ | |
| --with-openssl=shared \ | |
| --with-pdo-mysql=shared,mysqlnd \ | |
| --with-pdo-sqlite=shared \ | |
| --with-sqlite3=shared \ | |
| --with-zip=shared \ | |
| --with-zlib=shared,$spc_root | |
| grep -E 'CoreFoundation|SystemConfiguration|CoreServices' Makefile | |
| make -j"$(sysctl -n hw.ncpu)" | |
| make install | |
| mkdir -p "$staging_dir/etc/conf.d" | |
| curl --fail --location --retry 3 "$xdebug_url" --output "$work_dir/xdebug.tgz" | |
| tar -xzf "$work_dir/xdebug.tgz" -C "$work_dir" | |
| xdebug_source="$(find "$work_dir" -maxdepth 1 -type d -name 'xdebug-*' | head -n 1)" | |
| cd "$xdebug_source" | |
| "$staging_dir/bin/phpize" | |
| ./configure --with-php-config="$staging_dir/bin/php-config" | |
| make -j"$(sysctl -n hw.ncpu)" | |
| make install | |
| extension_dir="$("$staging_dir/bin/php-config" --extension-dir)" | |
| relative_extension_dir="${extension_dir#"$staging_dir"/}" | |
| 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": "bin/php", | |
| "extensionDir": "$relative_extension_dir", | |
| "libraryDir": "lib", | |
| "xdebug": "$relative_extension_dir/xdebug.so" | |
| } | |
| JSON | |
| "$staging_dir/bin/php" -n \ | |
| -d "extension_dir=$extension_dir" \ | |
| -d "zend_extension=$extension_dir/xdebug.so" \ | |
| -d "xdebug.mode=debug" \ | |
| --ri xdebug | |
| echo "Smoke testing built-in extension: curl" | |
| "$staging_dir/bin/php" -n \ | |
| -r 'if ( ! extension_loaded( "curl" ) ) { fwrite( STDERR, "curl is not loaded\n" ); exit( 1 ); } echo PHP_VERSION, PHP_EOL;' | |
| smoke_extensions=(gd mbstring pdo pdo_sqlite sqlite3) | |
| for extension in "${smoke_extensions[@]}"; do | |
| echo "Smoke testing extension: $extension" | |
| otool -L "$extension_dir/$extension.so" | |
| "$staging_dir/bin/php" -n \ | |
| -d "extension_dir=$extension_dir" \ | |
| -d "extension=$extension" \ | |
| -r 'echo PHP_VERSION, PHP_EOL;' | |
| done | |
| smoke_extension_args=() | |
| for extension in "${smoke_extensions[@]}"; do | |
| smoke_extension_args+=(-d "extension=$extension") | |
| echo "Smoke testing extension set: ${smoke_extensions[*]:0:${#smoke_extension_args[@]}}" | |
| "$staging_dir/bin/php" -n \ | |
| -d "extension_dir=$extension_dir" \ | |
| "${smoke_extension_args[@]}" \ | |
| -r 'echo PHP_VERSION, PHP_EOL;' | |
| done | |
| - name: Package macOS artifact | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| out_dir="$GITHUB_WORKSPACE/out/php-binaries" | |
| staging_dir="$RUNNER_TEMP/php-artifact" | |
| artifact="php-${PHP_VERSION}-cli-${PHP_CLI_ARTIFACT_PLATFORM}-${PHP_CLI_ARTIFACT_ARCH}.${PHP_CLI_ARCHIVE_EXTENSION}" | |
| mkdir -p "$out_dir" | |
| tar -czf "$out_dir/$artifact" -C "$staging_dir" . | |
| 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.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 | |
| $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=curl" ` | |
| -d "extension=gd" ` | |
| -d "extension=mbstring" ` | |
| -d "extension=pdo" ` | |
| -d "extension=pdo_sqlite" ` | |
| -d "extension=sqlite3" ` | |
| -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.$env:PHP_CLI_ARCHIVE_EXTENSION" | |
| $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/* |