Skip to content

🚀 Build PHP Ext Packs 7.2,7.3,7.4,8.0,8.1,8.2,8.3,8.4 #28

🚀 Build PHP Ext Packs 7.2,7.3,7.4,8.0,8.1,8.2,8.3,8.4

🚀 Build PHP Ext Packs 7.2,7.3,7.4,8.0,8.1,8.2,8.3,8.4 #28

Workflow file for this run

name: 🚀 Build PHP Extensions Packs
run-name: 🚀 Build PHP Ext Packs ${{ inputs.php-versions || 'all-in-matrix' }}
on:
workflow_dispatch:
inputs:
php-versions:
description: 'Which PHP versions to build (comma-separated), e.g.: 7.2,7.4,8.1,8.3. Default: all found in matrix.json'
required: false
ts:
description: 'Build types: ts, nts or ts,nts'
required: false
default: 'ts,nts'
permissions:
contents: write
jobs:
generate-matrix:
name: 🎯 Generate Build Matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.mk-matrix.outputs.matrix }}
release-tag: ${{ steps.release-info.outputs.tag }}
release-title: ${{ steps.release-info.outputs.title }}
steps:
- name: 🛡️ Checkout repository
uses: actions/checkout@v4
- name: 🏷️ Generate release info
id: release-info
shell: bash
run: |
TAG="ext-pack"
TITLE="Extensions Pack for PHP"
echo "🏷️ Release tag: $TAG"
echo "📝 Release title: $TITLE"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "title=$TITLE" >> "$GITHUB_OUTPUT"
- name: 🧮 Generate matrix from matrix.json
id: mk-matrix
shell: bash
run: |
set -euo pipefail
echo "🔍 Reading build configuration from matrix.json..."
PHP_VERSIONS_INPUT="${{ inputs.php-versions || '' }}"
TS_INPUT="${{ inputs.ts || 'ts,nts' }}"
# Extract all available PHP versions from matrix.json
ALL_PHP_VERS=$(
jq -r '
to_entries
| map(.value.ver | keys[])
| unique
| .[]
' extension/BuildPhpExtension/config/matrix.json
)
echo "📋 Available PHP versions in matrix: $(echo "$ALL_PHP_VERS" | tr '\n' ' ')"
# Process requested PHP versions
if [[ -n "$PHP_VERSIONS_INPUT" ]]; then
echo "🎯 Using specified PHP versions: $PHP_VERSIONS_INPUT"
IFS=',' read -ra REQ <<< "$PHP_VERSIONS_INPUT"
PHP_VERS=()
for v in "${REQ[@]}"; do
v_trim=$(echo "$v" | xargs)
if printf '%s\n' "${ALL_PHP_VERS[@]}" | grep -qx "$v_trim"; then
PHP_VERS+=("$v_trim")
echo "✅ PHP $v_trim - found in matrix"
else
echo "❌ PHP $v_trim - not found in matrix, skipping"
fi
done
else
echo "🌐 Using all available PHP versions from matrix"
readarray -t PHP_VERS <<< "$ALL_PHP_VERS"
fi
if [[ ${#PHP_VERS[@]} -eq 0 ]]; then
echo "💥 No valid PHP versions found to build"
exit 1
fi
echo "🔨 Final PHP versions to build: ${PHP_VERS[*]}"
IFS=',' read -ra TS_VALUES <<< "$TS_INPUT"
echo "⚙️ Thread safety modes: ${TS_VALUES[*]}"
# Build matrix using jq for proper JSON construction
MATRIX_ITEMS=()
for phpv in "${PHP_VERS[@]}"; do
for ts in "${TS_VALUES[@]}"; do
ts_trim=$(echo "$ts" | xargs)
MATRIX_ITEMS+=("{\"php\":\"${phpv}\",\"ts\":\"${ts_trim}\"}")
done
done
# Join array elements and create proper JSON
printf -v ITEMS_STR '%s,' "${MATRIX_ITEMS[@]}"
ITEMS_STR="${ITEMS_STR%,}" # Remove trailing comma
MATRIX_JSON="{\"include\":[${ITEMS_STR}]}"
echo "📊 Generated matrix with ${#MATRIX_ITEMS[@]} combinations"
echo "matrix=$MATRIX_JSON" >> "$GITHUB_OUTPUT"
build-and-release:
name: 🔧 Build ${{ matrix.php }}-${{ matrix.ts }}
runs-on: windows-2022
needs: generate-matrix
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
steps:
- name: 🛡️ Checkout repository
uses: actions/checkout@v4
- name: 🎯 Determine toolset and naming
id: toolset
shell: bash
run: |
set -euo pipefail
PHP="${{ matrix.php }}"
TS="${{ matrix.ts }}"
echo "🔍 Determining compiler toolset for PHP $PHP..."
# Compiler mapping:
# 7.x => vc15
# 8.0–8.3 => vs16
# 8.4–8.5 => vs17
major="${PHP%%.*}"
minor="${PHP#*.}"
tool=""
if [[ "$major" == "7" ]]; then
tool="vc15"
echo "🏗️ PHP 7.x detected - using Visual Studio 2017 (vc15)"
else
if [[ "$minor" -ge 4 ]]; then
tool="vs17"
echo "🏗️ PHP 8.4+ detected - using Visual Studio 2022 (vs17)"
else
tool="vs16"
echo "🏗️ PHP 8.0-8.3 detected - using Visual Studio 2019 (vs16)"
fi
fi
TARGET_DIR="php-${PHP}-${TS}"
echo "📁 Target directory: $TARGET_DIR"
echo "tool=$tool" >> "$GITHUB_OUTPUT"
echo "target=$TARGET_DIR" >> "$GITHUB_OUTPUT"
- name: 📁 Create target directory
shell: pwsh
run: |
$target = "${{ steps.toolset.outputs.target }}"
Write-Host "📁 Creating target directory: $target"
New-Item -ItemType Directory -Force -Path $target | Out-Null
Write-Host "✅ Directory created successfully"
- name: 📋 Build download plan from matrix.json
id: plan
shell: bash
run: |
set -euo pipefail
PHP="${{ matrix.php }}"
TS="${{ matrix.ts }}"
TOOL="${{ steps.toolset.outputs.tool }}"
echo "📋 Building download plan for PHP $PHP ($TS, $TOOL)..."
# Extract extensions and versions for this PHP version
DOWNLOADS=$(jq -r --arg php "$PHP" '
to_entries
| map({
ext: .key,
display_name: .key,
ver: (.value.ver[$php] // "")
})
| map(select(.ver != ""))[]
| @json
' extension/BuildPhpExtension/config/matrix.json)
extension_count=$(echo "$DOWNLOADS" | wc -l)
echo "🔍 Found $extension_count extensions in matrix for PHP $PHP"
PLAN_JSON='[]'
processed=0
while IFS= read -r row; do
if [[ -n "$row" ]]; then
ext=$(echo "$row" | jq -r '.ext' | tr '[:upper:]' '[:lower:]')
display_name=$(echo "$row" | jq -r '.display_name')
ver=$(echo "$row" | jq -r '.ver')
original_ext="$ext"
# Normalize extension names (same logic as pecl.yml)
case "$ext" in
"base58-php-ext") ext="base58" ;;
"dd-trace-php") ext="ddtrace" ;;
"msgpack-php") ext="msgpack" ;;
"php-firebird") ext="interbase" ;;
"php-ext-lz4") ext="lz4" ;;
"php-memcached") ext="memcached" ;;
"pecl-database-oci8") ext="oci8_19" ;;
"pecl-database-pdo_oci") ext="pdo_oci" ;;
"pecl-text-ssdeep") ext="ssdeep" ;;
esac
# Clean version from 'v' prefix
ver_clean=$(echo "$ver" | sed 's/^v//')
if [[ "$original_ext" != "$ext" ]]; then
echo "🔄 Normalized: $original_ext -> $ext"
fi
tag="${ext}-${ver_clean}"
file="php_${ext}-${ver_clean}-${PHP}-${TS}-${TOOL}-x64.zip"
url="https://github.com/OSPanel/php-windows-builder/releases/download/${tag}/${file}"
PLAN_JSON=$(echo "$PLAN_JSON" | jq --arg url "$url" --arg file "$file" --arg display_name "$display_name" '. + [{url:$url,file:$file,display_name:$display_name}]')
processed=$((processed + 1))
echo "📦 [$processed/$extension_count] Planned: $display_name v$ver_clean"
fi
done <<< "$DOWNLOADS"
echo "✅ Download plan completed with $processed extensions"
# Save plan to output
echo "$PLAN_JSON" > plan.json
{
echo "plan<<EOF"
cat plan.json
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: 📥 Download extension archives
shell: pwsh
run: |
$ErrorActionPreference = 'Continue'
$target = "${{ steps.toolset.outputs.target }}"
$phpVersion = "${{ matrix.php }}"
$tsMode = "${{ matrix.ts }}".ToUpper()
Write-Host "📥 Starting download process..." -ForegroundColor Cyan
Write-Host "📁 Target directory: $target" -ForegroundColor Gray
Write-Host "🔧 Configuration: PHP $phpVersion ($tsMode)" -ForegroundColor Gray
$planJson = '${{ steps.plan.outputs.plan }}'
$plan = $planJson | ConvertFrom-Json
if (-not (Test-Path $target)) {
New-Item -ItemType Directory -Path $target | Out-Null
}
$totalExtensions = $plan.Count
$downloadedCount = 0
$failedCount = 0
$failedExtensions = @()
Write-Host "🎯 Found $totalExtensions extensions to download for PHP $phpVersion ($tsMode)" -ForegroundColor Cyan
Write-Host ""
$counter = 0
foreach ($item in $plan) {
$counter++
$url = $item.url
$file = Join-Path $target $item.file
$extName = $item.display_name
Write-Host "📦 [$counter/$totalExtensions] Downloading: $extName (PHP $phpVersion $tsMode)" -ForegroundColor Yellow
try {
# Check URL availability first
$response = Invoke-WebRequest -Uri $url -Method Head -UseBasicParsing -ErrorAction Stop
if ($response.StatusCode -eq 200) {
Invoke-WebRequest -Uri $url -OutFile $file -UseBasicParsing -ErrorAction Stop
Write-Host "✅ Success: $extName downloaded successfully" -ForegroundColor Green
$downloadedCount++
}
} catch {
$statusCode = ""
if ($_.Exception.Response) {
$statusCode = " (Status: $($_.Exception.Response.StatusCode.value__))"
}
Write-Host "❌ Failed: $extName (PHP $phpVersion $tsMode)$statusCode" -ForegroundColor Red
Write-Host "🔗 URL: $url" -ForegroundColor DarkGray
$failedExtensions += "$extName (PHP $phpVersion $tsMode)$statusCode"
$failedCount++
# Continue processing other extensions - don't break the loop
continue
}
}
Write-Host ""
Write-Host "📊 Download Summary for PHP $phpVersion ($tsMode)" -ForegroundColor Cyan
Write-Host "===========================================" -ForegroundColor Cyan
Write-Host "✅ Successful: $downloadedCount extensions" -ForegroundColor Green
Write-Host "❌ Failed: $failedCount extensions" -ForegroundColor $(if ($failedCount -gt 0) { 'Red' } else { 'Green' })
if ($failedExtensions.Count -gt 0) {
Write-Host "💥 Failed extensions for PHP $phpVersion ($tsMode):" -ForegroundColor Red
foreach ($failed in $failedExtensions) {
Write-Host " - $failed" -ForegroundColor Red
}
}
# Only fail if NO extensions were downloaded at all
if ($downloadedCount -eq 0) {
Write-Host "💥 Critical: No extensions downloaded successfully for PHP $phpVersion ($tsMode)!" -ForegroundColor Red
Write-Host "🔄 Note: Other PHP/TS combinations will still be processed" -ForegroundColor Yellow
throw "Download process failed completely for this configuration"
}
Write-Host ""
Write-Host "🚀 Proceeding with $downloadedCount available extensions for PHP $phpVersion ($tsMode)..." -ForegroundColor Green
- name: 📂 Extract archives and cleanup
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$target = "${{ steps.toolset.outputs.target }}"
$phpVersion = "${{ matrix.php }}"
$tsMode = "${{ matrix.ts }}".ToUpper()
Write-Host "📂 Extracting downloaded archives for PHP $phpVersion ($tsMode)..." -ForegroundColor Cyan
$zipFiles = Get-ChildItem -Path $target -Filter *.zip
$zipCount = $zipFiles.Count
if ($zipCount -eq 0) {
Write-Host "❌ No ZIP files found to extract for PHP $phpVersion ($tsMode)" -ForegroundColor Red
throw "No archives to extract"
}
Write-Host "🎯 Found $zipCount archives to extract for PHP $phpVersion ($tsMode)" -ForegroundColor Yellow
$counter = 0
$zipFiles | ForEach-Object {
$counter++
$zipName = $_.Name -replace '\.zip$', ''
Write-Host "📦 [$counter/$zipCount] Extracting: $zipName" -ForegroundColor Yellow
Expand-Archive -LiteralPath $_.FullName -DestinationPath $target -Force
Write-Host "✅ Extracted: $zipName" -ForegroundColor Green
}
Write-Host ""
Write-Host "🧹 Cleaning up ZIP files for PHP $phpVersion ($tsMode)..." -ForegroundColor Cyan
Get-ChildItem -Path $target -Filter *.zip | Remove-Item -Force
Write-Host "✅ Cleanup completed for PHP $phpVersion ($tsMode)" -ForegroundColor Green
- name: 🔥 Download and process additional extensions
shell: pwsh
run: |
$ErrorActionPreference = 'Continue'
$target = "${{ steps.toolset.outputs.target }}"
$phpVersion = "${{ matrix.php }}"
$tsMode = "${{ matrix.ts }}".ToLower()
$tool = "${{ steps.toolset.outputs.tool }}"
Write-Host ""
Write-Host "🔥 Processing additional extensions for PHP $phpVersion ($($tsMode.ToUpper()))" -ForegroundColor Cyan
Write-Host "=================================================================" -ForegroundColor Cyan
# Create ext and 3rd-party directories if they don't exist
$extDir = Join-Path $target "ext"
$thirdPartyDir = Join-Path $target "3rd-party"
if (-not (Test-Path $extDir)) {
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
Write-Host "📁 Created ext directory" -ForegroundColor Green
}
if (-not (Test-Path $thirdPartyDir)) {
New-Item -ItemType Directory -Path $thirdPartyDir -Force | Out-Null
Write-Host "📁 Created 3rd-party directory" -ForegroundColor Green
}
# Define extension configurations
$extensionConfigs = @{
"blackfire" = @{
"name" = "Blackfire"
"urls" = @{
"nts" = @{
"7.2" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/72"
"7.3" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/73"
"7.4" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/74"
"8.0" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/80"
"8.1" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/81"
"8.2" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/82"
"8.3" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/83"
"8.4" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/84"
}
"ts" = @{
"7.2" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/72-zts"
"7.3" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/73-zts"
"7.4" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/74-zts"
"8.0" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/80-zts"
"8.1" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/81-zts"
"8.2" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/82-zts"
"8.3" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/83-zts"
"8.4" = "https://blackfire.io/api/v1/releases/probe/php/windows/amd64/84-zts"
}
}
"dll_name" = "blackfire_php.dll"
"target_name" = "php_blackfire.dll"
"process_additional_files" = $true
}
"ioncube" = @{
"name" = "Ioncube"
"urls" = @{
"nts" = @{
"7.2" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_nonts_vc15_x86-64.zip"
"7.3" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_nonts_vc15_x86-64.zip"
"7.4" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_nonts_vc15_x86-64.zip"
"8.1" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_nonts_vc16_x86-64.zip"
"8.2" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_nonts_vc16_x86-64.zip"
"8.3" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_nonts_vc16_x86-64.zip"
"8.4" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_nonts_vc17_x86-64.zip"
}
"ts" = @{
"7.2" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_vc15_x86-64.zip"
"7.3" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_vc15_x86-64.zip"
"7.4" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_vc15_x86-64.zip"
"8.1" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_vc16_x86-64.zip"
"8.2" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_vc16_x86-64.zip"
"8.3" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_vc16_x86-64.zip"
"8.4" = "https://downloads.ioncube.com/loader_downloads/ioncube_loaders_win_vc17_x86-64.zip"
}
}
"dll_name" = "ioncube_loader_win_$phpVersion.dll"
"target_name" = "php_ioncube.dll"
"process_additional_files" = $true
}
"firebird" = @{
"name" = "Firebird Client"
"urls" = @{
"common" = @{
"7.3" = "https://github.com/FirebirdSQL/firebird/releases/download/v3.0.13/Firebird-3.0.13.33818-0-x64.zip"
"7.4" = "https://github.com/FirebirdSQL/firebird/releases/download/v3.0.13/Firebird-3.0.13.33818-0-x64.zip"
"8.0" = "https://github.com/FirebirdSQL/firebird/releases/download/v4.0.6/Firebird-4.0.6.3221-0-x64.zip"
"8.1" = "https://github.com/FirebirdSQL/firebird/releases/download/v4.0.6/Firebird-4.0.6.3221-0-x64.zip"
"8.2" = "https://github.com/FirebirdSQL/firebird/releases/download/v4.0.6/Firebird-4.0.6.3221-0-x64.zip"
"8.3" = "https://github.com/FirebirdSQL/firebird/releases/download/v4.0.6/Firebird-4.0.6.3221-0-x64.zip"
"8.4" = "https://github.com/FirebirdSQL/firebird/releases/download/v4.0.6/Firebird-4.0.6.3221-0-x64.zip"
}
}
"dll_name" = "fbclient.dll"
"target_name" = "fbclient.dll"
"target_location" = "root"
"process_additional_files" = $false
}
}
$successCount = 0
$failCount = 0
foreach ($extKey in $extensionConfigs.Keys) {
$config = $extensionConfigs[$extKey]
$extName = $config.name
Write-Host ""
Write-Host "🔥 Processing $extName..." -ForegroundColor Yellow
# Determine which URL structure to use
$urlSource = $null
if ($config.urls.ContainsKey($tsMode)) {
$urlSource = $config.urls[$tsMode]
} elseif ($config.urls.ContainsKey("common")) {
$urlSource = $config.urls["common"]
}
# Check if URL exists for this PHP version and TS mode
if (-not $urlSource -or -not $urlSource.ContainsKey($phpVersion)) {
Write-Host "⚠️ Skipping ${extName}: No URL configured for PHP $phpVersion ($($tsMode.ToUpper()))" -ForegroundColor Yellow
continue
}
$url = $urlSource[$phpVersion]
$dllName = $config.dll_name
$targetName = $config.target_name
try {
# Create temporary directory
$tmpDir = "$env:TEMP\${extKey}_$(Get-Random)"
$archivePath = if ($url.EndsWith(".zip")) { "$tmpDir.zip" } else { "$tmpDir.archive" }
Write-Host "📥 Downloading $extName for PHP $phpVersion ($($tsMode.ToUpper()))..." -ForegroundColor Gray
Write-Host "🔗 $url" -ForegroundColor DarkGray
# Download archive
Invoke-WebRequest -Uri $url -OutFile $archivePath -UseBasicParsing -ErrorAction Stop
$fileSize = [math]::Round((Get-Item $archivePath).Length / 1MB, 2)
Write-Host "✅ Downloaded $extName successfully ($fileSize MB)" -ForegroundColor Green
# Extract archive
Write-Host "📂 Extracting $extName archive..." -ForegroundColor Gray
if ($url.EndsWith(".zip")) {
Expand-Archive -Path $archivePath -DestinationPath $tmpDir -Force
} else {
# For non-zip archives, try to extract anyway
try {
Expand-Archive -Path $archivePath -DestinationPath $tmpDir -Force
} catch {
Write-Host "⚠️ Could not extract $extName as ZIP, trying to copy as-is" -ForegroundColor Yellow
New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null
Copy-Item $archivePath $tmpDir
}
}
# Find the DLL file
$dllFile = Get-ChildItem -Path $tmpDir -File -Recurse |
Where-Object { $_.Name -eq $dllName } |
Select-Object -First 1
if (-not $dllFile) {
Write-Host "❌ ${extName}: DLL file '$dllName' not found in archive" -ForegroundColor Red
Write-Host "📋 Available files in archive:" -ForegroundColor DarkGray
Get-ChildItem -Path $tmpDir -File -Recurse | ForEach-Object {
Write-Host " - $($_.Name)" -ForegroundColor DarkGray
}
$failCount++
continue
}
# Determine target location
$targetLocation = if ($config.ContainsKey("target_location") -and $config.target_location -eq "root") {
$target
} else {
$extDir
}
$targetDllPath = Join-Path $targetLocation $targetName
# Copy DLL to target location
Copy-Item -Path $dllFile.FullName -Destination $targetDllPath -Force
$dllSize = [math]::Round((Get-Item $targetDllPath).Length / 1KB, 2)
Write-Host "✅ Copied $targetName ($dllSize KB)" -ForegroundColor Green
# Process remaining files (move to 3rd-party directory) - only for extensions that need it
if ($config.process_additional_files) {
$extThirdPartyDir = Join-Path $thirdPartyDir $extKey
if (-not (Test-Path $extThirdPartyDir)) {
New-Item -ItemType Directory -Path $extThirdPartyDir -Force | Out-Null
}
$remainingFiles = Get-ChildItem -Path $tmpDir -Recurse -File |
Where-Object { $_.FullName -ne $dllFile.FullName }
$movedCount = 0
foreach ($file in $remainingFiles) {
try {
$relativePath = $file.FullName.Substring($tmpDir.Length + 1)
$targetPath = Join-Path $extThirdPartyDir $relativePath
# Create subdirectory if needed
$targetDir = Split-Path $targetPath -Parent
if (-not (Test-Path $targetDir)) {
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
}
Copy-Item -Path $file.FullName -Destination $targetPath -Force
$movedCount++
} catch {
Write-Host "⚠️ Error copying file $($file.Name)" -ForegroundColor Yellow
}
}
if ($movedCount -gt 0) {
Write-Host "📁 Moved $movedCount additional files to 3rd-party/$extKey" -ForegroundColor Green
} else {
Write-Host "📁 No additional files to process for $extName" -ForegroundColor Gray
}
} else {
Write-Host "📁 Skipping additional files processing for $extName (only DLL needed)" -ForegroundColor Gray
}
# Cleanup temporary files
Remove-Item $tmpDir, $archivePath -Recurse -Force -ErrorAction SilentlyContinue
Write-Host "✅ $extName processed successfully" -ForegroundColor Green
$successCount++
} catch {
Write-Host "❌ Error processing $extName" -ForegroundColor Red
Write-Host "🔗 URL was: $url" -ForegroundColor DarkGray
Write-Host "💥 Error: $($_.Exception.Message)" -ForegroundColor Red
$failCount++
# Cleanup on error
Remove-Item $tmpDir, $archivePath -Recurse -Force -ErrorAction SilentlyContinue
}
}
Write-Host ""
Write-Host "📊 Additional Extensions Summary for PHP $phpVersion ($($tsMode.ToUpper()))" -ForegroundColor Cyan
Write-Host "=================================================================" -ForegroundColor Cyan
Write-Host "✅ Successful: $successCount extensions" -ForegroundColor Green
Write-Host "❌ Failed: $failCount extensions" -ForegroundColor $(if ($failCount -gt 0) { 'Red' } else { 'Green' })
Write-Host ""
- name: 🔧 Configure delegates.xml
shell: pwsh
run: |
$target = "${{ steps.toolset.outputs.target }}"
$phpVersion = "${{ matrix.php }}"
$tsMode = "${{ matrix.ts }}".ToUpper()
Write-Host "🔧 Configuring delegates.xml for PHP $phpVersion ($tsMode)..." -ForegroundColor Cyan
$delegatesPath = Join-Path $target "config\delegates.xml"
if (Test-Path $delegatesPath) {
Write-Host "📝 Found delegates.xml, updating PSDelegate references..." -ForegroundColor Yellow
$content = Get-Content $delegatesPath -Raw -Encoding UTF8
$updatedContent = $content -replace '@PSDelegate@', 'gswin64c.exe'
Set-Content $delegatesPath $updatedContent -Encoding UTF8
Write-Host "✅ Updated delegates.xml: @PSDelegate@ -> gswin64c.exe" -ForegroundColor Green
} else {
Write-Host "⚠️ delegates.xml not found at: $delegatesPath" -ForegroundColor Yellow
}
- name: 📦 Pack final result
id: pack
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$php = "${{ matrix.php }}"
$ts = "${{ matrix.ts }}"
$tool = "${{ steps.toolset.outputs.tool }}"
$target = "${{ steps.toolset.outputs.target }}"
Write-Host "📦 Creating final extension pack for PHP $php ($($ts.ToUpper()))..." -ForegroundColor Cyan
# Generate archive name: php-ext-pack-<php>-<ts>-Win32-<tool>-x64.zip
$zipName = "php-ext-pack-$php-$ts-Win32-$tool-x64.zip"
Write-Host "📄 Archive name: $zipName" -ForegroundColor Yellow
if (Test-Path $zipName) {
Write-Host "🗑️ Removing existing archive..." -ForegroundColor Gray
Remove-Item $zipName -Force
}
Write-Host "🔨 Compressing extensions..." -ForegroundColor Yellow
Compress-Archive -Path (Join-Path $target '*') -DestinationPath $zipName
$archiveSize = [math]::Round((Get-Item $zipName).Length / 1MB, 2)
Write-Host "✅ Archive created successfully: $zipName ($archiveSize MB)" -ForegroundColor Green
Write-Output "zip=$zipName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: 📤 Upload artifact for release
uses: actions/upload-artifact@v4
with:
name: ${{ steps.pack.outputs.zip }}
path: ${{ steps.pack.outputs.zip }}
retention-days: 1
- name: ✅ Build completed
run: |
echo "🎉 Build completed successfully!"
echo "📊 Configuration: PHP ${{ matrix.php }} (${{ matrix.ts }})"
echo "🔧 Toolset: ${{ steps.toolset.outputs.tool }}"
echo "📦 Archive: ${{ steps.pack.outputs.zip }}"
echo "⏰ Completion time: $(date)"
echo ""
echo "🚀 Extension pack ready for release!"
create-unified-release:
name: 🚀 Create Unified Release
runs-on: ubuntu-latest
needs: [generate-matrix, build-and-release]
if: always() && (needs.build-and-release.result == 'success' || contains(needs.build-and-release.result, 'success'))
steps:
- name: 🛡️ Checkout repository
uses: actions/checkout@v4
- name: 📥 Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: 📋 List downloaded artifacts
shell: bash
run: |
echo "📋 Downloaded artifacts:"
find ./artifacts -type f -name "*.zip" | while read -r file; do
size=$(du -h "$file" | cut -f1)
echo "📦 $(basename "$file") - $size"
done
- name: 🚀 Create unified release
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
RELEASE_TAG="${{ needs.generate-matrix.outputs.release-tag }}"
RELEASE_TITLE="${{ needs.generate-matrix.outputs.release-title }}"
echo "🚀 Creating unified release..."
echo "🏷️ Tag: $RELEASE_TAG"
echo "📝 Title: $RELEASE_TITLE"
# Count artifacts
ARTIFACT_COUNT=$(find ./artifacts -type f -name "*.zip" | wc -l)
echo "📦 Found $ARTIFACT_COUNT extension packs to release"
if [ "$ARTIFACT_COUNT" -eq 0 ]; then
echo "❌ No artifacts found to release"
exit 1
fi
# Generate release body
RELEASE_BODY="🔧 **PHP Extensions Pack Collection**
This release contains pre-compiled PHP extension packs for Windows (x64) across multiple PHP versions and thread safety modes.
📦 **Package Contents:**
- $ARTIFACT_COUNT extension packs
- Support for multiple PHP versions (7.x, 8.x)
- Both Thread Safe (TS) and Non-Thread Safe (NTS) variants
- Compiled with appropriate Visual Studio toolsets
⚡ **Quick Installation:**
1. Download the appropriate pack for your PHP version and thread safety mode
2. Extract the archive to your PHP installation directory
3. Restart your web server
🛠️ **Toolset Mapping:**
- \`vc15\` - Visual Studio 2017 (PHP 7.x)
- \`vs16\` - Visual Studio 2019 (PHP 8.0-8.3)
- \`vs17\` - Visual Studio 2022 (PHP 8.4+)
🤖 **Auto-generated** by GitHub Actions on $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
# Delete existing release if it exists
if gh release view "$RELEASE_TAG" -R "${{ github.repository }}" >/dev/null 2>&1; then
echo "🗑️ Deleting existing release: $RELEASE_TAG"
gh release delete "$RELEASE_TAG" -R "${{ github.repository }}" -y
fi
# Create new release
echo "✨ Creating new release: $RELEASE_TAG"
gh release create "$RELEASE_TAG" \
-t "$RELEASE_TITLE" \
-n "$RELEASE_BODY" \
-R "${{ github.repository }}"
# Upload all artifacts
echo "📤 Uploading artifacts to release..."
counter=0
find ./artifacts -type f -name "*.zip" | while read -r file; do
counter=$((counter + 1))
filename=$(basename "$file")
echo "📦 [$counter/$ARTIFACT_COUNT] Uploading: $filename"
gh release upload "$RELEASE_TAG" "$file" -R "${{ github.repository }}"
echo "✅ Uploaded: $filename"
done
echo ""
echo "🎉 Unified release created successfully!"
echo "📊 Repository: ${{ github.repository }}"
echo "🏷️ Release: $RELEASE_TAG"
echo "📦 Artifacts: $ARTIFACT_COUNT extension packs"
echo "🌐 URL: https://github.com/${{ github.repository }}/releases/tag/$RELEASE_TAG"
- name: 🎉 Release completed
run: |
echo "🎉 All extension packs published successfully!"
echo "📊 Release: ${{ needs.generate-matrix.outputs.release-tag }}"
echo "⏰ Completion time: $(date)"
echo ""
echo "🚀 Extension packs are now available for download!"