Windows build #2
This file contains 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: Windows build | |
on: | |
workflow_dispatch: | |
inputs: | |
flavors: | |
description: "flavors, comma splited, empty for 'min,lite,max-swow', available: min, lite, max[-eventengine, like swow/swoole/libev], custom" | |
required: false | |
default: "" | |
archs: | |
description: "archs, comma splited, empty for all, available: x64, arm64" | |
required: false | |
default: "" | |
sapis: | |
description: "SAPIs, comma splited, empty for all, available: micro, micro-cli, cli" | |
required: false | |
default: "" | |
phpVers: | |
description: "PHP versions, empty for all, available: 8.0, 8.1, 8.2" | |
required: false | |
default: "" | |
customExtensions: | |
description: "custom extensions, used for custom flavor build" | |
required: false | |
default: "" | |
customLibraries: | |
description: "custom libraries, used for custom flavor build" | |
required: false | |
default: "" | |
uploadRelease: | |
description: "upload binaries to releases, otherwise only upload via artifact" | |
required: false | |
type: boolean | |
default: false | |
workflow_call: | |
inputs: | |
flavors: | |
description: "flavors, comma splited, empty for 'min,lite,max-swow', available: min, lite, max[-eventengine, like swow/swoole/libev], custom" | |
required: false | |
type: string | |
default: "" | |
archs: | |
description: "archs, comma splited, empty for all, available: x64, arm64" | |
required: false | |
type: string | |
default: "" | |
sapis: | |
description: "SAPIs, comma splited, empty for all, available: micro, micro-cli, cli" | |
required: false | |
type: string | |
default: "" | |
phpVers: | |
description: "PHP versions, empty for all, available: 8.0, 8.1, 8.2" | |
required: false | |
type: string | |
default: "" | |
customExtensions: | |
description: "custom extensions, used for custom flavor build" | |
required: false | |
type: string | |
default: "" | |
customLibraries: | |
description: "custom libraries, used for custom flavor build" | |
required: false | |
type: string | |
default: "" | |
uploadRelease: | |
description: "upload binaries to releases, otherwise only upload via artifact" | |
required: false | |
type: boolean | |
default: false | |
workflowRepo: | |
description: "repository of workflow to call" | |
required: true | |
type: string | |
workflowRev: | |
description: "revision of workflow to call" | |
required: true | |
type: string | |
jobs: | |
gen-jobs: | |
name: Generate jobs | |
runs-on: windows-latest | |
outputs: | |
jobs: ${{ steps.gen-jobs.outputs.jobs }} | |
permissions: | |
contents: write | |
steps: | |
- name: Generate jobs | |
id: gen-jobs | |
shell: php {0} | |
run: | | |
<?php | |
set_error_handler(function ($severity, $message, $file, $line) { | |
throw new ErrorException($message, 0, $severity, $file, $line); | |
}); | |
if (${{ inputs.uploadRelease }}) { | |
# create github release | |
echo "create release\n"; | |
$tagName = date('Ymd') . '-windows-${{ github.run_id }}'; | |
$context = stream_context_create([ | |
'http' => [ | |
'method' => 'POST', | |
'header' => implode("\r\n", [ | |
'Content-Type: application/json', | |
'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}', | |
'User-Agent: file_get_contents/0', | |
]), | |
'content' => json_encode([ | |
'tag_name' => $tagName, | |
'name' => $tagName, | |
'body' => "automatic release\n\nworkflow run: [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})", | |
#'draft' => true, | |
#'prerelease' => true, | |
]), | |
], | |
]); | |
file_get_contents('https://api.github.com/repos/${{ github.repository }}/releases', context: $context); | |
} | |
function arg2arr(string $arg): array | |
{ | |
return array_filter(array_map("trim", explode(',', $arg))); | |
} | |
$flavors = arg2arr(<<<'ARG' | |
${{ inputs.flavors }} | |
ARG); | |
$archs = arg2arr(<<<'ARG' | |
${{ inputs.archs }} | |
ARG); | |
$sapis = arg2arr(<<<'ARG' | |
${{ inputs.sapis }} | |
ARG); | |
$phpVers = arg2arr(<<<'ARG' | |
${{ inputs.phpVers }} | |
ARG); | |
if (!$flavors) { | |
$flavors = ['min', 'lite', 'max-swow']; | |
} | |
if (!$archs) { | |
$archs = ['x64', 'arm64']; | |
} | |
if (!$sapis) { | |
$sapis = ['micro', 'micro-cli', 'cli']; | |
} | |
if (!$phpVers) { | |
$phpVers = ['8.0', '8.1', '8.2']; | |
} | |
$customLibraries = <<<'ARG' | |
${{ inputs.customLibraries }} | |
ARG; | |
$customExtensions = <<<'ARG' | |
${{ inputs.customExtensions }} | |
ARG; | |
$customLibraries = trim($customLibraries); | |
$customExtensions = trim($customExtensions); | |
foreach ($archs as $arch) { | |
foreach ($phpVers as $phpVer) { | |
if ($arch === 'arm64' && version_compare($phpVer, '8.2', '<')) { | |
// arm64 only supported on PHP 8.2+ | |
echo "skip arm64 $phpVer\n"; | |
continue; | |
} | |
$job = [ | |
'flavors' => $flavors, | |
'customLibraries' => $customLibraries, | |
'customExtensions' => $customExtensions, | |
'arch' => $arch, | |
'sapis' => $sapis, | |
'phpVer' => $phpVer, | |
]; | |
$jobs[] = $job; | |
} | |
} | |
if (!$jobs) { | |
echo "no jobs generated\n"; | |
exit(1); | |
} | |
$json = json_encode($jobs); | |
file_put_contents(getenv('GITHUB_OUTPUT'), "jobs=$json"); | |
# $jsonDebug = <<<'JSON' | |
# [{ | |
# "flavors": [ | |
# "custom" | |
# ], | |
# "customLibraries": "", | |
# "customExtensions": "swow", | |
# "arch": "arm64", | |
# "sapis": [ | |
# "micro" | |
# ], | |
# "phpVer": "8.2" | |
# }] | |
# JSON; | |
# $json = json_encode(json_decode($jsonDebug, true)); | |
# file_put_contents(getenv('GITHUB_OUTPUT'), "jobs=$json"); | |
build: | |
name: ${{ matrix.phpVer }} ${{ matrix.arch }} ${{ toJson(matrix.flavors) }} | |
runs-on: windows-latest | |
# TODO: use docker | |
# container: "ghcr.io/dixyes/prepared-lwmbs:windows-${{ matrix.arch }}-src" | |
needs: | |
- gen-jobs | |
permissions: | |
contents: write | |
strategy: | |
max-parallel: 3 | |
fail-fast: false | |
matrix: | |
include: ${{ fromJson(needs.gen-jobs.outputs.jobs) }} | |
steps: | |
- name: Restore tools and sources | |
uses: actions/cache/restore@v3 | |
id: cache-restore | |
with: | |
path: | | |
C:\tools\php-sdk-binary-tools | |
C:\build\bin | |
C:\build\downloads | |
C:\build\src | |
C:\build\versionFile | |
key: ${{ runner.os }}-build-v1-${{ matrix.phpVer }}-${{ hashFiles('C:\build\versionFile') }} | |
restore-keys: | | |
${{ runner.os }}-build-v1-${{ matrix.phpVer }}- | |
- name: Prepare tools and sources | |
id: prepare | |
shell: pwsh | |
run: | | |
${env:GITHUB_TOKEN} = "${{ secrets.GITHUB_TOKEN }}" | |
# prepare lwmbs | |
New-Item -ItemType Container -Force C:\lwmbs | |
Set-Location C:\lwmbs | |
git init | |
if (0 -Ne $lastexitcode) { | |
exit 1 | |
} | |
git remote add origin https://github.com/${{ inputs.workflowRepo || github.repository }} | |
if (0 -Ne $lastexitcode) { | |
exit 1 | |
} | |
git fetch --depth 1 origin ${{ inputs.workflowRev || github.sha }} | |
if (0 -Ne $lastexitcode) { | |
exit 1 | |
} | |
git checkout FETCH_HEAD | |
if (0 -Ne $lastexitcode) { | |
exit 1 | |
} | |
if (-not (Test-Path C:\build\versionFile)) { | |
Write-Output "cache miss, prepare tools" | |
# install php-sdk-binary-tools | |
git clone --depth 1 --single-branch --branch master https://github.com/php/php-sdk-binary-tools C:\tools\php-sdk-binary-tools | |
if (0 -Ne $lastexitcode) { | |
exit 1 | |
} | |
# prepare nasm | |
Invoke-WebRequest -Uri "https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/win64/nasm-2.15.05-win64.zip" ` | |
-OutFile "nasm-2.15.05-win64.zip" | |
unzip nasm-2.15.05-win64.zip | |
if (0 -Ne $lastexitcode) { | |
exit 1 | |
} | |
New-Item -ItemType Container -Force C:\build\bin | |
Move-Item nasm-2.15.05/*.exe C:\build\bin\ | |
Move-Item nasm-2.15.05/rdoff/*.exe C:\build\bin\ | |
} | |
# download source | |
Set-Location C:\build | |
php C:\lwmbs\fetch_source.php ` | |
"" ` | |
"" ` | |
"--phpVer=${{ matrix.phpVer }}" ` | |
"--versionFile=C:\build\versionFile" | |
if (0 -Ne $lastexitcode) { | |
exit 1 | |
} | |
$srcHash = (Get-FileHash -Path C:\build\versionFile -Algorithm SHA256).Hash.ToLower() | |
if ("${{ steps.cache-restore.outputs.cache-matched-key }}".Contains($srcHash)) { | |
Write-Output "srcHash=" | Out-File -FilePath "${env:GITHUB_OUTPUT}" -Append | |
} else { | |
Write-Output "srcHash=$srcHash" | Out-File -FilePath "${env:GITHUB_OUTPUT}" -Append | |
} | |
Write-Output "PATH=${env:PATH};C:\build\bin" | Out-File -FilePath "${env:GITHUB_ENV}" -Append | |
- name: Save tools and sources | |
uses: actions/cache/save@v3 | |
if: steps.prepare.outputs.srcHash != '' | |
with: | |
path: | | |
C:\tools\php-sdk-binary-tools | |
C:\build\bin | |
C:\build\downloads | |
C:\build\src | |
C:\build\versionFile | |
key: ${{ runner.os }}-build-v1-${{ matrix.phpVer }}-${{ steps.prepare.outputs.srcHash }} | |
- name: Make build commands | |
shell: php {0} | |
run: | | |
<?php | |
$matrix = <<<'EOF' | |
${{ toJson(matrix) }} | |
EOF; | |
$matrix = json_decode($matrix, true); | |
$customLibraries = $matrix['customLibraries']; | |
$customExtensions = $matrix['customExtensions']; | |
$commands = []; | |
@mkdir('C:\build'); | |
$output = fopen('C:\build\build.ps1', 'w'); | |
$writeps = function (...$args) use ($output) { | |
fwrite($output, sprintf(...$args)); | |
fwrite(STDOUT, sprintf(...$args)); | |
}; | |
$writeps(<<<PWSH | |
# pwsh 7.3+ support this | |
\$PSNativeCommandArgumentPassing = 'Standard' | |
# NOTE: to build these binaries on your machine, you may require php8.1+, vs, git, cmake, 7z(-zstd/nanazip better), perl | |
\$buildDir = "C:\\build" | |
\$phpSdkDir = "C:\\tools\\php-sdk-binary-tools" | |
\$lwmbsDir = "C:\\lwmbs" | |
\$vsVer = "17" | |
\$outDir = "C:\\out" | |
Set-Location -Path \$buildDir | |
New-Item -Path \$buildDir -ItemType Directory -Force | |
# # prepare source for specified php version | |
# # you may select only libraries and extensions you will use | |
# # we donot do this because in actions it was cached | |
# & php \${lwmbsDir}\\fetch_source.php ` | |
# "" ` | |
# "" ` | |
# "--phpVer={$matrix['phpVer']}" ` | |
# "--versionFile=\${buildDir}\\versionFile" | |
# \$ret=\$LASTEXITCODE | |
# if (\$ret -ne 0) { | |
# Write-Host "::error::fetch source failed" | |
# exit 1 | |
# } | |
Write-Host "::group::Show versions" | |
Get-Content \${buildDir}\\versionFile | |
Get-FileHash -Path \${buildDir}\\versionFile -Algorithm SHA256 | |
Write-Host "::endgroup::" | |
PWSH); | |
$maxLibraries = [ | |
// compression | |
'zlib','zstd','bzip2','xz','brotli','libzip', | |
// curl and its deps | |
'nghttp2','libssh2','curl', | |
'libffi', | |
'openssl', | |
'onig', | |
'libyaml', | |
'libxml2', | |
// gd deps | |
'libpng', 'libjpegturbo', 'freetype', 'libwebp', | |
]; | |
$maxLibraries = implode(',', $maxLibraries); | |
$maxExtensions = [ | |
// xml things | |
'dom','xml','simplexml','xmlwriter','xmlreader', | |
'opcache', | |
'bcmath', | |
'phar','zip','zlib','bz2','zstd', | |
'mysqlnd','mysqli','pdo','pdo_mysql', | |
'mbstring','mbregex', | |
'session', | |
'ctype', | |
'fileinfo', | |
'filter', | |
'tokenizer', | |
'curl', | |
'ffi', | |
'redis', | |
'sockets', | |
'openssl', | |
'yaml', | |
'gd', | |
]; | |
$maxExtensions = implode(',', $maxExtensions); | |
foreach ($matrix['flavors'] as $flavor) { | |
$libraries = match ($flavor) { | |
'min' => 'libffi', | |
'lite' => 'zstd,zlib,libffi,libzip,bzip2,xz,onig', | |
'max', 'max-swow' => $maxLibraries, | |
'max-libev' => "{$maxLibraries},libev", | |
'max-swoole' => "{$maxLibraries},libstdc++", | |
'custom' => $customLibraries, | |
}; | |
$extensions = match ($flavor) { | |
'min' => 'ffi,filter,tokenizer,ctype', | |
'lite' => 'opcache,ffi,filter,tokenizer,ctype,mbstring,mbregex,sockets,zip,zstd,zlib,bz2,phar,fileinfo', | |
'max' => $maxExtensions, | |
'max-swow' => "{$maxExtensions},swow", | |
'max-swoole' => "{$maxExtensions},swoole", | |
'max-libev' => "{$maxExtensions},libev", | |
'custom' => $customExtensions, | |
}; | |
$writeps("\n\n"); | |
$writeps(<<<PWSH | |
# ----- "$flavor" flavor ----- | |
Write-Host "::group::Build $flavor libs" | |
# rebuild libs for this flavor to avoid cache | |
& php \${lwmbsDir}\build_libs.php ` | |
"$libraries" ` | |
"--phpBinarySDKDir=\$phpSdkDir" ` | |
"--vsVer=\$vsVer" ` | |
"--arch={$matrix['arch']}" ` | |
"--fresh" | |
\$ret=\$LASTEXITCODE | |
Write-Host "::endgroup::" | |
if (\$ret -ne 0) { | |
Write-Host "::error::failed build lib for $flavor" | |
} else { | |
PWSH); | |
foreach ($matrix['sapis'] as $sapi) { | |
$command = match ($sapi) { | |
'micro' => 'build_micro.php', | |
'micro-cli' => 'build_micro.php --fakeCli', | |
'cli' => 'build_cli.php', | |
}; | |
$targetBin = match ($sapi) { | |
'micro', 'micro-cli' => "\\src\\php-src\\{$matrix['arch']}\\Release_TS\\micro.sfx", | |
'cli' => "\\src\\php-src\\{$matrix['arch']}\\Release_TS\\php.exe", | |
}; | |
$binName = match ($sapi) { | |
'micro' => "micro.sfx", | |
'micro-cli' => "micro_cli.sfx", | |
'cli' => "php.exe", | |
}; | |
$targetBinPDB = match ($sapi) { | |
'micro', 'micro-cli' => "\\src\\php-src\\{$matrix['arch']}\\Release_TS\\micro.pdb", | |
'cli' => "\\src\\php-src\\{$matrix['arch']}\\Release_TS\\php.pdb", | |
}; | |
$binNamePDB = match ($sapi) { | |
'micro' => "micro.pdb", | |
'micro-cli' => "micro_cli.pdb", | |
'cli' => "php.pdb", | |
}; | |
// $writeps("\n"); | |
// $writeps("# cleam php build dir\n"); | |
$writeps("\n\n"); | |
$writeps(<<<PWSH | |
Write-Host "::group::Build $flavor $sapi" | |
# $sapi | |
# delete the built bin to avoid cache | |
Remove-Item -ErrorAction SilentlyContinue -Force ` | |
src/php-src/{$matrix['arch']}/Release_TS/sapi/micro/php_micro.obj | |
& php \${lwmbsDir}\\$command ` | |
"$libraries" ` | |
"$extensions" ` | |
"--phpBinarySDKDir=\$phpSdkDir" ` | |
"--vsVer=\$vsVer" ` | |
"--arch={$matrix['arch']}" | |
\$ret=\$LASTEXITCODE | |
if (\$ret -eq 0) { | |
# copy the built bin out | |
New-Item -Path "\$outDir\\{$flavor}" -ItemType Directory -Force | |
Copy-Item -Path \${buildDir}$targetBin -Destination "\$outDir\\{$flavor}\\$binName" -Force | |
Copy-Item -Path \${buildDir}$targetBinPDB -Destination "\$outDir\\{$flavor}\\$binNamePDB" -Force | |
} | |
Write-Host "::endgroup::" | |
if (\$ret -ne 0) { | |
Write-Host "::error::failed build $flavor $sapi" | |
} | |
PWSH); | |
} | |
$writeps("\n\n"); | |
$writeps(<<<PWSH | |
Write-Host "::group::Dump $flavor licenses" | |
# dump licenses | |
New-Item -Path "\$outDir\\{$flavor}/\\licenses" -ItemType Directory -Force | |
& php \${lwmbsDir}\\dump_licenses.php ` | |
"\$outDir\\{$flavor}/\\licenses" ` | |
"$libraries" ` | |
"$extensions" | |
# copy versionFile | |
Copy-Item -Path \${buildDir}\\versionFile -Destination "\$outDir\\{$flavor}\\versionFile" -Force | |
Write-Host "::endgroup::" | |
} | |
PWSH); | |
} | |
$writeps("\n"); | |
- name: Enable pwsh PSNativeCommandArgumentPassing | |
shell: pwsh | |
run: | | |
# github actions use pwsh 7.2, so we need to enable experimental feature | |
Enable-ExperimentalFeature -Name 'PSNativeCommandArgumentPassing' | |
# - name: Setup tmate session | |
# uses: mxschmitt/action-tmate@v3 | |
- name: Build | |
shell: pwsh | |
run: | | |
# fix swow source link | |
Remove-Item C:\build\src\php-src\ext\swow -Force | |
New-Item -ItemType SymbolicLink -Path "C:\build\src\php-src\ext\swow" -Target "C:\build\src\php-src\ext\swow-src\ext" | |
. C:\build\build.ps1 | |
# New-Item -Path C:\out\custom\licenses -ItemType Directory -Force | |
# Out-File -FilePath C:\out\custom\micro.sfx -Encoding Ascii -Force | |
# Out-File -FilePath C:\out\custom\micro.pdb -Encoding Ascii -Force | |
# Set-Location C:\build | |
# & php C:\lwmbs\dump_licenses.php ` | |
# "C:\out\custom\licenses" ` | |
# "libffi" ` | |
# "ffi" | |
# cp C:\build\versionFile C:\out\custom\versionFile | |
- name: Setup node | |
if: always() | |
uses: actions/setup-node@v3 | |
with: | |
node-version: '16' | |
- name: Prepare node for artifact upload | |
if: always() | |
run: | | |
& npm i -g ` | |
'@actions/core' ` | |
'@actions/github' ` | |
'@actions/artifact' ` | |
'@actions/exec' | |
$nodePath = & npm root --quiet -g | |
$nodePath = $nodePath.Trim() | |
Write-OutPut "NODE_PATH=$nodePath" | Out-File -FilePath "${env:GITHUB_ENV}" -Append | |
- name: Expose GitHub Runtime | |
if: always() | |
uses: crazy-max/ghaction-github-runtime@v2 | |
- name: Upload artifacts | |
if: always() | |
shell: node {0} | |
run: | | |
const context = { | |
matrix: ${{ toJson(matrix) }}, | |
github: ${{ toJson(github) }}, | |
inputs: ${{ toJson(inputs) }}, | |
} | |
const core = require('@actions/core'); | |
const { main } = require('C:\\lwmbs\\.github\\workflows\\upload.js') | |
main('${{ secrets.GITHUB_TOKEN }}', 'windows', context).catch(err => core.setFailed(err.message)) |