From 14611ed1e975a676a465b7e4045b8f9a91c7d172 Mon Sep 17 00:00:00 2001 From: henderkes Date: Fri, 21 Aug 2026 22:37:11 +0200 Subject: [PATCH] move to spc-v3 --- .github/workflows/static.yaml | 102 ++++++--- build-static.sh | 387 ++++++++++++++++----------------- craft.yml | 38 ++++ docker-bake.hcl | 59 ++--- docs/static.md | 67 +++--- install.sh | 14 +- static-builder-gnu.Dockerfile | 148 ------------- static-builder-musl.Dockerfile | 112 ---------- static-builder.Dockerfile | 45 ++++ 9 files changed, 400 insertions(+), 572 deletions(-) create mode 100644 craft.yml delete mode 100644 static-builder-gnu.Dockerfile delete mode 100644 static-builder-musl.Dockerfile create mode 100644 static-builder.Dockerfile diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index 61854f9edf..9923270443 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -9,6 +9,7 @@ on: branches: - main paths: + - "craft.yml" - "docker-bake.hcl" - ".github/workflows/static.yaml" - "**cgo.go" @@ -37,7 +38,6 @@ permissions: env: IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }} - SPC_OPT_BUILD_ARGS: --debug GOTOOLCHAIN: local jobs: @@ -142,21 +142,49 @@ jobs: uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 with: pull: true - load: ${{ !fromJson(needs.prepare.outputs.push) || matrix.debug || matrix.mimalloc }} + load: true source: . targets: static-builder-musl set: | ${{ matrix.debug && 'static-builder-musl.args.DEBUG_SYMBOLS=1' || '' }} ${{ matrix.mimalloc && 'static-builder-musl.args.MIMALLOC=1' || '' }} - *.tags= + *.tags=frankenphp-static-builder-musl:ci *.platform=${{ matrix.platform }} ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope={0}-static-builder-musl{1}{2}', needs.prepare.outputs.ref || github.ref, matrix.debug && '-debug' || '', matrix.mimalloc && '-mimalloc' || '') }} ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope=refs/heads/main-static-builder-musl{0}{1}', matrix.debug && '-debug' || '', matrix.mimalloc && '-mimalloc' || '') }} ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-to=type=gha,scope={0}-static-builder-musl{1}{2},ignore-error=true', needs.prepare.outputs.ref || github.ref, matrix.debug && '-debug' || '', matrix.mimalloc && '-mimalloc' || '') }} - ${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }} env: SHA: ${{ github.sha }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build FrankenPHP + run: | + mkdir -p output + docker run --rm --platform="${PLATFORM}" --ulimit nofile=8192:8192 -v "${PWD}/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log -e GITHUB_TOKEN ${DEBUG_SYMBOLS:+-e DEBUG_SYMBOLS} ${MIMALLOC:+-e MIMALLOC} frankenphp-static-builder-musl:ci + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PLATFORM: ${{ matrix.platform }} + DEBUG_SYMBOLS: ${{ matrix.debug && '1' || '' }} + MIMALLOC: ${{ matrix.mimalloc && '1' || '' }} + - name: Upload static-php-cli logs + if: ${{ failure() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: static-php-cli-log-musl-${{ steps.prepare.outputs.sanitized_platform }}-${{ github.sha }} + path: output/log + if-no-files-found: warn + - name: Push the builder image + id: push + if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc + uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 + with: + source: . + targets: static-builder-musl + set: | + *.tags= + *.platform=${{ matrix.platform }} + *.output=type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + env: + SHA: ${{ github.sha }} - # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600 name: Export metadata if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc @@ -164,10 +192,10 @@ jobs: mkdir -p /tmp/metadata # shellcheck disable=SC2086 - digest=$(jq -r '."static-builder-musl"."containerimage.digest"' <<< ${METADATA}) + digest=$(jq -r '."static-builder-musl"."containerimage.digest"' <<< "${METADATA}") touch "/tmp/metadata/${digest#sha256:}" env: - METADATA: ${{ steps.build.outputs.metadata }} + METADATA: ${{ steps.push.outputs.metadata }} - name: Upload metadata if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -178,16 +206,9 @@ jobs: retention-days: 1 - name: Copy binary run: | - # shellcheck disable=SC2034 - # TODO: remove "containerimage.config.digest" fallback once all runners use buildx v0.18+ - # which replaced it with "containerimage.digest" and "containerimage.descriptor" - digest=$(jq -r '."static-builder-musl" | ${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '."containerimage.digest"' || '(."containerimage.config.digest" // ."containerimage.digest")' }}' <<< "${METADATA}") - docker create --platform="${PLATFORM}" --name static-builder-musl "${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '${IMAGE_NAME}@${digest}' || '${digest}' }}" - docker cp "static-builder-musl:/go/src/app/dist/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}" + cp "output/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}" env: - METADATA: ${{ steps.build.outputs.metadata }} BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }} - PLATFORM: ${{ matrix.platform }} - name: Upload artifact if: ${{ !fromJson(needs.prepare.outputs.push) }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -297,19 +318,45 @@ jobs: uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 with: pull: true - load: ${{ !fromJson(needs.prepare.outputs.push) }} + load: true source: . targets: static-builder-gnu set: | - *.tags= + *.tags=frankenphp-static-builder-gnu:ci *.platform=${{ matrix.platform }} ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-from=type=gha,scope={0}-static-builder-gnu', needs.prepare.outputs.ref || github.ref) }} ${{ fromJson(needs.prepare.outputs.push) && '' || '*.cache-from=type=gha,scope=refs/heads/main-static-builder-gnu' }} ${{ fromJson(needs.prepare.outputs.push) && '' || format('*.cache-to=type=gha,scope={0}-static-builder-gnu,ignore-error=true', needs.prepare.outputs.ref || github.ref) }} - ${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }} env: SHA: ${{ github.sha }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build FrankenPHP + run: | + mkdir -p output + docker run --rm --platform="${PLATFORM}" --ulimit nofile=8192:8192 -v "${PWD}/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log -e GITHUB_TOKEN frankenphp-static-builder-gnu:ci + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PLATFORM: ${{ matrix.platform }} + - name: Upload static-php-cli logs + if: ${{ failure() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: static-php-cli-log-gnu-${{ steps.prepare.outputs.sanitized_platform }}-${{ github.sha }} + path: output/log + if-no-files-found: warn + - name: Push the builder image + id: push + if: fromJson(needs.prepare.outputs.push) + uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 + with: + source: . + targets: static-builder-gnu + set: | + *.tags= + *.platform=${{ matrix.platform }} + *.output=type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + env: + SHA: ${{ github.sha }} - # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600 name: Export metadata if: fromJson(needs.prepare.outputs.push) @@ -317,10 +364,10 @@ jobs: mkdir -p /tmp/metadata-gnu # shellcheck disable=SC2086 - digest=$(jq -r '."static-builder-gnu"."containerimage.digest"' <<< ${METADATA}) + digest=$(jq -r '."static-builder-gnu"."containerimage.digest"' <<< "${METADATA}") touch "/tmp/metadata-gnu/${digest#sha256:}" env: - METADATA: ${{ steps.build.outputs.metadata }} + METADATA: ${{ steps.push.outputs.metadata }} - name: Upload metadata if: fromJson(needs.prepare.outputs.push) uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -331,22 +378,11 @@ jobs: retention-days: 1 - name: Copy all frankenphp* files run: | - # shellcheck disable=SC2034 - # TODO: remove "containerimage.config.digest" fallback once all runners use buildx v0.18+ - # which replaced it with "containerimage.digest" and "containerimage.descriptor" - digest=$(jq -r '."static-builder-gnu" | ${{ fromJson(needs.prepare.outputs.push) && '."containerimage.digest"' || '(."containerimage.config.digest" // ."containerimage.digest")' }}' <<< "${METADATA}") - container_id=$(docker create --platform="${PLATFORM}" "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}") mkdir -p gh-output - cd gh-output - for file in $(docker run --rm "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}" sh -c "ls /go/src/app/dist | grep '^frankenphp'"); do - docker cp "${container_id}:/go/src/app/dist/${file}" "./${file}" - done - docker rm "${container_id}" - mv "${BINARY}" "${BINARY}-gnu" + cp output/frankenphp* gh-output/ + mv "gh-output/${BINARY}" "gh-output/${BINARY}-gnu" env: - METADATA: ${{ steps.build.outputs.metadata }} BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }} - PLATFORM: ${{ matrix.platform }} - name: Upload artifact if: ${{ !fromJson(needs.prepare.outputs.push) }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -468,7 +504,7 @@ jobs: if: ${{ failure() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - path: dist/static-php-cli/log + path: dist/log name: static-php-cli-log-${{ matrix.platform }}-${{ github.sha }} - if: needs.prepare.outputs.ref || github.ref_type == 'tag' uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4.2.0 diff --git a/build-static.sh b/build-static.sh index 798ff19fb2..4c241c643d 100755 --- a/build-static.sh +++ b/build-static.sh @@ -1,234 +1,211 @@ #!/bin/bash set -o errexit -set -x +set -o pipefail + +# Build a static (musl) or mostly static (glibc) FrankenPHP binary using +# static-php-cli v3 (https://static-php.dev). +# +# What gets built is described in craft.yml: PHP version, extensions, libraries, +# SAPI and build flags. craft.yml works on its own; this script copies it to +# dist/craft.yml with a few values rewritten, fetches the `spc` binary if needed +# and runs `spc craft`. +# +# Supported variables: +# - PHP_VERSION: PHP version to build (default: the one pinned in craft.yml) +# - PHP_EXTENSIONS: comma-separated extensions (default: the list in craft.yml) +# - PHP_EXTENSION_LIBS: comma-separated extra libraries (default: the list in craft.yml) +# - XCADDY_ARGS: extra Caddy modules to pass to xcaddy (default: the list in craft.yml) +# - FRANKENPHP_VERSION: FrankenPHP version (default: current Git commit) +# - EMBED: path to the PHP app to embed (default: none) +# - CLEAN: when set, rebuild everything from scratch (default: none) +# - DEBUG_SYMBOLS: when set, keep debug symbols (default: none) +# - COMPRESS: when set, pack the resulting Linux binary with UPX; ignored when +# DEBUG_SYMBOLS is set (default: none) +# - MIMALLOC: when set, use mimalloc as the allocator (default: none) +# - OUTPUT_DIR: where to write the resulting binary (default: dist/) +# - RELEASE: [maintainer only] create a GitHub release if set to 1 (default: none) +# +# The libc to link against (Linux only) is selected with: +# - LIBC: "musl" for a fully static binary (default), "gnu" to link dynamically +# against the system glibc using the native GCC +# +# LIBC is a shorthand for static-php-cli's own SPC_TARGET and SPC_TOOLCHAIN. +# Setting those directly still works, but both have to be consistent, and they +# must be real environment variables: static-php-cli resolves its toolchain at +# startup, before craft.yml is read. if ! type "git" >/dev/null 2>&1; then echo "The \"git\" command must be installed." exit 1 fi -CURRENT_DIR=$(pwd) - -arch="$(uname -m)" -os="$(uname -s | tr '[:upper:]' '[:lower:]')" -[ "$os" = "darwin" ] && os="mac" - -# Supported variables: -# - PHP_VERSION: PHP version to build (default: "8.4") -# - PHP_EXTENSIONS: PHP extensions to build (default: ${defaultExtensions} set below) -# - PHP_EXTENSION_LIBS: PHP extension libraries to build (default: ${defaultExtensionLibs} set below) -# - FRANKENPHP_VERSION: FrankenPHP version (default: current Git commit) -# - EMBED: Path to the PHP app to embed (default: none) -# - DEBUG_SYMBOLS: Enable debug symbols if set to 1 (default: none) -# - COMPRESS: Pack the resulting Linux binary with UPX if set to 1; ignored when DEBUG_SYMBOLS is set (default: none) -# - MIMALLOC: Use mimalloc as the allocator if set to 1 (default: none) -# - XCADDY_ARGS: Additional arguments to pass to xcaddy -# - RELEASE: [maintainer only] Create a GitHub release if set to 1 (default: none) - -# - SPC_REL_TYPE: Release type to download (accept "source" and "binary", default: "source") -# - SPC_OPT_BUILD_ARGS: Additional arguments to pass to spc build -# - SPC_OPT_DOWNLOAD_ARGS: Additional arguments to pass to spc download -# - SPC_LIBC: Set to glibc to build with GNU toolchain (default: musl) - -# init spc command, if we use spc binary, just use it instead of fetching source -if [ -z "${SPC_REL_TYPE}" ]; then - SPC_REL_TYPE="source" -fi -# init spc libc -if [ -z "${SPC_LIBC}" ]; then - if [ "${os}" = "linux" ]; then - SPC_LIBC="musl" - fi -fi -# init spc build additional args -if [ -z "${SPC_OPT_BUILD_ARGS}" ]; then - SPC_OPT_BUILD_ARGS="" -fi -if [ "${SPC_LIBC}" = "musl" ] && [[ "${SPC_OPT_BUILD_ARGS}" != *"--disable-opcache-jit"* ]]; then - SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --disable-opcache-jit" -fi -# init spc download additional args -if [ -z "${SPC_OPT_DOWNLOAD_ARGS}" ]; then - SPC_OPT_DOWNLOAD_ARGS="--ignore-cache-sources=php-src --retry 5" - if [ "${SPC_LIBC}" = "musl" ]; then - SPC_OPT_DOWNLOAD_ARGS="${SPC_OPT_DOWNLOAD_ARGS} --prefer-pre-built" - fi -fi -# if we need debug symbols, disable strip -if [ -n "${DEBUG_SYMBOLS}" ]; then - SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --no-strip" -fi -# php version to build -if [ -z "${PHP_VERSION}" ]; then - get_latest_php_version() { - input="$1" - json=$(curl -fsSL "https://www.php.net/releases/index.php?json&version=$input" 2>/dev/null || curl -fsSL "https://phpmirror.static-php.dev/releases/index.php?json&version=$input") - latest=$(echo "$json" | jq -r '.version') - - if [[ "$latest" == "$input"* ]]; then - echo "$latest" - else - echo "$input" - fi - } - - PHP_VERSION="$(get_latest_php_version "8.5")" - export PHP_VERSION -fi -# default extension set -defaultExtensions="amqp,apcu,ast,bcmath,brotli,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,lz4,mbregex,mbstring,memcached,mysqli,mysqlnd,opcache,openssl,password-argon2,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,xsl,xz,zip,zlib,yaml,zstd" -defaultExtensionLibs="libavif,nghttp2,nghttp3,ngtcp2,watcher" - +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DIST_DIR="${CURRENT_DIR}/dist" + +# Binary naming, kept stable for install.sh and the GitHub release assets +bin_arch="$(uname -m)" +bin_os="$(uname -s | tr '[:upper:]' '[:lower:]')" +[ "${bin_os}" = "darwin" ] && bin_os="mac" + +# static-php-cli release naming +spc_os="$([ "${bin_os}" = "mac" ] && echo "macos" || echo "${bin_os}")" +spc_arch="$([ "${bin_arch}" = "arm64" ] && echo "aarch64" || echo "${bin_arch}")" + +unset SPC_LIBC +if [ "${bin_os}" = "linux" ]; then + case "${LIBC:-musl}" in + musl) + : "${SPC_TARGET:=native-native-musl}" + ;; + gnu) + : "${SPC_TARGET:=}" + : "${SPC_TOOLCHAIN:=StaticPHP\Toolchain\GccNativeToolchain}" + ;; + *) + echo "LIBC must be \"musl\" or \"gnu\", got \"${LIBC}\"." + exit 1 + ;; + esac + export SPC_TARGET + export SPC_TOOLCHAIN="${SPC_TOOLCHAIN:-}" +fi + +# Check out the requested version, if any if [ -z "${FRANKENPHP_VERSION}" ]; then - FRANKENPHP_VERSION="$(git rev-parse --verify HEAD)" - export FRANKENPHP_VERSION -elif [ -d ".git/" ]; then - CURRENT_REF="$(git rev-parse --abbrev-ref HEAD)" + FRANKENPHP_VERSION="$(git -C "${CURRENT_DIR}" rev-parse --verify HEAD)" +elif [ -d "${CURRENT_DIR}/.git/" ]; then + CURRENT_REF="$(git -C "${CURRENT_DIR}" rev-parse --abbrev-ref HEAD)" export CURRENT_REF if echo "${FRANKENPHP_VERSION}" | grep -F -q "."; then - # Tag - - # Trim "v" prefix if any + # Tag: trim the "v" prefix, if any FRANKENPHP_VERSION=${FRANKENPHP_VERSION#v} - export FRANKENPHP_VERSION - - git checkout "v${FRANKENPHP_VERSION}" + git -C "${CURRENT_DIR}" checkout "v${FRANKENPHP_VERSION}" else - git checkout "${FRANKENPHP_VERSION}" + git -C "${CURRENT_DIR}" checkout "${FRANKENPHP_VERSION}" fi fi +export FRANKENPHP_VERSION if [ -n "${CLEAN}" ]; then - rm -Rf dist/ - go clean -cache + # Drop the build root, the sources, the download cache and the spc binary + rm -rf "${DIST_DIR}" fi -mkdir -p dist/ -cd dist/ +mkdir -p "${DIST_DIR}" +# macOS dependencies if type "brew" >/dev/null 2>&1; then - if ! type "composer" >/dev/null; then - packages="composer" - fi - if ! type "go" >/dev/null 2>&1; then - packages="${packages} go" - fi if [ -n "${RELEASE}" ] && ! type "gh" >/dev/null 2>&1; then - packages="${packages} gh" - fi - - if [ -n "${packages}" ]; then - # shellcheck disable=SC2086 - brew install --formula --quiet ${packages} - fi -fi - -if [ "${SPC_REL_TYPE}" = "binary" ]; then - mkdir -p static-php-cli/ - cd static-php-cli/ - if [[ "${arch}" =~ "arm" ]]; then - dl_arch="aarch64" - else - dl_arch="${arch}" + brew install --formula --quiet gh fi - curl -o spc -fsSL "https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-${dl_arch}" - chmod +x spc - spcCommand="./spc" -elif [ -d "static-php-cli/src" ]; then - cd static-php-cli/ - git pull - composer install --no-dev -a --no-interaction - spcCommand="./bin/spc" -else - git clone --depth 1 https://github.com/crazywhalecc/static-php-cli --branch main - cd static-php-cli/ - composer install --no-dev -a --no-interaction - spcCommand="./bin/spc" fi -# turn potentially relative EMBED path into absolute path -if [ -n "${EMBED}" ]; then - if [[ "${EMBED}" != /* ]]; then - EMBED="${CURRENT_DIR}/${EMBED}" - fi -fi - -# Extensions to build -if [ -z "${PHP_EXTENSIONS}" ]; then - # enable EMBED mode, first check if project has dumped extensions - if [ -n "${EMBED}" ] && [ -f "${EMBED}/composer.json" ] && [ -f "${EMBED}/composer.lock" ] && [ -f "${EMBED}/vendor/composer/installed.json" ]; then - # read the extensions using spc dump-extensions - PHP_EXTENSIONS=$(${spcCommand} dump-extensions "${EMBED}" --format=text --no-dev --no-ext-output="${defaultExtensions}") - else - PHP_EXTENSIONS="${defaultExtensions}" - fi -fi - -# Additional libraries to build -if [ -z "${PHP_EXTENSION_LIBS}" ]; then - PHP_EXTENSION_LIBS="${defaultExtensionLibs}" -fi - -# The Brotli library must always be built as it is required by http://github.com/dunglas/caddy-cbrotli -if ! echo "${PHP_EXTENSION_LIBS}" | grep -q "\bbrotli\b"; then - PHP_EXTENSION_LIBS="${PHP_EXTENSION_LIBS},brotli" -fi - -# The mimalloc library must be built if MIMALLOC is true -if [ -n "${MIMALLOC}" ]; then - if ! echo "${PHP_EXTENSION_LIBS}" | grep -q "\bmimalloc\b"; then - PHP_EXTENSION_LIBS="${PHP_EXTENSION_LIBS},mimalloc" - fi -fi - -# Embed PHP app, if any -if [ -n "${EMBED}" ] && [ -d "${EMBED}" ]; then - # shellcheck disable=SC2089 - SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --with-frankenphp-app='${EMBED}'" -fi - -SPC_OPT_INSTALL_ARGS="go-xcaddy" -if [ -n "${COMPRESS}" ] && [ -z "${DEBUG_SYMBOLS}" ] && [ "${os}" = "linux" ]; then - SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --with-upx-pack" - SPC_OPT_INSTALL_ARGS="${SPC_OPT_INSTALL_ARGS} upx" -fi - -MTLS_CFLAGS="$(sh "${CURRENT_DIR}/mtls-cflags.sh")" -export CGO_CFLAGS="${CGO_CFLAGS} ${MTLS_CFLAGS}" - -export SPC_DEFAULT_C_FLAGS="-fPIC -O2" -if [ -n "${DEBUG_SYMBOLS}" ]; then - SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS} -fPIE -g" -else - SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS="${SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS} -fPIE -fstack-protector-strong -O2 -w -s" -fi -export SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS -if [ -z "$SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES" ]; then - export SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="--with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy --with github.com/dunglas/caddy-cbrotli" -fi - -# Build FrankenPHP -${spcCommand} doctor --auto-fix -for pkg in ${SPC_OPT_INSTALL_ARGS}; do - ${spcCommand} install-pkg "${pkg}" -done -# shellcheck disable=SC2086 -${spcCommand} download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" ${SPC_OPT_DOWNLOAD_ARGS} -export FRANKENPHP_SOURCE_PATH="${CURRENT_DIR}" -# shellcheck disable=SC2086,SC2090 -${spcCommand} build --enable-zts --build-embed --build-frankenphp ${SPC_OPT_BUILD_ARGS} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" - -if [ -n "$CI" ]; then - rm -rf ./downloads - rm -rf ./source -fi - -cd ../.. - -bin="dist/frankenphp-${os}-${arch}" -cp "dist/static-php-cli/buildroot/bin/frankenphp" "${bin}" +# Fetch the spc binary if we don't have it yet +spc="${DIST_DIR}/spc" +if [ ! -x "${spc}" ]; then + curl -fsSL -o "${spc}" "https://dl.static-php.dev/v3/spc-bin/nightly/spc-${spc_os}-${spc_arch}" + chmod +x "${spc}" +fi + +# Turn a potentially relative EMBED path into an absolute one +if [ -n "${EMBED}" ] && [[ "${EMBED}" != /* ]]; then + EMBED="$(cd "${EMBED}" && pwd)" +fi + +# When embedding an app that declares its dependencies, build only the +# extensions it actually needs. +if [ -z "${PHP_EXTENSIONS}" ] && [ -n "${EMBED}" ] && + [ -f "${EMBED}/composer.json" ] && [ -f "${EMBED}/composer.lock" ] && + [ -f "${EMBED}/vendor/composer/installed.json" ]; then + PHP_EXTENSIONS="$("${spc}" dump-extensions "${EMBED}" --format=text --no-dev)" +fi + +libs="${PHP_EXTENSION_LIBS}" +if [ -n "${libs}" ]; then + libs="${libs}${MIMALLOC:+,mimalloc}" + # caddy-cbrotli needs the brotli library, always build it + case ",${libs}," in + *,brotli,*) ;; + *) libs="${libs},brotli" ;; + esac +fi + +bool() { [ -n "${1}" ] && echo true || echo false; } + +upx_pack=false +if [ -n "${COMPRESS}" ] && [ -z "${DEBUG_SYMBOLS}" ] && [ "${bin_os}" = "linux" ]; then + upx_pack=true +fi + +# Rewrite the values craft.yml cannot hold a working default for, and apply the +# optional overrides. Each case replaces a whole value, so running this over an +# already rendered craft.yml gives the same result. +craft="${DIST_DIR}/craft.yml" +: >"${craft}" +while IFS= read -r line || [ -n "${line}" ]; do + case "${line}" in + "php-version:"*) + [ -n "${PHP_VERSION}" ] && line="php-version: \"${PHP_VERSION}\"" + ;; + "extensions:"*) + [ -n "${PHP_EXTENSIONS}" ] && line="extensions: \"${PHP_EXTENSIONS}\"" + ;; + "packages:"*) + packages="${PHP_EXTENSION_LIBS}" + if [ -z "${packages}" ]; then + packages="${line#packages:}" + packages="${packages# }" + packages="${packages#\"}" + packages="${packages%\"}" + fi + if [ -n "${MIMALLOC}" ]; then + case ",${packages}," in + *,mimalloc,*) ;; + *) packages="${packages},mimalloc" ;; + esac + fi + # caddy-cbrotli needs the brotli library, always build it + case ",${packages}," in + *,brotli,*) ;; + *) packages="${packages},brotli" ;; + esac + line="packages: \"${packages}\"" + ;; + "clean-build:"*) + line="clean-build: $(bool "${CLEAN}")" + ;; + *"no-strip:"*) + line=" no-strip: $(bool "${DEBUG_SYMBOLS}")" + ;; + *"with-upx-pack:"*) + line=" with-upx-pack: ${upx_pack}" + ;; + *"with-frankenphp-app:"*) + line=" with-frankenphp-app: \"${EMBED}\"" + ;; + *"custom-local:"*) + line=" custom-local: [\"frankenphp:${CURRENT_DIR}\"]" + ;; + *'- "frankenphp:'*) + # stale list entry, custom-local is rewritten as a one-liner above + continue + ;; + *"SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES:"*) + [ -n "${XCADDY_ARGS}" ] && line=" SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES: \"${XCADDY_ARGS}\"" + ;; + esac + + printf '%s\n' "${line}" >>"${craft}" +done <"${CURRENT_DIR}/craft.yml" + +cd "${DIST_DIR}" +"${spc}" craft "${craft}" + +bin="${OUTPUT_DIR:-${DIST_DIR}}/frankenphp-${bin_os}-${bin_arch}" +mkdir -p "$(dirname "${bin}")" +cp "${DIST_DIR}/buildroot/bin/frankenphp" "${bin}" "${bin}" version "${bin}" build-info @@ -237,5 +214,5 @@ if [ -n "${RELEASE}" ]; then fi if [ -n "${CURRENT_REF}" ]; then - git checkout "${CURRENT_REF}" + git -C "${CURRENT_DIR}" checkout "${CURRENT_REF}" fi diff --git a/craft.yml b/craft.yml new file mode 100644 index 0000000000..acf4229c7a --- /dev/null +++ b/craft.yml @@ -0,0 +1,38 @@ +# FrankenPHP static build definition, consumed by static-php-cli v3 (`spc craft`). +# Reference: https://static-php.dev/en/develop/craft-yml + +php-version: "8.5" + +# Statically linked PHP extensions. +extensions: "amqp,apcu,ast,bcmath,brotli,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,lz4,mbregex,mbstring,memcached,mysqli,mysqlnd,opcache,openssl,password-argon2,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,xsl,xz,zip,zlib,yaml,zstd" + +# Extra libraries that are not pulled in by an extension. +# brotli is required by github.com/dunglas/caddy-cbrotli, watcher by the file watcher. +packages: "brotli,watcher,libavif,nghttp2,nghttp3,ngtcp2" +sapi: "frankenphp" + +clean-build: false + +build-options: + enable-zts: true + with-config-file-path: "/etc/frankenphp" + with-config-file-scan-dir: "/etc/frankenphp/php.d" + no-strip: false + with-upx-pack: false + with-frankenphp-app: "" + with-suggests: true + +download-options: + parallel: 10 + retry: 5 + # build-static.sh points this at the working tree it runs from + custom-local: [] + +extra-env: + SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES: "--with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" + EXTENSION_DIR: "/usr/lib/frankenphp/modules" + OPENSSLDIR: "/etc/ssl" + +craft-options: + doctor: true + download: true diff --git a/docker-bake.hcl b/docker-bake.hcl index 7826903d16..4c21683ae3 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -29,10 +29,6 @@ variable "CREATED" { default = "" } -variable "SPC_OPT_BUILD_ARGS" { - default = "" -} - variable "SHA" {} variable "LATEST" { @@ -150,21 +146,13 @@ target "default" { secret = ["id=github-token,env=GITHUB_TOKEN"] } -target "static-builder-musl" { - contexts = { - golang-base = "docker-image://golang:${GO_VERSION}-alpine" - } - dockerfile = "static-builder-musl.Dockerfile" +target "static-builder" { + dockerfile = "static-builder.Dockerfile" context = "./" platforms = [ "linux/amd64", "linux/arm64", ] - tags = distinct(flatten([ - LATEST ? "${IMAGE_NAME}:static-builder-musl" : "", - SHA == "" || VERSION != "dev" ? "" : "${IMAGE_NAME}:static-builder-musl-sha-${substr(SHA, 0, 7)}", - VERSION == "dev" ? [] : [for v in semver(VERSION) : "${IMAGE_NAME}:static-builder-musl-${v}"] - ])) labels = { "org.opencontainers.image.created" = CREATED != "" ? CREATED : timestamp() "org.opencontainers.image.version" = VERSION @@ -173,35 +161,28 @@ target "static-builder-musl" { } args = { FRANKENPHP_VERSION = VERSION - CI = CI - SPC_OPT_BUILD_ARGS = SPC_OPT_BUILD_ARGS } - secret = ["id=github-token,env=GITHUB_TOKEN"] } -target "static-builder-gnu" { - dockerfile = "static-builder-gnu.Dockerfile" - context = "./" - platforms = [ - "linux/amd64", - "linux/arm64" - ] - tags = distinct(flatten([ - LATEST ? "${IMAGE_NAME}:static-builder-gnu" : "", - SHA == "" || VERSION != "dev" ? "" : "${IMAGE_NAME}:static-builder-gnu-sha-${substr(SHA, 0, 7)}", - VERSION == "dev" ? [] : [for v in semver(VERSION) : "${IMAGE_NAME}:static-builder-gnu-${v}"] +# static_builder_tags builds the tag list for one libc flavor +function "static_builder_tags" { + params = [libc] + result = distinct(flatten([ + LATEST ? "${IMAGE_NAME}:static-builder-${libc}" : "", + SHA == "" || VERSION != "dev" ? "" : "${IMAGE_NAME}:static-builder-${libc}-sha-${substr(SHA, 0, 7)}", + VERSION == "dev" ? [] : [for v in semver(VERSION) : "${IMAGE_NAME}:static-builder-${libc}-${v}"] ])) - labels = { - "org.opencontainers.image.created" = CREATED != "" ? CREATED : timestamp() - "org.opencontainers.image.version" = VERSION - "org.opencontainers.image.revision" = SHA - "dev.frankenphp.base.fingerprint" = BASE_FINGERPRINT - } +} + +target "static-builder-musl" { + inherits = ["static-builder"] + tags = static_builder_tags("musl") args = { - FRANKENPHP_VERSION = VERSION - GO_VERSION = GO_VERSION - CI = CI - SPC_OPT_BUILD_ARGS = SPC_OPT_BUILD_ARGS + LIBC = "musl" } - secret = ["id=github-token,env=GITHUB_TOKEN"] +} + +target "static-builder-gnu" { + inherits = ["static-builder"] + tags = static_builder_tags("gnu") } diff --git a/docs/static.md b/docs/static.md index 803053abb1..85a29af689 100644 --- a/docs/static.md +++ b/docs/static.md @@ -29,13 +29,14 @@ For a fully-static binary that runs on any Linux distribution without dependenci ```console docker buildx bake --load static-builder-musl -docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-musl +docker run --rm -v "$PWD/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log dunglas/frankenphp:static-builder-musl +cp "output/frankenphp-linux-$(uname -m)" frankenphp ``` For better performance in heavily concurrent scenarios, consider using the [mimalloc](https://github.com/microsoft/mimalloc) allocator. ```console -docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl +docker run --rm -v "$PWD/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log -e MIMALLOC=1 dunglas/frankenphp:static-builder-musl ``` ### glibc-based, mostly static build (with dynamic extension support) @@ -44,47 +45,49 @@ For a binary that supports loading PHP extensions dynamically while still having ```console docker buildx bake --load static-builder-gnu -docker cp $(docker create --name static-builder-gnu dunglas/frankenphp:static-builder-gnu):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-gnu +docker run --rm -v "$PWD/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log dunglas/frankenphp:static-builder-gnu +cp "output/frankenphp-linux-$(uname -m)" frankenphp ``` -This binary supports all glibc versions 2.17 and higher but does not run on musl-based systems (like Alpine Linux). +This binary supports all glibc versions 2.28 and higher but does not run on musl-based systems (like Alpine Linux). The resulting mostly static (except `glibc`) binary is named `frankenphp` and is available in the current directory. +If the build fails, the static-php-cli logs are in `output/log/`. + If you want to build the static binary without Docker, take a look at the macOS instructions, which also work for Linux. ### Custom PHP extensions in the static build By default, the most popular PHP extensions are compiled. +The defaults live in [`craft.yml`](https://github.com/php/frankenphp/blob/main/craft.yml). -To reduce the size of the binary and to reduce the attack surface, you can choose the list of extensions to build using the `PHP_EXTENSIONS` Docker ARG. +To reduce the size of the binary and to reduce the attack surface, you can choose the list of extensions to build using the `PHP_EXTENSIONS` variable. For instance, run the following command to only build the `opcache` extension: ```console -docker buildx bake --load --set static-builder-musl.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder-musl +docker run --rm -v "$PWD/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log -e PHP_EXTENSIONS=opcache,pdo_sqlite dunglas/frankenphp:static-builder-musl # ... ``` -To add libraries enabling additional functionality to the extensions you've enabled, you can pass the `PHP_EXTENSION_LIBS` Docker ARG: +To add libraries enabling additional functionality to the extensions you've enabled, you can pass the `PHP_EXTENSION_LIBS` variable: ```console -docker buildx bake \ - --load \ - --set static-builder-musl.args.PHP_EXTENSIONS=gd \ - --set static-builder-musl.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \ - static-builder-musl +docker run --rm -v "$PWD/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log \ + -e PHP_EXTENSIONS=gd \ + -e PHP_EXTENSION_LIBS=libjpeg,libwebp \ + dunglas/frankenphp:static-builder-musl ``` ### Extra Caddy modules -To add extra Caddy modules or pass other arguments to [xcaddy](https://github.com/caddyserver/xcaddy), use the `XCADDY_ARGS` Docker ARG: +To add extra Caddy modules or pass other arguments to [xcaddy](https://github.com/caddyserver/xcaddy), use the `XCADDY_ARGS` variable: ```console -docker buildx bake \ - --load \ - --set static-builder-musl.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \ - static-builder-musl +docker run --rm -v "$PWD/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log \ + -e XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \ + dunglas/frankenphp:static-builder-musl ``` In this example, we add the [Souin](https://souin.io) HTTP cache module for Caddy as well as the [cbrotli](https://github.com/dunglas/caddy-cbrotli), [Mercure](https://mercure.rocks) and [Vulcain](https://vulcain.rocks) modules. @@ -101,7 +104,7 @@ See also how to [customize the FrankenPHP static build](#customizing-the-franken If you hit the GitHub API rate limit, set a GitHub Personal Access Token in an environment variable named `GITHUB_TOKEN`: ```console -GITHUB_TOKEN="xxx" docker --load buildx bake static-builder-musl +docker run --rm -v "$PWD/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log -e GITHUB_TOKEN="xxx" dunglas/frankenphp:static-builder-musl # ... ``` @@ -119,7 +122,7 @@ Note: this script also works on Linux (and probably on other Unixes), and is use ## Customizing the FrankenPHP static build -The following environment variables can be passed to `docker build` and to the `build-static.sh` +The following environment variables can be passed to `docker run` and to the `build-static.sh` script to customize the static build: - `FRANKENPHP_VERSION`: the version of FrankenPHP to use @@ -134,32 +137,32 @@ script to customize the static build: - `MIMALLOC`: (experimental, Linux-only) replace musl's mallocng by [mimalloc](https://github.com/microsoft/mimalloc) for improved performance. We only recommend using this for musl targeting builds, for glibc prefer disabling this option and using [`LD_PRELOAD`](https://microsoft.github.io/mimalloc/overrides.html) when you run your binary instead. - `RELEASE`: (maintainers only) when set, the resulting binary will be uploaded on GitHub +- `LIBC`: (Linux only) `musl` for a fully static binary (the default), or `gnu` to link + dynamically against the system glibc. It is a shorthand for static-php-cli's `SPC_TARGET` + and `SPC_TOOLCHAIN`, which can still be set directly; they have to be real environment + variables because static-php-cli resolves its toolchain before reading `craft.yml`. + ## Loading PHP extensions dynamically in the static binary With the glibc or macOS-based binaries, you can load PHP extensions dynamically. However, these extensions will have to be compiled with ZTS support. Since most package managers do not currently offer ZTS versions of their extensions, you will have to compile them yourself. -For this, you can build and run the `static-builder-gnu` Docker container, remote into it, and compile the extensions with `./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config`. +For this, you can run the `static-builder-gnu` Docker container, remote into it, and compile the extensions with `./configure --with-php-config=/go/src/app/dist/buildroot/bin/php-config`. Example steps for [the Xdebug extension](https://xdebug.org): ```console -docker build -t gnu-ext -f static-builder-gnu.Dockerfile --build-arg FRANKENPHP_VERSION=1.0 . -docker create --name static-builder-gnu -it gnu-ext /bin/sh -docker start static-builder-gnu -docker exec -it static-builder-gnu /bin/sh -cd /go/src/app/dist/static-php-cli/buildroot/bin +docker buildx bake --load static-builder-gnu +docker run --rm -v "$PWD/dist:/go/src/app/dist" -v "$PWD/output:/output" -e OUTPUT_DIR=/output -e SPC_LOGS_DIR=/output/log dunglas/frankenphp:static-builder-gnu +docker run --rm -it -v "$PWD/dist:/go/src/app/dist" dunglas/frankenphp:static-builder-gnu /bin/bash +cd /go/src/app/dist/buildroot/bin git clone https://github.com/xdebug/xdebug.git && cd xdebug -source scl_source enable devtoolset-10 ../phpize -./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config +./configure --with-php-config=/go/src/app/dist/buildroot/bin/php-config make exit -docker cp static-builder-gnu:/go/src/app/dist/static-php-cli/buildroot/bin/xdebug/modules/xdebug.so xdebug-zts.so -docker cp static-builder-gnu:/go/src/app/dist/frankenphp-linux-$(uname -m) ./frankenphp -docker stop static-builder-gnu -docker rm static-builder-gnu -docker rmi gnu-ext +cp dist/buildroot/bin/xdebug/modules/xdebug.so xdebug-zts.so +cp "output/frankenphp-linux-$(uname -m)" frankenphp ``` This will have created `frankenphp` and `xdebug-zts.so` in the current directory. diff --git a/install.sh b/install.sh index 0a5254ef73..1c1829d9bb 100755 --- a/install.sh +++ b/install.sh @@ -111,9 +111,17 @@ Linux*) ;; esac - if getconf GNU_LIBC_VERSION >/dev/null 2>&1; then - THE_ARCH_BIN="${THE_ARCH_BIN}-gnu" - GNU=" (glibc)" + # The glibc binaries need glibc 2.28 or later (RHEL 8, Debian 10, Ubuntu 18.10). + # Older systems get the fully static musl binary instead, which runs anywhere. + if libc_version=$(getconf GNU_LIBC_VERSION 2>/dev/null); then + libc_version=${libc_version##* } + libc_major=${libc_version%%.*} + libc_minor=${libc_version#*.} + libc_minor=${libc_minor%%.*} + if [ "${libc_major}" -gt 2 ] || { [ "${libc_major}" -eq 2 ] && [ "${libc_minor}" -ge 28 ]; }; then + THE_ARCH_BIN="${THE_ARCH_BIN}-gnu" + GNU=" (glibc)" + fi fi ;; Darwin*) diff --git a/static-builder-gnu.Dockerfile b/static-builder-gnu.Dockerfile deleted file mode 100644 index 41cd831423..0000000000 --- a/static-builder-gnu.Dockerfile +++ /dev/null @@ -1,148 +0,0 @@ -# syntax=docker/dockerfile:1 -#checkov:skip=CKV_DOCKER_2 -#checkov:skip=CKV_DOCKER_3 -FROM centos:7 - -ARG FRANKENPHP_VERSION='' -ENV FRANKENPHP_VERSION=${FRANKENPHP_VERSION} - -ARG PHP_VERSION='' -ENV PHP_VERSION=${PHP_VERSION} - -# args passed to static-php-cli -ARG PHP_EXTENSIONS='' -ARG PHP_EXTENSION_LIBS='' -ARG SPC_OPT_BUILD_ARGS - -# args passed to xcaddy -ARG XCADDY_ARGS='--with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy' -ENV SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="${XCADDY_ARGS}" -ARG CLEAN='' -ARG EMBED='' -ARG DEBUG_SYMBOLS='' -ARG MIMALLOC='' -ARG COMPRESS='' - -# Go -ARG GO_VERSION -ENV GOTOOLCHAIN=local - -SHELL ["/bin/bash", "-o", "pipefail", "-c"] - -# Pass through CI environment flag so build-static.sh can detect CI context -ARG CI -ENV CI=${CI} - -# labels, same as static-builder.Dockerfile - -LABEL org.opencontainers.image.title=FrankenPHP -LABEL org.opencontainers.image.description="The modern PHP app server" -LABEL org.opencontainers.image.url=https://frankenphp.dev -LABEL org.opencontainers.image.source=https://github.com/php/frankenphp -LABEL org.opencontainers.image.licenses=MIT -LABEL org.opencontainers.image.vendor="Kévin Dunglas" - -# yum update -RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/*.repo && \ - sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/*.repo && \ - sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/*.repo && \ - yum clean all && \ - yum makecache && \ - yum update -y && \ - yum install -y centos-release-scl - -# different arch for different scl repo -RUN if [ "$(uname -m)" = "aarch64" ]; then \ - sed -i 's|mirror.centos.org/centos|vault.centos.org/altarch|g' /etc/yum.repos.d/CentOS-SCLo-scl-rh.repo ; \ - sed -i 's|mirror.centos.org/centos|vault.centos.org/altarch|g' /etc/yum.repos.d/CentOS-SCLo-scl.repo ; \ - sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/*.repo ; \ - sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/*.repo ; \ - else \ - sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/*.repo ; \ - sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/*.repo ; \ - sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/*.repo ; \ - fi; \ - yum update -y && \ - yum install -y devtoolset-10-gcc-* && \ - echo "source scl_source enable devtoolset-10" >> /etc/bashrc && \ - source /etc/bashrc - -# install build essentials -RUN yum install -y \ - perl \ - make \ - bison \ - flex \ - git \ - autoconf \ - automake \ - tar \ - unzip \ - gzip \ - gcc \ - bzip2 \ - patch \ - xz \ - libtool \ - perl-IPC-Cmd ; \ - curl -o make.tar.gz -fsSL https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && \ - tar -zxvf make.tar.gz && \ - cd make-* && \ - ./configure && \ - make && \ - make install && \ - ln -sf /usr/local/bin/make /usr/bin/make && \ - cd .. && \ - rm -Rf make* && \ - curl -o cmake.tar.gz -fsSL https://github.com/Kitware/CMake/releases/download/v4.1.2/cmake-4.1.2-linux-$(uname -m).tar.gz && \ - mkdir /cmake && \ - tar -xzf cmake.tar.gz -C /cmake --strip-components 1 && \ - rm cmake.tar.gz && \ - curl -fsSL -o patchelf.tar.gz https://github.com/NixOS/patchelf/releases/download/0.18.0/patchelf-0.18.0-$(uname -m).tar.gz && \ - mkdir -p /patchelf && \ - tar -xzf patchelf.tar.gz -C /patchelf --strip-components=1 && \ - cp /patchelf/bin/patchelf /usr/bin/ && \ - rm patchelf.tar.gz && \ - if [ "$(uname -m)" = "aarch64" ]; then \ - GO_ARCH="arm64" ; \ - else \ - GO_ARCH="amd64" ; \ - fi; \ - curl -o /usr/local/bin/jq -fsSL https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-${GO_ARCH} && \ - chmod +x /usr/local/bin/jq && \ - curl -o go.tar.gz -fsSL https://go.dev/dl/$(curl -fsS https://go.dev/dl/?mode=json | jq -r "first(first(.[] | select(.stable and (.version | startswith(\"go${GO_VERSION}\")))).files[] | select(.os == \"linux\" and (.kind == \"archive\") and (.arch == \"${GO_ARCH}\"))).filename") && \ - rm -rf /usr/local/go && \ - tar -C /usr/local -xzf go.tar.gz && \ - rm go.tar.gz && \ - /usr/local/go/bin/go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest - -ENV PATH="/opt/rh/devtoolset-10/root/usr/bin:/cmake/bin:/usr/local/go/bin:$PATH" - -# Apply GNU mode -ENV SPC_DEFAULT_C_FLAGS='-fPIE -fPIC -O3' -ENV SPC_LIBC='glibc' -ENV SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM='-Wl,-O3 -pie' -ENV SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS='-ldl -lpthread -lm -lresolv -lutil -lrt' -# Keep default config paths and append any externally provided SPC_OPT_BUILD_ARGS (e.g., from CI) -ENV SPC_OPT_BUILD_ARGS="--with-config-file-path=/etc/frankenphp --with-config-file-scan-dir=/etc/frankenphp/php.d ${SPC_OPT_BUILD_ARGS}" -ENV SPC_REL_TYPE='binary' -ENV EXTENSION_DIR='/usr/lib/frankenphp/modules' - -# not sure if this is needed -ENV COMPOSER_ALLOW_SUPERUSER=1 - -WORKDIR /go/src/app -COPY go.mod go.sum ./ -RUN go mod download - -WORKDIR /go/src/app/caddy -COPY caddy/go.mod caddy/go.sum ./ -RUN go mod download - -WORKDIR /go/src/app -COPY --link *.* ./ -COPY --link caddy caddy -COPY --link internal internal -COPY --link package package - -RUN --mount=type=secret,id=github-token GITHUB_TOKEN=$(cat /run/secrets/github-token) ./build-static.sh diff --git a/static-builder-musl.Dockerfile b/static-builder-musl.Dockerfile deleted file mode 100644 index 040f85192a..0000000000 --- a/static-builder-musl.Dockerfile +++ /dev/null @@ -1,112 +0,0 @@ -# syntax=docker/dockerfile:1 -#checkov:skip=CKV_DOCKER_2 -#checkov:skip=CKV_DOCKER_3 -#checkov:skip=CKV_DOCKER_7 -FROM golang-base - -ARG TARGETARCH - -ARG FRANKENPHP_VERSION='' -ENV FRANKENPHP_VERSION=${FRANKENPHP_VERSION} - -ARG PHP_VERSION='' -ENV PHP_VERSION=${PHP_VERSION} - -# args passed to static-php-cli -ARG PHP_EXTENSIONS='' -ARG PHP_EXTENSION_LIBS='' -ARG SPC_OPT_BUILD_ARGS - -# args passed to xcaddy -ARG XCADDY_ARGS='--with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy' -ENV SPC_CMD_VAR_FRANKENPHP_XCADDY_MODULES="${XCADDY_ARGS}" -ARG CLEAN='' -ARG EMBED='' -ARG DEBUG_SYMBOLS='' -ARG MIMALLOC='' -ARG COMPRESS='' - -ENV GOTOOLCHAIN=local - -SHELL ["/bin/ash", "-eo", "pipefail", "-c"] - -ARG CI -ENV CI=${CI} - -LABEL org.opencontainers.image.title=FrankenPHP -LABEL org.opencontainers.image.description="The modern PHP app server" -LABEL org.opencontainers.image.url=https://frankenphp.dev -LABEL org.opencontainers.image.source=https://github.com/php/frankenphp -LABEL org.opencontainers.image.licenses=MIT -LABEL org.opencontainers.image.vendor="Kévin Dunglas" - -RUN apk update; \ - apk add --no-cache \ - alpine-sdk \ - autoconf \ - automake \ - bash \ - binutils \ - bison \ - build-base \ - cmake \ - curl \ - file \ - flex \ - g++ \ - gcc \ - git \ - jq \ - libgcc \ - libstdc++ \ - libtool \ - linux-headers \ - m4 \ - make \ - pkgconfig \ - php84 \ - php84-common \ - php84-ctype \ - php84-curl \ - php84-dom \ - php84-iconv \ - php84-mbstring \ - php84-openssl \ - php84-pcntl \ - php84-phar \ - php84-posix \ - php84-session \ - php84-sodium \ - php84-tokenizer \ - php84-xml \ - php84-xmlwriter \ - upx \ - wget \ - xz ; \ - ln -sf /usr/bin/php84 /usr/bin/php && \ - go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest - -# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser -ENV COMPOSER_ALLOW_SUPERUSER=1 -COPY --from=composer/composer:2-bin /composer /usr/bin/composer - -WORKDIR /go/src/app -COPY go.mod go.sum ./ -RUN go mod download - -WORKDIR /go/src/app/caddy -COPY caddy/go.mod caddy/go.sum ./ -RUN go mod download - -WORKDIR /go/src/app -COPY --link . ./ - -ENV SPC_DEFAULT_C_FLAGS='-fPIE -fPIC -O3' -ENV SPC_LIBC='musl' -ENV SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM='-Wl,-O3 -pie' -# Keep default config paths and append any externally provided SPC_OPT_BUILD_ARGS (e.g., from CI) -ENV SPC_OPT_BUILD_ARGS="--with-config-file-path=/etc/frankenphp --with-config-file-scan-dir=/etc/frankenphp/php.d ${SPC_OPT_BUILD_ARGS}" -ENV SPC_REL_TYPE='binary' -ENV EXTENSION_DIR='/usr/lib/frankenphp/modules' - -RUN --mount=type=secret,id=github-token GITHUB_TOKEN=$(cat /run/secrets/github-token) ./build-static.sh diff --git a/static-builder.Dockerfile b/static-builder.Dockerfile new file mode 100644 index 0000000000..1150230629 --- /dev/null +++ b/static-builder.Dockerfile @@ -0,0 +1,45 @@ +# syntax=docker/dockerfile:1 +#checkov:skip=CKV_DOCKER_2 +#checkov:skip=CKV_DOCKER_3 +#checkov:skip=CKV_DOCKER_7 + +# Toolchain image for the static builds, shared by the musl and the glibc flavor. +# It ships glibc 2.28 (RHEL 8) and GCC 16. +# +# The build happens when the container *runs*, not while the image is built, so +# that the static-php-cli logs can be copied out of the container when it fails. +# +# docker run --name static-builder-gnu dunglas/frankenphp:static-builder-gnu +# docker cp static-builder-gnu:/go/src/app/dist/frankenphp-linux-x86_64 frankenphp +# +# The musl flavor links with zig, which opens a lot of file descriptors when many +# extensions are built statically; give it `--ulimit nofile=8192:8192`. +# +# Every variable build-static.sh understands (PHP_VERSION, PHP_EXTENSIONS, +# XCADDY_ARGS, EMBED, MIMALLOC, ...) is passed with `docker run -e`; there is no +# need to rebuild the image to change them. +FROM ghcr.io/static-php/packages-builder-rhel-8:latest + +LABEL org.opencontainers.image.title=FrankenPHP +LABEL org.opencontainers.image.description="The modern PHP app server" +LABEL org.opencontainers.image.url=https://frankenphp.dev +LABEL org.opencontainers.image.source=https://github.com/php/frankenphp +LABEL org.opencontainers.image.licenses=MIT +LABEL org.opencontainers.image.vendor="Kévin Dunglas" + +ARG FRANKENPHP_VERSION='' +ENV FRANKENPHP_VERSION=${FRANKENPHP_VERSION} + +ARG LIBC=gnu +ENV LIBC=${LIBC} + +# static-php-cli downloads the Go toolchain it needs, don't let go.mod fetch another one. +ENV GOTOOLCHAIN=local + +# we build with OPENSSLDIR=/etc/ssl which doesn't exist in RHEL +RUN ln -s ../pki/tls/cert.pem /etc/ssl/cert.pem + +WORKDIR /go/src/app +COPY --link . ./ + +CMD ["./build-static.sh"]