Linux build #9
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: Linux build | |
on: | |
workflow_dispatch: | |
inputs: | |
flavors: | |
description: "flavors, comma splited, empty for 'min,lite,max', available: min, lite, max[-eventengine, like swow/swoole/libev], [WIP]custom" | |
required: false | |
default: "" | |
archs: | |
description: "archs, comma splited, empty for all, available: x86_64, aarch64" | |
required: false | |
default: "" | |
sapis: | |
description: "SAPIs, comma splited, empty for all, available: micro, micro-cli, cli" | |
required: false | |
default: "" | |
libcs: | |
description: "libcs, comma splited, empty for all, available: musl, glibc" | |
required: false | |
default: "" | |
phpVers: | |
description: "PHP versions, empty for all, available: 8.0, 8.1, 8.2" | |
required: false | |
default: "" | |
customExtensions: | |
description: "[WIP]custom extensions, used for custom flavor build" | |
required: false | |
default: "" | |
customLibraries: | |
description: "[WIP]custom libraries, used for custom flavor build" | |
required: false | |
default: "" | |
schedule: | |
- cron: "33 4 * * *" | |
jobs: | |
gen-jobs: | |
name: Generate jobs | |
runs-on: ubuntu-latest | |
outputs: | |
jobs: ${{ steps.gen-jobs.outputs.jobs }} | |
steps: | |
- name: Generate jobs | |
id: gen-jobs | |
shell: php {0} | |
run: | | |
<?php | |
function arg2arr(string $arg): array | |
{ | |
return array_filter(array_map("trim", explode(',', $arg))); | |
} | |
$flavors = arg2arr(<<<'ARG' | |
${{ github.event.inputs.flavors }} | |
ARG); | |
$archs = arg2arr(<<<'ARG' | |
ARG); | |
$sapis = arg2arr(<<<'ARG' | |
${{ github.event.inputs.sapis }} | |
ARG); | |
$libcs = arg2arr(<<<'ARG' | |
${{ github.event.inputs.libcs }} | |
ARG); | |
$phpVers = arg2arr(<<<'ARG' | |
${{ github.event.inputs.phpVers }} | |
ARG); | |
if (!$flavors) { | |
$flavors = ['min', 'lite', 'max']; | |
} | |
if (!$archs) { | |
$archs = ['x86_64', 'aarch64']; | |
} | |
if (!$sapis) { | |
$sapis = ['micro', 'micro-cli', 'cli']; | |
} | |
if (!$libcs) { | |
$libcs = ['musl', 'glibc']; | |
} | |
if (!$phpVers) { | |
$phpVers = ['8.0', '8.1', '8.2']; | |
} | |
$customLibraries = <<<'ARG' | |
${{ github.event.inputs.customLibraries }} | |
ARG; | |
$customExtensions = <<<'ARG' | |
${{ github.event.inputs.customExtensions }} | |
ARG; | |
$customLibraries = trim($customLibraries); | |
$customExtensions = trim($customExtensions); | |
foreach ($archs as $arch) { | |
foreach ($libcs as $libc) { | |
foreach ($phpVers as $phpVer) { | |
$imageTag = "linux-${libc}-${arch}-src"; | |
$job = [ | |
'flavors' => $flavors, | |
'customLibraries' => $customLibraries, | |
'customExtensions' => $customExtensions, | |
'imageTag' => $imageTag, | |
'arch' => $arch, | |
'sapis' => $sapis, | |
'libc' => $libc, | |
'phpVer' => $phpVer, | |
]; | |
$jobs[] = $job; | |
} | |
} | |
} | |
$json = json_encode($jobs); | |
file_put_contents(getenv('GITHUB_OUTPUT'), "jobs=$json"); | |
$jsonDebug = <<<'JSON' | |
[{ | |
"flavors": [ | |
"min", | |
"lite", | |
"max" | |
], | |
"customLibraries": "", | |
"customExtensions": "", | |
"imageTag": "linux-glibc-x86_64-src", | |
"arch": "x86_64", | |
"sapis": [ | |
"micro", | |
"micro_cli", | |
"cli" | |
], | |
"libc": "musl", | |
"phpVer": "8.2" | |
}] | |
JSON; | |
$json = json_encode(json_decode($jsonDebug, true)); | |
file_put_contents(getenv('GITHUB_OUTPUT'), "jobs=$json"); | |
build: | |
name: Build ${{ matrix.phpVer }} ${{ matrix.libc }} ${{ matrix.arch }} ${{ toJson(matrix.flavors) }} | |
runs-on: ubuntu-latest | |
container: 'ghcr.io/dixyes/prepared-lwmbs:${{ matrix.imageTag }}' | |
needs: | |
- gen-jobs | |
strategy: | |
max-parallel: 6 | |
fail-fast: false | |
matrix: | |
include: ${{ fromJson(needs.gen-jobs.outputs.jobs) }} | |
steps: | |
- 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 = []; | |
$output = fopen('/tmp/build.sh', 'w'); | |
$writesh = function (...$args) use ($output) { | |
fwrite($output, sprintf(...$args)); | |
fwrite(STDOUT, sprintf(...$args)); | |
}; | |
$writesh(<<<BASH | |
#!/bin/sh | |
set -xeo pipefail | |
# NOTE: to build these binaries on your machine, just use dixyes/prepared-lwmbs:{$matrix['imageTag']} image | |
# prepare source for specified php version | |
# you may select only libraries and extensions you will use | |
php /lwmbs/fetch_source.php \ | |
"" \ | |
"" \ | |
--phpVer={$matrix['phpVer']} | |
BASH); | |
foreach ($matrix['flavors'] as $flavor) { | |
$libraries = match ($flavor) { | |
'min' => 'libffi', | |
'lite' => 'zstd,zlib,libffi,libzip,bzip2,xz,onig', | |
'max', 'max-swow', 'max-libev' => 'zstd,libssh2,curl,zlib,brotli,libffi,openssl,libzip,bzip2,nghttp2,onig,libyaml,xz,libxml2', | |
'max-swoole' => 'zstd,libssh2,curl,zlib,brotli,libffi,openssl,libzip,bzip2,nghttp2,onig,libyaml,xz,libxml2,libstdc++', | |
'custom' => $customLibraries, | |
}; | |
$extensions = match ($flavor) { | |
'min' => 'posix,pcntl,ffi,filter,tokenizer,ctype', | |
'lite' => 'opcache,posix,pcntl,ffi,filter,tokenizer,ctype,iconv,mbstring,mbregex,sockets,zip,zstd,zlib,bz2,phar,fileinfo', | |
'max' => 'iconv,dom,xml,simplexml,xmlwriter,xmlreader,opcache,bcmath,pdo,phar,mysqlnd,mysqli,pdo,pdo_mysql,mbstring,mbregex,session,ctype,fileinfo,filter,tokenizer,curl,ffi,redis,sockets,openssl,zip,zlib,bz2,yaml,zstd,posix,pcntl,sysvshm,sysvsem,sysvmsg', | |
'max-swow' => 'iconv,dom,xml,simplexml,xmlwriter,xmlreader,opcache,bcmath,pdo,phar,mysqlnd,mysqli,pdo,pdo_mysql,mbstring,mbregex,session,ctype,fileinfo,filter,tokenizer,curl,ffi,redis,sockets,openssl,zip,zlib,bz2,yaml,zstd,posix,pcntl,sysvshm,sysvsem,sysvmsg,swow', | |
'max-swoole' => 'iconv,dom,xml,simplexml,xmlwriter,xmlreader,opcache,bcmath,pdo,phar,mysqlnd,mysqli,pdo,pdo_mysql,mbstring,mbregex,session,ctype,fileinfo,filter,tokenizer,curl,ffi,redis,sockets,openssl,zip,zlib,bz2,yaml,zstd,posix,pcntl,sysvshm,sysvsem,sysvmsg,swoole', | |
'max-libev' => 'iconv,dom,xml,simplexml,xmlwriter,xmlreader,opcache,bcmath,pdo,phar,mysqlnd,mysqli,pdo,pdo_mysql,mbstring,mbregex,session,ctype,fileinfo,filter,tokenizer,curl,ffi,redis,sockets,openssl,zip,zlib,bz2,yaml,zstd,posix,pcntl,sysvshm,sysvsem,sysvmsg,libev', | |
'custom' => $customExtensions, | |
}; | |
$writesh("\n\n"); | |
$writesh(<<<BASH | |
# ----- "$flavor" flavor ----- | |
# rebuild libs for this flavor to avoid cache | |
php /lwmbs/build_libs.php \ | |
"$libraries" \ | |
"--cc=\$LWMBS_CC" \ | |
"--cxx=\$LWMBS_CXX" \ | |
"--arch={$matrix['arch']}" \ | |
"--fresh" | |
BASH); | |
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' => '/src/php-src/sapi/micro/micro.sfx', | |
'micro-cli' => '/src/php-src/sapi/micro/micro.sfx', | |
'cli' => '/src/php-src/sapi/cli/php', | |
}; | |
$binName = match ($sapi) { | |
'micro' => "micro.sfx", | |
'micro-cli' => "micro_cli.sfx", | |
'cli' => "php", | |
}; | |
// $writesh("\n"); | |
// $writesh("# cleam php build dir\n"); | |
$writesh("\n\n"); | |
$writesh(<<<BASH | |
# {$matrix['libc']}_shared $sapi | |
php /lwmbs/$command \ | |
"$libraries" \ | |
"$extensions" \ | |
"--cc=\$LWMBS_CC" \ | |
"--cxx=\$LWMBS_CXX" \ | |
"--arch={$matrix['arch']}" | |
# copy the built bin out | |
mkdir -p /out/{$flavor}_shared | |
cp $targetBin /out/{$flavor}_shared/$binName | |
cp $targetBin.debug /out/{$flavor}_shared/$binName.debug | |
BASH); | |
if ($matrix['libc'] == 'musl') { | |
$writesh("\n\n"); | |
$writesh(<<<BASH | |
# {$matrix['libc']}_static $sapi | |
php /lwmbs/$command \ | |
"$libraries" \ | |
"$extensions" \ | |
"--cc=\$LWMBS_CC" \ | |
"--cxx=\$LWMBS_CXX" \ | |
"--arch={$matrix['arch']}" \ | |
"--allStatic" | |
# copy the built bin out | |
mkdir -p /out/{$flavor}_static | |
cp $targetBin /out/{$flavor}_static/$binName | |
cp $targetBin.debug /out/{$flavor}_static/$binName.debug | |
BASH); | |
} | |
} | |
$writesh("\n\n"); | |
$writesh(<<<BASH | |
# dump licenses | |
php /lwmbs/dump_licenses.php \ | |
"/out/{$flavor}_shared/licenses" \ | |
"$libraries" \ | |
"$extensions" | |
php /lwmbs/dump_licenses.php \ | |
"/out/{$flavor}_static/licenses" \ | |
"$libraries" \ | |
"$extensions" | |
# copy versionFile | |
cp /versionFile "/out/{$flavor}_shared/versionFile" | |
cp /versionFile "/out/{$flavor}_static/versionFile" | |
BASH); | |
} | |
$writesh("\n"); | |
- name: Build | |
shell: bash | |
run: | | |
#. /tmp/build.sh | |
mkdir -p /out/min_shared | |
touch /out/min_shared/micro.sfx | |
php /lwmbs/dump_licenses.php \ | |
"/out/min_shared/licenses" \ | |
"libffi" \ | |
"ffi" | |
cp /versionFile "/out/min_shared/versionFile" | |
- name: Prepare gh actions for artifact upload | |
if: always() | |
run: | | |
npm i -g @actions/artifact @actions/core @actions/github | |
- name: Upload artifacts | |
if: always() | |
shell: node {0} | |
run: | | |
const fs = require('fs.promise'); | |
const core = require('@actions/core'); | |
// const github = require('@actions/github'); | |
const artifact = require('@actions/artifact'); | |
const matrix = JSON.parse(` | |
${{ toJson(matrix) }} | |
`) | |
const sapis = ['micro','micro_cli','cli']; | |
const flavors = ['min', 'lite', 'max', 'max_swow', 'max_swoole', 'max_libev'] | |
const binFile = { | |
'micro': 'micro.sfx', | |
'micro-cli': 'micro_cli.sfx', | |
'cli': 'php', | |
} | |
async function main() { | |
let client = artifact.create() | |
let artifacts = {}; | |
let results = {}; | |
for (const flavor of flavors) { | |
let staticDir = `/out/${flavor}_static`; | |
let sharedDir = `/out/${flavor}_shared`; | |
for (const sapi of sapis) { | |
let staticFile = `${staticDir}/${binFile[sapi]}`; | |
let sharedFile = `${sharedDir}/${binFile[sapi]}`; | |
if (await fs.exists(staticFile)) { | |
let artifactName = `${sapi}_static_${flavor}`; | |
artifacts[artifactName] = { | |
'file': staticFile, | |
'dir': staticDir, | |
}; | |
} | |
if (await fs.exists(sharedFile)) { | |
let artifactName = `${sapi}_shared_${flavor}`; | |
artifacts[artifactName] = { | |
'file': sharedFile, | |
'dir': sharedDir, | |
} | |
} | |
} | |
} | |
for (const [name, info] of Object.entries(artifacts)) { | |
let fileList = [ | |
info.file | |
]; | |
for (const file of await fs.readdir(`${info.dir}/licenses`)) { | |
fileList.push(`${info.dir}/${file}`); | |
} | |
fileList.push(`${info.dir}/versionFile`); | |
try { | |
let uploadResponse = client.uploadArtifact(name, fileList, info.dir); | |
results[name] = uploadResponse; | |
} catch (error) { | |
core.setFailed(error.message); | |
} | |
} | |
for (const [name, result] of Object.entries(results)) { | |
let res = await result; | |
console.log(`Artifact ${name} uploaded: ${res.artifactName}`); | |
} | |
} | |
main().catch(err => core.setFailed(err.message)); | |