Skip to content

Linux build

Linux build #882

Workflow file for this run

name: Linux 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: 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.1, 8.2, 8.3"
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: x86_64, aarch64"
required: false
type: string
default: ""
sapis:
description: "SAPIs, comma splited, empty for all, available: micro, micro-cli, cli"
required: false
type: string
default: ""
libcs:
description: "libcs, comma splited, empty for all, available: musl, glibc"
required: false
type: string
default: ""
phpVers:
description: "PHP versions, empty for all, available: 8.1, 8.2, 8.3"
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: ubuntu-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') . '-linux-${{ 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);
$libcs = arg2arr(<<<'ARG'
${{ inputs.libcs }}
ARG);
$phpVers = arg2arr(<<<'ARG'
${{ inputs.phpVers }}
ARG);
if (!$flavors) {
$flavors = ['min', 'lite', 'max-swow'];
}
if (!$archs) {
$archs = ['x86_64', 'aarch64'];
}
if (!$sapis) {
$sapis = ['micro', 'micro-cli', 'cli'];
}
if (!$libcs) {
$libcs = ['musl', 'glibc'];
}
if (!$phpVers) {
$phpVers = ['8.1', '8.2', '8.3'];
}
$customLibraries = <<<'ARG'
${{ inputs.customLibraries }}
ARG;
$customExtensions = <<<'ARG'
${{ 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}";
$job = [
'flavors' => $flavors,
'customLibraries' => $customLibraries,
'customExtensions' => $customExtensions,
'imageTag' => $imageTag,
'arch' => $arch,
'sapis' => $sapis,
'libc' => $libc,
'phpVer' => $phpVer,
'volumes' => [],
];
if ($imageTag === "linux-glibc-x86_64") {
$job['volumes'] = [
'/node20217:/node20217:rw,rshared',
'/node20217:/__e/node20:ro,rshared',
];
}
$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": [
# "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: ${{ matrix.phpVer }} ${{ matrix.libc }} ${{ matrix.arch }} ${{ toJson(matrix.flavors) }}
runs-on: ubuntu-latest
container:
image: 'ghcr.io/dixyes/prepared-lwmbs:${{ matrix.imageTag }}'
volumes: ${{ matrix.volumes }}
needs:
- gen-jobs
permissions:
contents: write
strategy:
max-parallel: 6
fail-fast: false
matrix:
include: ${{ fromJson(needs.gen-jobs.outputs.jobs) }}
steps:
# https://github.com/actions/runner/issues/2906
- name: Setup node 20 for centos:7
if: matrix.imageTag == 'linux-glibc-x86_64'
shell: bash
run: |
cp -r /opt/nodejs/* /node20217
- name: Restore sources
uses: actions/cache/restore@v4
id: cache-restore
with:
path: |
/downloads
#/src
/versionFile
key: ${{ runner.os }}-build-v1-${{ matrix.phpVer }}-${{ hashFiles('/versionFile') }}
restore-keys: |
${{ runner.os }}-build-v1-${{ matrix.phpVer }}-
- name: Prepare lwmbs and sources
id: prepare
run: |
export GITHUB_TOKEN="${{ secrets.GITHUB_TOKEN }}"
# prepare lwmbs
cd /lwmbs
git remote set-url origin "https://github.com/${{ inputs.workflowRepo || github.repository }}"
git fetch origin ${{ inputs.workflowRev || github.sha }}
git checkout FETCH_HEAD
cd /
php /lwmbs/fetch_source.php \
"" \
"" \
"--phpVer=${{ matrix.phpVer }}" \
"--versionFile=/versionFile"
matched_key="${{ steps.cache-restore.outputs.cache-matched-key }}"
src_hash="$(sha256sum /versionFile | head -c 64)"
if [ "$matched_key" != "${matched_key##*${src_hash}}" ]
then
echo "srcHash=" >> "$GITHUB_OUTPUT"
else
echo "srcHash=${src_hash}" >> "$GITHUB_OUTPUT"
fi
- name: Save sources
uses: actions/cache/save@v4
if: steps.prepare.outputs.srcHash != ''
with:
path: |
/downloads
#/src
/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 = [];
$output = fopen('/tmp/build.sh', 'w');
$writesh = function (...$args) use ($output) {
fwrite($output, sprintf(...$args));
fwrite(STDOUT, sprintf(...$args));
};
$writesh(<<<BASH
#!/bin/sh
#set -x
# NOTE: to build these binaries on your machine, just use dixyes/prepared-lwmbs:{$matrix['imageTag']} image
# cached in github actions
# echo "::group::Prepare source"
# # prepare source for specified php version
# # you may select only libraries and extensions you will use
# php /lwmbs/fetch_source.php \
# "" \
# "" \
# --phpVer={$matrix['phpVer']} \
# --versionFile=/versionFile
# ret="\$?"
# echo "::endgroup::"
# if [ ! "\$ret" = "0" ]
# then
# echo "::error::fetch source failed"
# exit 1
# fi
echo "::group::Show versions"
cat /versionFile
sha256sum /versionFile
echo "::endgroup::"
BASH);
$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 = [
'iconv',
// 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',
'posix','pcntl',
'sysvshm','sysvsem','sysvmsg',
'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' => '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' => $maxExtensions,
'max-swow' => "{$maxExtensions},swow",
'max-swoole' => "{$maxExtensions},swoole",
'max-libev' => "{$maxExtensions},libev",
'custom' => $customExtensions,
};
$writesh("\n\n");
$writesh(<<<BASH
# ----- "$flavor" flavor -----
echo "::group::Build $flavor libs"
# rebuild libs for this flavor to avoid cache
php /lwmbs/build_libs.php \
"$libraries" \
"--cc=\$LWMBS_CC" \
"--cxx=\$LWMBS_CXX" \
"--arch={$matrix['arch']}" \
"--fresh"
ret="\$?"
echo "::endgroup::"
if [ ! "\$ret" = "0" ]
then
echo "::error::failed build lib for $flavor"
else
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
echo "::group::Build $flavor shared $sapi"
# {$matrix['libc']}_shared $sapi
# rm php_micro.lo to avoid cache
rm -f /src/php-src/sapi/micro/php_micro.lo
php /lwmbs/$command \
"$libraries" \
"$extensions" \
"--cc=\$LWMBS_CC" \
"--cxx=\$LWMBS_CXX" \
"--arch={$matrix['arch']}"
ret="\$?"
echo "::endgroup::"
if [ ! "\$ret" = "0" ]
then
echo "::error::failed build $flavor shared $sapi"
else
# copy the built bin out
mkdir -p /out/{$flavor}_shared
cp $targetBin /out/{$flavor}_shared/$binName
cp $targetBin.debug /out/{$flavor}_shared/$binName.debug
fi
BASH);
if ($matrix['libc'] == 'musl') {
$writesh("\n\n");
$writesh(<<<BASH
echo "::group::Build $flavor static $sapi"
# {$matrix['libc']}_static $sapi
# rm php_micro.lo to avoid cache
rm -f /src/php-src/sapi/micro/php_micro.lo
php /lwmbs/$command \
"$libraries" \
"$extensions" \
"--cc=\$LWMBS_CC" \
"--cxx=\$LWMBS_CXX" \
"--arch={$matrix['arch']}" \
"--allStatic"
ret="\$?"
echo "::endgroup::"
if [ ! "\$ret" = "0" ]
then
echo "::error::failed build $flavor static $sapi"
else
# copy the built bin out
mkdir -p /out/{$flavor}_static
cp $targetBin /out/{$flavor}_static/$binName
cp $targetBin.debug /out/{$flavor}_static/$binName.debug
fi
BASH);
}
}
$writesh("\n\n");
$writesh(<<<BASH
echo "::group::Dump $flavor licenses"
# dump licenses
php /lwmbs/dump_licenses.php \
"/out/{$flavor}_shared/licenses" \
"$libraries" \
"$extensions"
[ -d "/out/{$flavor}_static" ] && php /lwmbs/dump_licenses.php \
"/out/{$flavor}_static/licenses" \
"$libraries" \
"$extensions"
# copy versionFile
cp /versionFile "/out/{$flavor}_shared/versionFile"
[ -d "/out/{$flavor}_static" ] && cp /versionFile "/out/{$flavor}_static/versionFile"
echo "::endgroup::"
fi
BASH);
}
$writesh("\n");
- name: Build
shell: sh {0}
working-directory: /
run: |
set +e
. /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: Setup node
# if: always()
# uses: actions/setup-node@v3
# with:
# node-version: '16'
- name: Prepare node for artifact upload
if: always()
shell: sh {0}
run: |
if [ '${{ matrix.libc }}' = 'musl' ]
then
apk add gcompat libstdc++
fi
npm i -g \
@actions/core \
@actions/github \
@actions/artifact \
@actions/exec
echo "NODE_PATH=$(npm root --quiet -g)" >> "$GITHUB_ENV"
which node
node --version
- name: Expose GitHub Runtime
if: always()
uses: crazy-max/ghaction-github-runtime@v3
- 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('/lwmbs/.github/workflows/upload.js')
main('${{ secrets.GITHUB_TOKEN }}', 'linux', context).catch(err => core.setFailed(err.message))