From 3bc426482aed2912284b6ab2d26ee68ac6bbc22d Mon Sep 17 00:00:00 2001 From: Jerry Ma Date: Sat, 22 Mar 2025 18:41:47 +0800 Subject: [PATCH] feat: add glibc-based static binary (#1438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add gnu static binary build support * Remove --libc option * configure ./build-static.sh to allow extension loading with glibc * use tabs everywhere * do not use prebuilt sources for glibc build * ffi does not work with musl builds * remove unnecessary tabs * disable opcache jit on musl * disable opcache jit on musl again * err, build command, not download command * cs fixes * spellcheck * even more cs fixes * fix ar removing .a libs * disable ffi extension for now * add gnu static action * add gnu-static target * skip CHECKOV 2 and 3 * rename static-builder to static-builder-musl, gnu-static to static-builder-gnu run arm64 gnu job on ubuntu-arm * rename build-linux to build-linux-musl * rename job description to specify musl * higher optimisation flags * Update docker-bake.hcl --------- Co-authored-by: DubbleClick Co-authored-by: Kévin Dunglas --- .github/workflows/static.yaml | 162 +++++++++++++++-- CONTRIBUTING.md | 2 +- build-static.sh | 164 ++++++++++++------ docker-bake.hcl | 33 +++- docs/cn/CONTRIBUTING.md | 2 +- docs/cn/static.md | 2 +- docs/fr/static.md | 2 +- docs/ru/CONTRIBUTING.md | 2 +- docs/ru/static.md | 2 +- docs/static.md | 2 +- docs/tr/CONTRIBUTING.md | 2 +- docs/tr/static.md | 2 +- static-builder-gnu.Dockerfile | 135 ++++++++++++++ ...ckerfile => static-builder-musl.Dockerfile | 4 + 14 files changed, 433 insertions(+), 83 deletions(-) create mode 100644 static-builder-gnu.Dockerfile rename static-builder.Dockerfile => static-builder-musl.Dockerfile (93%) diff --git a/.github/workflows/static.yaml b/.github/workflows/static.yaml index 8b32cc9b..74143237 100644 --- a/.github/workflows/static.yaml +++ b/.github/workflows/static.yaml @@ -36,6 +36,7 @@ jobs: push: ${{ toJson((steps.check.outputs.ref || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')) && true || false) }} platforms: ${{ steps.matrix.outputs.platforms }} metadata: ${{ steps.matrix.outputs.metadata }} + gnu_metadata: ${{ steps.matrix.outputs.gnu_metadata }} ref: ${{ steps.check.outputs.ref }} steps: - name: Get version @@ -58,15 +59,17 @@ jobs: - name: Create platforms matrix id: matrix run: | - METADATA="$(docker buildx bake --print static-builder | jq -c)" + METADATA="$(docker buildx bake --print static-builder-musl | jq -c)" + GNU_METADATA="$(docker buildx bake --print static-builder-gnu | jq -c)" { echo metadata="${METADATA}" echo platforms="$(jq -c 'first(.target[]) | .platforms' <<< "${METADATA}")" + echo gnu_metadata="${GNU_METADATA}" } >> "${GITHUB_OUTPUT}" env: SHA: ${{ github.sha }} VERSION: ${{ steps.check.outputs.ref || 'dev' }} - build-linux: + build-linux-musl: strategy: fail-fast: false matrix: @@ -79,7 +82,7 @@ jobs: debug: true - platform: linux/amd64 mimalloc: true - name: Build ${{ matrix.platform }} static binary${{ matrix.debug && ' (debug)' || '' }}${{ matrix.mimalloc && ' (mimalloc)' || '' }} + name: Build ${{ matrix.platform }} static musl binary${{ matrix.debug && ' (debug)' || '' }}${{ matrix.mimalloc && ' (mimalloc)' || '' }} runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} needs: [prepare] steps: @@ -107,16 +110,16 @@ jobs: with: pull: true load: ${{ !fromJson(needs.prepare.outputs.push) || matrix.debug || matrix.mimalloc }} - targets: static-builder + targets: static-builder-musl set: | - ${{ matrix.debug && 'static-builder.args.DEBUG_SYMBOLS=1' || '' }} - ${{ matrix.mimalloc && 'static-builder.args.MIMALLOC=1' || '' }} - ${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder.args.NO_COMPRESS=1' || '' }} + ${{ matrix.debug && 'static-builder-musl.args.DEBUG_SYMBOLS=1' || '' }} + ${{ matrix.mimalloc && 'static-builder-musl.args.MIMALLOC=1' || '' }} + ${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder-musl.args.NO_COMPRESS=1' || '' }} *.tags= *.platform=${{ matrix.platform }} - *.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} - *.cache-from=type=gha,scope=refs/heads/main-static-builder${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} - *.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }},ignore-error=true + *.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} + *.cache-from=type=gha,scope=refs/heads/main-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} + *.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }},ignore-error=true ${{ (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 }} @@ -129,7 +132,7 @@ jobs: mkdir -p /tmp/metadata # shellcheck disable=SC2086 - digest=$(jq -r '."static-builder"."containerimage.digest"' <<< ${METADATA}) + digest=$(jq -r '."static-builder-musl"."containerimage.digest"' <<< ${METADATA}) touch "/tmp/metadata/${digest#sha256:}" env: METADATA: ${{ steps.build.outputs.metadata }} @@ -137,16 +140,16 @@ jobs: if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc uses: actions/upload-artifact@v4 with: - name: metadata-static-builder-${{ steps.prepare.outputs.sanitized_platform }} + name: metadata-static-builder-musl-${{ steps.prepare.outputs.sanitized_platform }} path: /tmp/metadata/* if-no-files-found: error retention-days: 1 - name: Copy binary run: | # shellcheck disable=SC2034 - digest=$(jq -r '."static-builder"."${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}") - docker create --platform=${{ matrix.platform }} --name static-builder "${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '${IMAGE_NAME}@${digest}' || '${digest}' }}" - docker cp "static-builder:/go/src/app/dist/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}" + digest=$(jq -r '."static-builder-musl"."${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}") + docker create --platform=${{ matrix.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' || '' }}" env: METADATA: ${{ steps.build.outputs.metadata }} BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }} @@ -177,20 +180,127 @@ jobs: env: BINARY: ./frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} + build-linux-gnu: + strategy: + fail-fast: false + matrix: + platform: ${{ fromJson(needs.prepare.outputs.platforms) }} + name: Build ${{ matrix.platform }} static GNU binary + runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} + needs: [prepare] + steps: + - name: Prepare + id: prepare + run: | + platform=${{ matrix.platform }} + echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}" + - uses: actions/checkout@v4 + with: + ref: ${{ needs.prepare.outputs.ref }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + platforms: ${{ matrix.platform }} + - name: Login to DockerHub + if: ${{ fromJson(needs.prepare.outputs.push) }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + - name: Build + id: build + uses: docker/bake-action@v6 + with: + pull: true + load: ${{ !fromJson(needs.prepare.outputs.push) }} + targets: static-builder-gnu + set: | + ${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder-gnu.args.NO_COMPRESS=1' || '' }} + *.tags= + *.platform=${{ matrix.platform }} + *.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-gnu + *.cache-from=type=gha,scope=refs/heads/main-static-builder-gnu + *.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-gnu,ignore-error=true + ${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0}-gnu,push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }} + env: + SHA: ${{ github.sha }} + VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || 'dev' }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600 + name: Export metadata + if: fromJson(needs.prepare.outputs.push) + run: | + mkdir -p /tmp/metadata-gnu + + # shellcheck disable=SC2086 + digest=$(jq -r '."static-builder-gnu"."containerimage.digest"' <<< ${METADATA}) + touch "/tmp/metadata-gnu/${digest#sha256:}" + env: + METADATA: ${{ steps.build.outputs.metadata }} + - name: Upload metadata + if: fromJson(needs.prepare.outputs.push) + uses: actions/upload-artifact@v4 + with: + name: metadata-static-builder-gnu-${{ steps.prepare.outputs.sanitized_platform }} + path: /tmp/metadata-gnu/* + if-no-files-found: error + retention-days: 1 + - name: Copy binary + run: | + # shellcheck disable=SC2034 + digest=$(jq -r '."static-builder-gnu"."${{ fromJson(needs.prepare.outputs.push) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}") + docker create --platform=${{ matrix.platform }} --name static-builder-gnu "${{ fromJson(needs.prepare.outputs.push) && format('{0}-gnu@{1}', env.IMAGE_NAME, '${digest}') || '${digest}' }}" + docker cp "static-builder-gnu:/go/src/app/dist/${BINARY}" "${BINARY}-gnu" + env: + METADATA: ${{ steps.build.outputs.metadata }} + BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }} + - name: Upload artifact + if: ${{ !fromJson(needs.prepare.outputs.push) }} + uses: actions/upload-artifact@v4 + with: + name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu + path: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu + - name: Upload assets + if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag') + run: gh release upload "${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}" frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu --repo dunglas/frankenphp --clobber + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag') + uses: actions/attest-build-provenance@v2 + with: + subject-path: ${{ github.workspace }}/frankenphp-linux-*-gnu + - name: Run sanity checks + run: | + "${BINARY}" version + "${BINARY}" list-modules | grep frankenphp + "${BINARY}" list-modules | grep http.encoders.br + "${BINARY}" list-modules | grep http.handlers.mercure + "${BINARY}" list-modules | grep http.handlers.mercure + "${BINARY}" list-modules | grep http.handlers.vulcain + env: + BINARY: ./frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu + # Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/ push: runs-on: ubuntu-24.04 needs: - prepare - - build-linux + - build-linux-musl + - build-linux-gnu if: fromJson(needs.prepare.outputs.push) steps: - name: Download metadata uses: actions/download-artifact@v4 with: - pattern: metadata-static-builder-* + pattern: metadata-static-builder-musl-* path: /tmp/metadata merge-multiple: true + - name: Download GNU metadata + uses: actions/download-artifact@v4 + with: + pattern: metadata-static-builder-gnu-* + path: /tmp/metadata-gnu + merge-multiple: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to DockerHub @@ -202,16 +312,30 @@ jobs: working-directory: /tmp/metadata run: | # shellcheck disable=SC2046,SC2086 - docker buildx imagetools create $(jq -cr '.target."static-builder".tags | map("-t " + .) | join(" ")' <<< "${METADATA}") \ + docker buildx imagetools create $(jq -cr '.target."static-builder-musl".tags | map("-t " + .) | join(" ")' <<< "${METADATA}") \ $(printf "${IMAGE_NAME}@sha256:%s " *) env: METADATA: ${{ needs.prepare.outputs.metadata }} + - name: Create GNU manifest list and push + working-directory: /tmp/metadata-gnu + run: | + # shellcheck disable=SC2046,SC2086 + docker buildx imagetools create $(jq -cr '.target."static-builder-gnu".tags | map("-t " + . + "-gnu") | join(" ")' <<< "${GNU_METADATA}") \ + $(printf "${IMAGE_NAME}-gnu@sha256:%s " *) + env: + GNU_METADATA: ${{ needs.prepare.outputs.gnu_metadata }} - name: Inspect image run: | # shellcheck disable=SC2046,SC2086 - docker buildx imagetools inspect "$(jq -cr '.target."static-builder".tags | first' <<< "${METADATA}")" + docker buildx imagetools inspect "$(jq -cr '.target."static-builder-musl".tags | first' <<< "${METADATA}")" env: METADATA: ${{ needs.prepare.outputs.metadata }} + - name: Inspect GNU image + run: | + # shellcheck disable=SC2046,SC2086 + docker buildx imagetools inspect "$(jq -cr '.target."static-builder-gnu".tags | first' <<< "${GNU_METADATA}")-gnu" + env: + GNU_METADATA: ${{ needs.prepare.outputs.gnu_metadata }} build-mac: strategy: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b7b7796e..a720e13f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,7 +117,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push --set static-builder.args.DEBUG_SYMBOLS=1 \ --set "static-builder.platform=linux/amd64" \ static-builder - docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp + docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ``` 2. Replace your current version of `frankenphp` by the debug FrankenPHP executable diff --git a/build-static.sh b/build-static.sh index 517a8298..15945c6d 100755 --- a/build-static.sh +++ b/build-static.sh @@ -10,8 +10,63 @@ fi arch="$(uname -m)" os="$(uname -s | tr '[:upper:]' '[:lower:]')" -# FIXME: re-enable PHP errors when SPC will be compatible with PHP 8.4 -spcCommand="php -ddisplay_errors=Off ./bin/spc" + +# 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) +# - 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="" + if [ "${SPC_LIBC}" = "musl" ]; then + SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --disable-opcache-jit" + fi +fi +# init spc download additional args +if [ -z "${SPC_OPT_DOWNLOAD_ARGS}" ]; then + if [ "${SPC_LIBC}" = "musl" ]; then + SPC_OPT_DOWNLOAD_ARGS="--prefer-pre-built --ignore-cache-sources=php-src" + else + SPC_OPT_DOWNLOAD_ARGS="--ignore-cache-sources=php-src" + 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 + export PHP_VERSION="8.4" +fi +# default extension set +defaultExtensions="apcu,bcmath,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,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,zip,zlib,yaml,zstd" +# if [ "${os}" != "linux" ] || [ "${SPC_LIBC}" = "glibc" ]; then +# defaultExtensions="${defaultExtensions},ffi" +# fi +defaultExtensionLibs="bzip2,freetype,libavif,libjpeg,liblz4,libwebp,libzip,nghttp2" + md5binary="md5sum" if [ "${os}" = "darwin" ]; then os="mac" @@ -35,32 +90,6 @@ else fpie="-fpie" fi -if [ -z "${PHP_EXTENSIONS}" ]; then - if [ -n "${EMBED}" ] && [ -f "${EMBED}/composer.json" ]; then - cd "${EMBED}" - # read the composer.json file and extract the required PHP extensions - # remove internal extensions from the list: https://github.com/crazywhalecc/static-php-cli/blob/4b16631d45a57370b4747df15c8f105130e96d03/src/globals/defines.php#L26-L34 - PHP_EXTENSIONS="$(composer check-platform-reqs --no-dev 2>/dev/null | grep ^ext | sed -e 's/^ext-core//' -e 's/^ext-hash//' -e 's/^ext-json//' -e 's/^ext-pcre//' -e 's/^ext-reflection//' -e 's/^ext-spl//' -e 's/^ext-standard//' -e 's/^ext-//' -e 's/ .*//' | xargs | tr ' ' ',')" - export PHP_EXTENSIONS - cd - - else - export PHP_EXTENSIONS="apcu,bcmath,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,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,zip,zlib,yaml,zstd" - fi -fi - -if [ -z "${PHP_EXTENSION_LIBS}" ]; then - export PHP_EXTENSION_LIBS="bzip2,freetype,libavif,libjpeg,liblz4,libwebp,libzip,nghttp2" -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 - export PHP_EXTENSION_LIBS="${PHP_EXTENSION_LIBS},brotli" -fi - -if [ -z "${PHP_VERSION}" ]; then - export PHP_VERSION="8.4" -fi - if [ -z "${FRANKENPHP_VERSION}" ]; then FRANKENPHP_VERSION="$(git rev-parse --verify HEAD)" export FRANKENPHP_VERSION @@ -98,14 +127,6 @@ else cd dist/ echo -n "${cache_key}" >cache_key - if [ -d "static-php-cli/" ]; then - cd static-php-cli/ - git pull - else - git clone --depth 1 https://github.com/crazywhalecc/static-php-cli - cd static-php-cli/ - fi - if type "brew" >/dev/null 2>&1; then if ! type "composer" >/dev/null; then packages="composer" @@ -123,20 +144,49 @@ else fi fi - composer install --no-dev -a - - if [ "${os}" = "linux" ]; then - extraOpts="--disable-opcache-jit" + if [ "${SPC_REL_TYPE}" = "binary" ]; then + mkdir static-php-cli/ + cd static-php-cli/ + curl -o spc -fsSL "https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-$(uname -m)" + chmod +x spc + spcCommand="./spc" + elif [ -d "static-php-cli/src" ]; then + cd static-php-cli/ + git pull + composer install --no-dev -a + 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 + spcCommand="./bin/spc" fi - if [ -n "${DEBUG_SYMBOLS}" ]; then - extraOpts="${extraOpts} --no-strip" + # 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/installed.json" ]; then + cd "${EMBED}" + # 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 libs 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 ${spcCommand} doctor --auto-fix - ${spcCommand} download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" --ignore-cache-sources=php-src --prefer-pre-built # shellcheck disable=SC2086 - ${spcCommand} build --debug --enable-zts --build-embed ${extraOpts} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" + ${spcCommand} download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" ${SPC_OPT_DOWNLOAD_ARGS} + # shellcheck disable=SC2086 + ${spcCommand} build --enable-zts --build-embed ${SPC_OPT_BUILD_ARGS} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" fi if ! type "go" >/dev/null 2>&1; then @@ -166,7 +216,12 @@ curl -f --retry 5 "${curlGitHubHeaders[@]}" https://api.github.com/repos/e-dant/ xargs curl -fL --retry 5 "${curlGitHubHeaders[@]}" | tar xz --strip-components 1 cd watcher-c -cc -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra "${fpic}" +if [ -z "${CC}" ]; then + watcherCC=cc +else + watcherCC="${CC}" +fi +${watcherCC} -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra "${fpic}" ar rcs libwatcher-c.a libwatcher-c.o cp libwatcher-c.a ../../buildroot/lib/libwatcher-c.a mkdir -p ../../buildroot/include/wtr @@ -188,12 +243,15 @@ if [ "${os}" = "mac" ]; then elif [ "${os}" = "linux" ] && [ -z "${DEBUG_SYMBOLS}" ]; then CGO_LDFLAGS="-Wl,-O1 -pie" fi +if [ "${os}" = "linux" ] && [ "${SPC_LIBC}" = "glibc" ]; then + CGO_LDFLAGS="${CGO_LDFLAGS} -Wl,--allow-multiple-definition -Wl,--export-dynamic" +fi CGO_LDFLAGS="${CGO_LDFLAGS} ${PWD}/buildroot/lib/libbrotlicommon.a ${PWD}/buildroot/lib/libbrotlienc.a ${PWD}/buildroot/lib/libbrotlidec.a ${PWD}/buildroot/lib/libwatcher-c.a $(${spcCommand} spc-config "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" --libs)" -if [ "${os}" = "linux" ]; then - if echo "${PHP_EXTENSIONS}" | grep -qE "\b(intl|imagick|grpc|v8js|protobuf|mongodb|tbb)\b"; then - CGO_LDFLAGS="${CGO_LDFLAGS} -lstdc++" - fi +if [ "${os}" = "linux" ] && [ "${SPC_LIBC}" = "glibc" ]; then + CGO_LDFLAGS="${CGO_LDFLAGS//-lphp/-Wl,--whole-archive -lphp -Wl,--no-whole-archive}" + # shellcheck disable=SC2046 + ar d "${PWD}/buildroot/lib/libphp.a" $(ar t "${PWD}/buildroot/lib/libphp.a" | grep '\.a$') fi export CGO_LDFLAGS @@ -302,9 +360,15 @@ fi go env cd caddy/ +if [ -z "${SPC_LIBC}" ] || [ "${SPC_LIBC}" = "musl" ]; then + xcaddyGoBuildFlags="-buildmode=pie -tags cgo,netgo,osusergo,static_build,nobadger,nomysql,nopgx -ldflags \"-linkmode=external -extldflags '-static-pie ${extraExtldflags}' ${extraLdflags} -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ${FRANKENPHP_VERSION} PHP ${LIBPHP_VERSION} Caddy'\"" +elif [ "${SPC_LIBC}" = "glibc" ]; then + xcaddyGoBuildFlags="-buildmode=pie -tags cgo,netgo,osusergo,nobadger,nomysql,nopgx -ldflags \"-linkmode=external -extldflags '-pie ${extraExtldflags}' ${extraLdflags} -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ${FRANKENPHP_VERSION} PHP ${LIBPHP_VERSION} Caddy'\"" +fi + # shellcheck disable=SC2086 CGO_ENABLED=1 \ - XCADDY_GO_BUILD_FLAGS="-buildmode=pie -tags cgo,netgo,osusergo,static_build,nobadger,nomysql,nopgx -ldflags \"-linkmode=external -extldflags '-static-pie ${extraExtldflags}' ${extraLdflags} -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ${FRANKENPHP_VERSION} PHP ${LIBPHP_VERSION} Caddy'\"" \ + XCADDY_GO_BUILD_FLAGS=${xcaddyGoBuildFlags} \ XCADDY_DEBUG="${XCADDY_DEBUG}" \ ${XCADDY_COMMAND} build \ --output "../dist/${bin}" \ diff --git a/docker-bake.hcl b/docker-bake.hcl index f7fa2893..d3b42a16 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -118,20 +118,43 @@ target "default" { } } -target "static-builder" { +target "static-builder-musl" { contexts = { golang-base = "docker-image://golang:${GO_VERSION}-alpine" } - dockerfile = "static-builder.Dockerfile" + dockerfile = "static-builder-musl.Dockerfile" context = "./" platforms = [ "linux/amd64", "linux/arm64", ] tags = distinct(flatten([ - LATEST ? "${IMAGE_NAME}:static-builder" : "", - SHA == "" || VERSION != "dev" ? "" : "${IMAGE_NAME}:static-builder-sha-${substr(SHA, 0, 7)}", - VERSION == "dev" ? [] : [for v in semver(VERSION) : "${IMAGE_NAME}:static-builder-${v}"] + 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" = "${timestamp()}" + "org.opencontainers.image.version" = VERSION + "org.opencontainers.image.revision" = SHA + } + args = { + FRANKENPHP_VERSION = VERSION + } + 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}"] ])) labels = { "org.opencontainers.image.created" = "${timestamp()}" diff --git a/docs/cn/CONTRIBUTING.md b/docs/cn/CONTRIBUTING.md index 66be9ab2..957fd991 100644 --- a/docs/cn/CONTRIBUTING.md +++ b/docs/cn/CONTRIBUTING.md @@ -114,7 +114,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push --set static-builder.args.DEBUG_SYMBOLS=1 \ --set "static-builder.platform=linux/amd64" \ static-builder - docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp + docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ``` 2. 将当前版本的 `frankenphp` 替换为 debug FrankenPHP 可执行文件 diff --git a/docs/cn/static.md b/docs/cn/static.md index 73537849..daf3d5c7 100644 --- a/docs/cn/static.md +++ b/docs/cn/static.md @@ -13,7 +13,7 @@ FrankenPHP 还支持 [将 PHP 应用程序嵌入到静态二进制文件中](emb ```console docker buildx bake --load static-builder -docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder +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 ``` 生成的静态二进制文件名为 `frankenphp`,可在当前目录中找到。 diff --git a/docs/fr/static.md b/docs/fr/static.md index e28c5c2d..3fce5f74 100644 --- a/docs/fr/static.md +++ b/docs/fr/static.md @@ -12,7 +12,7 @@ Nous fournissons une image Docker pour créer un binaire statique pour Linux : ```console docker buildx bake --load static-builder -docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder +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 ``` Le binaire statique résultant est nommé `frankenphp`, et il est disponible dans le répertoire courant. diff --git a/docs/ru/CONTRIBUTING.md b/docs/ru/CONTRIBUTING.md index d3e4e6f5..564e95d5 100644 --- a/docs/ru/CONTRIBUTING.md +++ b/docs/ru/CONTRIBUTING.md @@ -114,7 +114,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push --set static-builder.args.DEBUG_SYMBOLS=1 \ --set "static-builder.platform=linux/amd64" \ static-builder - docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp + docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ``` 2. Замените текущую версию `frankenphp` на бинарный файл с включенным отладочным режимом. diff --git a/docs/ru/static.md b/docs/ru/static.md index e03b1ce8..a898c40d 100644 --- a/docs/ru/static.md +++ b/docs/ru/static.md @@ -12,7 +12,7 @@ FrankenPHP также поддерживает [встраивание PHP-пр ```console docker buildx bake --load static-builder -docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder +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 ``` Созданный статический бинарный файл называется `frankenphp` и будет доступен в текущей директории. diff --git a/docs/static.md b/docs/static.md index 4df67347..2aa768c3 100644 --- a/docs/static.md +++ b/docs/static.md @@ -13,7 +13,7 @@ We provide a Docker image to build a Linux static binary: ```console docker buildx bake --load static-builder -docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder +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 ``` The resulting static binary is named `frankenphp` and is available in the current directory. diff --git a/docs/tr/CONTRIBUTING.md b/docs/tr/CONTRIBUTING.md index 4f6e33c8..8962548d 100644 --- a/docs/tr/CONTRIBUTING.md +++ b/docs/tr/CONTRIBUTING.md @@ -114,7 +114,7 @@ docker buildx bake -f docker-bake.hcl --pull --no-cache --push --set static-builder.args.DEBUG_SYMBOLS=1 \ --set "static-builder.platform=linux/amd64" \ static-builder - docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp + docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ``` 2. Mevcut `frankenphp` sürümünüzü hata ayıklama FrankenPHP çalıştırılabilir dosyasıyla değiştirin diff --git a/docs/tr/static.md b/docs/tr/static.md index f07572b6..89095b3c 100644 --- a/docs/tr/static.md +++ b/docs/tr/static.md @@ -13,7 +13,7 @@ Linux statik binary dosyası oluşturmak için bir Docker imajı sağlıyoruz: ```console docker buildx bake --load static-builder -docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder +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 ``` Elde edilen statik binary `frankenphp` olarak adlandırılır ve geçerli dizinde kullanılabilir. diff --git a/static-builder-gnu.Dockerfile b/static-builder-gnu.Dockerfile new file mode 100644 index 00000000..120126bb --- /dev/null +++ b/static-builder-gnu.Dockerfile @@ -0,0 +1,135 @@ +# 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='' + +# args passed to xcaddy +ARG XCADDY_ARGS='' +ARG CLEAN='' +ARG EMBED='' +ARG DEBUG_SYMBOLS='' +ARG MIMALLOC='' +ARG NO_COMPRESS='' + +# go version +ENV GO_VERSION=1.24.1 + +# 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/dunglas/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 newer cmake to build some newer libs +RUN curl -o cmake.tgz -fsSL https://github.com/Kitware/CMake/releases/download/v3.31.4/cmake-3.31.4-linux-$(uname -m).tar.gz && \ + mkdir /cmake && \ + tar -xzf cmake.tgz -C /cmake --strip-components 1 && \ + rm cmake.tgz + +# 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.tgz -fsSL https://ftp.gnu.org/gnu/make/make-4.4.tar.gz && \ + tar -zxvf make.tgz && \ + rm make.tgz && \ + cd make-4.4 && \ + ./configure && \ + make && \ + make install && \ + ln -sf /usr/local/bin/make /usr/bin/make ; \ + if [ "$(uname -m)" = "aarch64" ]; then \ + GO_ARCH="arm64" ; \ + else \ + GO_ARCH="amd64" ; \ + fi ; \ + curl -o go.tgz -fsSL https://go.dev/dl/go${GO_VERSION}.linux-${GO_ARCH}.tar.gz && \ + rm -rf /usr/local/go && \ + tar -C /usr/local -xzf go.tgz && \ + rm go.tgz && \ + /usr/local/go/bin/go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest + +ENV PATH="/cmake/bin:/usr/local/go/bin:$PATH" + +# Apply gnu mode +ENV CC='/opt/rh/devtoolset-10/root/usr/bin/gcc' +ENV CXX='/opt/rh/devtoolset-10/root/usr/bin/g++' +ENV AR='/opt/rh/devtoolset-10/root/usr/bin/ar' +ENV LD='/opt/rh/devtoolset-10/root/usr/bin/ld' +ENV SPC_DEFAULT_C_FLAGS='-fPIE -fPIC -O3 -march=native' +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' +ENV SPC_OPT_DOWNLOAD_ARGS='--ignore-cache-sources=php-src' +ENV SPC_OPT_BUILD_ARGS='' +ENV SPC_REL_TYPE='binary' + +# not sure if this is needed +ENV COMPOSER_ALLOW_SUPERUSER=1 + +WORKDIR /go/src/app +COPY go.mod go.sum ./ +RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get + +WORKDIR /go/src/app/caddy +COPY caddy/go.mod caddy/go.sum ./ +RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get + +WORKDIR /go/src/app +COPY --link *.* ./ +COPY --link caddy caddy +COPY --link internal internal + +RUN --mount=type=secret,id=github-token ./build-static.sh && \ + rm -Rf dist/static-php-cli/source/* diff --git a/static-builder.Dockerfile b/static-builder-musl.Dockerfile similarity index 93% rename from static-builder.Dockerfile rename to static-builder-musl.Dockerfile index 7bc79c72..e122862b 100644 --- a/static-builder.Dockerfile +++ b/static-builder-musl.Dockerfile @@ -90,5 +90,9 @@ RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get WORKDIR /go/src/app COPY --link . ./ +ENV SPC_DEFAULT_C_FLAGS='-fPIE -fPIC -O3 -march=native' +ENV SPC_LIBC='musl' +ENV SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM='-Wl,-O3 -pie' + RUN --mount=type=secret,id=github-token GITHUB_TOKEN=$(cat /run/secrets/github-token) ./build-static.sh && \ rm -Rf dist/static-php-cli/source/*