Build & Release TypePHP Webman #54
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 & Release TypePHP Webman | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| inputs: | |
| platform: | |
| description: Build target | |
| type: choice | |
| required: true | |
| default: all | |
| options: | |
| - all | |
| - linux | |
| - windows | |
| permissions: | |
| contents: write | |
| env: | |
| COMPOSER_NO_INTERACTION: 1 | |
| COMPOSER_PROCESS_TIMEOUT: 0 | |
| jobs: | |
| select-platform: | |
| name: Select build platform | |
| runs-on: ubuntu-22.04 | |
| outputs: | |
| matrix: ${{ steps.matrix.outputs.matrix }} | |
| steps: | |
| - name: Generate build matrix | |
| id: matrix | |
| shell: bash | |
| env: | |
| REQUESTED_PLATFORM: ${{ inputs.platform || 'linux' }} | |
| run: | | |
| case "$REQUESTED_PLATFORM" in | |
| linux) | |
| matrix='{"include":[{"os":"ubuntu-22.04","platform":"linux-x64","archive_name":"tinywan-typephp-webman-linux-x64.tar.gz"}]}' | |
| ;; | |
| windows) | |
| matrix='{"include":[{"os":"windows-latest","platform":"windows-x64","archive_name":"tinywan-typephp-webman-windows-x64.zip"}]}' | |
| ;; | |
| all) | |
| matrix='{"include":[{"os":"ubuntu-22.04","platform":"linux-x64","archive_name":"tinywan-typephp-webman-linux-x64.tar.gz"},{"os":"windows-latest","platform":"windows-x64","archive_name":"tinywan-typephp-webman-windows-x64.zip"}]}' | |
| ;; | |
| *) | |
| echo "Unsupported platform: $REQUESTED_PLATFORM" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| echo "matrix=$matrix" >> "$GITHUB_OUTPUT" | |
| build: | |
| needs: select-platform | |
| name: Build on ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.select-platform.outputs.matrix) }} | |
| env: | |
| PHPX_HOME: ${{ github.workspace }}/vendor/swoole/phpx | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@v4 | |
| - name: Load PHP Extensions List | |
| id: ext | |
| shell: bash | |
| run: | | |
| if [ "${{ runner.os }}" = "Linux" ] && [ -f "extensions.txt" ]; then | |
| EXT_LIST=$(grep -v '^[[:space:]]*#' extensions.txt | grep -v '^[[:space:]]*$' | tr '\r\n' ',' | sed 's/,,*/,/g; s/^,//; s/,$//') | |
| echo "Loaded Linux extensions: $EXT_LIST" | |
| echo "list=$EXT_LIST" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "list=zip" >> "$GITHUB_OUTPUT" | |
| fi | |
| # ---------------- Setup PHP 8.4 (参考官方配置 ZTS 与 embed 支持) ---------------- | |
| - name: Setup PHP | |
| uses: shivammathur/setup-php@v2 | |
| with: | |
| php-version: '8.4' | |
| extensions: ${{ steps.ext.outputs.list }} | |
| coverage: none | |
| ini-values: precision=17, memory_limit=4G, error_reporting=E_ERROR|E_WARNING, display_errors=1, display_startup_errors=1, log_errors=0 | |
| tools: composer:v2 | |
| env: | |
| fail-fast: true | |
| phpts: ts | |
| update: true | |
| # ---------------- Linux 构建环境准备 (官方原生依赖) ---------------- | |
| - name: Install Linux native build dependencies | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --yes build-essential cmake libgmp-dev libmpfr-dev pkg-config python3-dev patchelf | |
| - name: Patch php_hash.h for C++ compatibility | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| PHP_INC="$(php-config --include-dir 2>/dev/null || echo '')" | |
| echo "PHP include dir: $PHP_INC" | |
| if [ -n "$PHP_INC" ]; then | |
| sudo find "$PHP_INC" /usr/include -name "php_hash.h" -exec sed -i 's/static inline const php_hash_ops\* php_hash_fetch_ops/static inline const struct _php_hash_ops\* php_hash_fetch_ops/g' {} + 2>/dev/null || true | |
| fi | |
| - name: Configure Linux ZTS PHP embed library | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| php_home="$(php-config --prefix)" | |
| embed_library="${php_home}/lib/libphp.so" | |
| test -x "${php_home}/bin/php-config" | |
| test -f "${embed_library}" | |
| echo "PHP_HOME=${php_home}" >> "${GITHUB_ENV}" | |
| echo "Using ZTS PHP $(php-config --version) embed library: ${embed_library}" | |
| # ---------------- Windows 构建环境准备 ---------------- | |
| - name: Setup MSVC Toolchain | |
| if: runner.os == 'Windows' | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| - name: Restore Windows GMP and MPFR cache | |
| if: runner.os == 'Windows' | |
| id: cache-gmp-mpfr | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: ${{ runner.temp }}\typephp-cache\vcpkg-x64-windows | |
| key: windows-2022-msvc-vcpkg-gmp-mpfr-x64-v1 | |
| - name: Install Windows GMP and MPFR dependencies | |
| if: runner.os == 'Windows' && steps.cache-gmp-mpfr.outputs.cache-hit != 'true' | |
| shell: powershell | |
| run: | | |
| if (-not $env:VCPKG_INSTALLATION_ROOT) { | |
| throw "[CI] VCPKG_INSTALLATION_ROOT is not set on windows-latest" | |
| } | |
| $vcpkg = Join-Path $env:VCPKG_INSTALLATION_ROOT "vcpkg.exe" | |
| if (-not (Test-Path $vcpkg)) { | |
| throw "[CI] vcpkg executable not found: $vcpkg" | |
| } | |
| $triplet = "x64-windows" | |
| & $vcpkg install gmp mpfr --triplet=$triplet --clean-after-build | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "[CI] vcpkg failed to install GMP/MPFR (exit code $LASTEXITCODE)" | |
| } | |
| $nativeRoot = Join-Path $env:VCPKG_INSTALLATION_ROOT "installed\$triplet" | |
| $cacheRoot = Join-Path $env:RUNNER_TEMP "typephp-cache\vcpkg-x64-windows" | |
| New-Item -ItemType Directory -Force -Path "$cacheRoot\include", "$cacheRoot\lib", "$cacheRoot\bin" | Out-Null | |
| $requiredFiles = @( | |
| (Join-Path $nativeRoot "include\gmp.h"), | |
| (Join-Path $nativeRoot "include\mpfr.h"), | |
| (Join-Path $nativeRoot "lib\gmp.lib"), | |
| (Join-Path $nativeRoot "lib\gmpxx.lib"), | |
| (Join-Path $nativeRoot "lib\mpfr.lib") | |
| ) | |
| $missing = $requiredFiles | Where-Object { -not (Test-Path $_) } | |
| if ($missing) { | |
| Write-Host "[CI] vcpkg GMP/MPFR root: $nativeRoot" | |
| Get-ChildItem (Join-Path $nativeRoot "include") -ErrorAction SilentlyContinue | Select-Object Name | |
| Get-ChildItem (Join-Path $nativeRoot "lib") -ErrorAction SilentlyContinue | Select-Object Name | |
| throw "[CI] Missing GMP/MPFR files: $($missing -join ', ')" | |
| } | |
| Copy-Item "$nativeRoot\include\*" "$cacheRoot\include" -Recurse -Force | |
| Copy-Item "$nativeRoot\lib\gmp.lib", "$nativeRoot\lib\gmpxx.lib", "$nativeRoot\lib\mpfr.lib" "$cacheRoot\lib" -Force | |
| Copy-Item "$nativeRoot\bin\*.dll" "$cacheRoot\bin" -Force -ErrorAction SilentlyContinue | |
| Write-Host "[CI] Cached GMP/MPFR under: $cacheRoot" | |
| - name: Save Windows GMP and MPFR cache | |
| if: runner.os == 'Windows' && steps.cache-gmp-mpfr.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v4 | |
| with: | |
| path: ${{ runner.temp }}\typephp-cache\vcpkg-x64-windows | |
| key: ${{ steps.cache-gmp-mpfr.outputs.cache-primary-key }} | |
| - name: Stage Windows GMP and MPFR dependencies | |
| if: runner.os == 'Windows' | |
| shell: powershell | |
| run: | | |
| $cacheRoot = Join-Path $env:RUNNER_TEMP "typephp-cache\vcpkg-x64-windows" | |
| $requiredFiles = @( | |
| (Join-Path $cacheRoot "include\gmp.h"), | |
| (Join-Path $cacheRoot "include\mpfr.h"), | |
| (Join-Path $cacheRoot "lib\gmp.lib"), | |
| (Join-Path $cacheRoot "lib\gmpxx.lib"), | |
| (Join-Path $cacheRoot "lib\mpfr.lib") | |
| ) | |
| $missing = $requiredFiles | Where-Object { -not (Test-Path $_) } | |
| if ($missing) { | |
| throw "[CI] GMP/MPFR cache is incomplete: $($missing -join ', ')" | |
| } | |
| "GMP_MPFR_ROOT=$cacheRoot" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| Write-Host "[CI] Using cached GMP/MPFR root: $cacheRoot" | |
| # ---------------- 安装 Composer 依赖 ---------------- | |
| - name: Install Composer Dependencies | |
| run: composer install --prefer-dist --no-progress | |
| # ---------------- Linux 编译与打包 ---------------- | |
| - name: Build and Package on Linux | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| chmod +x package.sh start.sh | |
| ./package.sh | |
| echo "=== Verify dynamic library dependencies (ldd) ===" | |
| ldd dist/webman-server.bin || true | |
| ldd dist/libphpx.so || true | |
| echo "=== Linux Smoke Test: running dist/start.sh status ===" | |
| ./dist/start.sh status || true | |
| tar -czvf ${{ matrix.archive_name }} -C dist . | |
| # ---------------- Windows 编译与打包 ---------------- | |
| - name: Setup PHP Windows Devpack (Headers & Libs for MSVC) | |
| if: runner.os == 'Windows' | |
| shell: powershell | |
| run: | | |
| $phpBin = (Get-Command php.exe).Source | |
| $phpDir = Split-Path -Parent $phpBin | |
| Write-Host "PHP Directory: $phpDir" | |
| $phpVer = (php -r "echo PHP_VERSION;") | |
| Write-Host "[CI] Installed PHP version is $phpVer. Resolving Windows Devpack URL..." | |
| $urls = @( | |
| "https://windows.php.net/downloads/releases/php-devel-pack-$phpVer-Win32-vs17-x64.zip", | |
| "https://windows.php.net/downloads/releases/archives/php-devel-pack-$phpVer-Win32-vs17-x64.zip" | |
| ) | |
| $downloaded = $false | |
| foreach ($url in $urls) { | |
| try { | |
| Write-Host "[CI] Trying to download from $url ..." | |
| Invoke-WebRequest -Uri $url -OutFile "$env:TEMP\php-devel-pack.zip" -UseBasicParsing -ErrorAction Stop | |
| $downloaded = $true | |
| Write-Host "[CI] Download successful!" | |
| break | |
| } catch { | |
| Write-Host "[CI] URL not available, trying next..." | |
| } | |
| } | |
| if (-not $downloaded) { | |
| # 兜底抓取 releases 目录里的任一最新 8.4 devpack | |
| Write-Host "[CI] Querying index of windows.php.net/downloads/releases/ ..." | |
| $html = (Invoke-WebRequest -Uri "https://windows.php.net/downloads/releases/" -UseBasicParsing).Content | |
| if ($html -match 'href="([^"]*php-devel-pack-8\.4\.[^"]*-Win32-vs17-x64\.zip)"') { | |
| $fallbackUrl = "https://windows.php.net/downloads/releases/" + $matches[1] | |
| Write-Host "[CI] Found fallback devpack: $fallbackUrl" | |
| Invoke-WebRequest -Uri $fallbackUrl -OutFile "$env:TEMP\php-devel-pack.zip" -UseBasicParsing | |
| $downloaded = $true | |
| } | |
| } | |
| Expand-Archive -Path "$env:TEMP\php-devel-pack.zip" -DestinationPath "$env:TEMP\php-devpack" -Force | |
| $extracted = Get-ChildItem "$env:TEMP\php-devpack" -Directory | Select-Object -First 1 | |
| if ($extracted) { | |
| @("$phpDir\SDK\lib", "$phpDir\SDK\include", "$phpDir\dev", "$phpDir\lib", "$phpDir\include") | ForEach-Object { | |
| if (-not (Test-Path $_)) { New-Item -ItemType Directory -Path $_ -Force } | |
| } | |
| Copy-Item -Path "$($extracted.FullName)\lib\*" -Destination "$phpDir\SDK\lib\" -Recurse -Force -ErrorAction SilentlyContinue | |
| Copy-Item -Path "$($extracted.FullName)\include\*" -Destination "$phpDir\SDK\include\" -Recurse -Force -ErrorAction SilentlyContinue | |
| Copy-Item -Path "$($extracted.FullName)\lib\*" -Destination "$phpDir\lib\" -Recurse -Force -ErrorAction SilentlyContinue | |
| Copy-Item -Path "$($extracted.FullName)\lib\*" -Destination "$phpDir\dev\" -Recurse -Force -ErrorAction SilentlyContinue | |
| Copy-Item -Path "$($extracted.FullName)\lib\*" -Destination "$phpDir\" -Force -ErrorAction SilentlyContinue | |
| Copy-Item -Path "$($extracted.FullName)\include\*" -Destination "$phpDir\include\" -Recurse -Force -ErrorAction SilentlyContinue | |
| Write-Host "[CI] Successfully placed .lib files in $phpDir\SDK\lib and $phpDir" | |
| Get-ChildItem "$phpDir\SDK\lib" | Select-Object Name | |
| # phpx_compat.h includes the UCRT declaration before PHP headers. | |
| # Do not inject a second _wchmod declaration into ioutil.h: newer | |
| # Windows SDKs already declare it in corecrt_wio.h. | |
| Write-Host "[CI] Using the UCRT _wchmod declaration from corecrt_wio.h." | |
| } | |
| "PHP_HOME=$phpDir" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| - name: Build PHPX Library on Windows | |
| if: runner.os == 'Windows' | |
| shell: cmd | |
| run: | | |
| cd "%GITHUB_WORKSPACE%\vendor\swoole\phpx" | |
| if not exist "build" mkdir "build" | |
| cd build | |
| cmake .. -G "NMake Makefiles" -Dphp_dir="%PHP_HOME%" -DGMP_MPFR_ROOT="%GMP_MPFR_ROOT%" -DBUILD_TESTS=OFF -DBUILD_EXT=OFF -DCMAKE_CXX_FLAGS="/DNOMINMAX" | |
| if not exist "%GITHUB_WORKSPACE%\vendor\swoole\phpx\lib" mkdir "%GITHUB_WORKSPACE%\vendor\swoole\phpx\lib" | |
| nmake phpx | |
| if not exist "%GITHUB_WORKSPACE%\vendor\swoole\phpx\lib\phpx.lib" if exist "phpx.lib" copy /y "phpx.lib" "%GITHUB_WORKSPACE%\vendor\swoole\phpx\lib\" | |
| if not exist "%GITHUB_WORKSPACE%\vendor\swoole\phpx\lib\phpx.lib" ( | |
| echo [ERROR] PHPX import library was not produced by CMake. | |
| dir /s /b "%GITHUB_WORKSPACE%\vendor\swoole\phpx\*.lib" | |
| exit /b 1 | |
| ) | |
| copy /y "%GITHUB_WORKSPACE%\vendor\swoole\phpx\lib\phpx.lib" "%GITHUB_WORKSPACE%\vendor\swoole\phpx\" | |
| dir "%GITHUB_WORKSPACE%\vendor\swoole\phpx\lib" | |
| - name: Build and Package on Windows | |
| if: runner.os == 'Windows' | |
| shell: cmd | |
| run: | | |
| call package.bat | |
| powershell Compress-Archive -Path dist/* -DestinationPath ${{ matrix.archive_name }} | |
| # ---------------- 上传构建产物 (Artifacts) ---------------- | |
| - name: Upload Build Artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.archive_name }} | |
| path: ${{ matrix.archive_name }} | |
| # ---------------- 发布 GitHub Release ---------------- | |
| - name: Create Release and Upload Asset | |
| if: startsWith(github.ref, 'refs/tags/') | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: ${{ matrix.archive_name }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |