🚀 Build PHP Ext Packs 7.2,7.3,7.4,8.0,8.1,8.2,8.3,8.4 #28
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 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!" |