Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions .github/scripts/install-alpine-php.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,63 @@
# no thread-safe PHP, while the official php:*-alpine Docker images are
# thread-safe from 8.6 on — and for a minor the Alpine release no longer
# packages (8.2).
#
# That build takes minutes, so with $PHP_CACHE_DIR set the PHP it installs
# into /usr/local (where nothing else lives in the Alpine images) is kept
# there as php.tar, and installed from there when the directory already has
# it. phar.yml keeps the directory in actions/cache under the key
# `install-alpine-php.sh --resolve` prints: the Alpine release, the
# architecture, the PHP release build-php.sh resolves and the thread
# safety, so a new patch release (or release candidate) or a new Alpine
# snapshot builds again. --resolve installs no PHP and prints
# `release=<key>` when this leg builds PHP, `release=` when it installs
# Alpine's packages.
set -eu

apk add --no-cache bash curl git make g++ musl-dev linux-headers patch tar zstd
apk add --no-cache bash curl git make g++ musl-dev linux-headers patch tar zstd >&2

V="$(echo "$PHP_MINOR" | tr -d .)"
# Alpine's php86 packages are currently only in edge/testing.
if [ "$PHP_MINOR" = "8.6" ]; then
echo 'https://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories
EDGE_TESTING='https://dl-cdn.alpinelinux.org/alpine/edge/testing'
if [ "$PHP_MINOR" = "8.6" ] && ! grep -qx "$EDGE_TESTING" /etc/apk/repositories; then
echo "$EDGE_TESTING" >> /etc/apk/repositories
fi

BUILD_PHP="$(dirname "$0")/../turbo-build/build-php.sh"
BUILDS_PHP=0
if [ "${PHP_ZTS:-0}" = "1" ] || [ -z "$(apk search -q -x "php$V")" ]; then
BUILDS_PHP=1
fi

if [ "${1:-}" = "--resolve" ]; then
if [ "$BUILDS_PHP" = "0" ]; then
echo "release="
exit 0
fi
apk add --no-cache jq >&2
RELEASE="$(PHP_MINOR="$PHP_MINOR" PHP_RESOLVE_ONLY=1 sh "$BUILD_PHP")"
ZTS_SUFFIX=""
if [ "${PHP_ZTS:-0}" = "1" ]; then
ZTS_SUFFIX="-zts"
fi
echo "release=alpine$(cat /etc/alpine-release)-$(uname -m)-php$RELEASE$ZTS_SUFFIX"
exit 0
fi

if [ "$BUILDS_PHP" = "1" ]; then
apk add --no-cache jq pkgconf xz libxml2-dev oniguruma-dev curl-dev openssl-dev zlib-dev
PHP_MINOR="$PHP_MINOR" PHP_ZTS="${PHP_ZTS:-0}" sh "$(dirname "$0")/../turbo-build/build-php.sh"
if [ -n "${PHP_CACHE_DIR:-}" ] && [ -f "$PHP_CACHE_DIR/php.tar" ]; then
if tar -xf "$PHP_CACHE_DIR/php.tar" -C /usr/local && php -r 'exit((int) PHP_ZTS === (int) getenv("PHP_ZTS") ? 0 : 1);'; then
echo "install-alpine-php.sh: installed PHP $(php -r 'echo PHP_VERSION;') from $PHP_CACHE_DIR"
exit 0
fi
echo "install-alpine-php.sh: the PHP in $PHP_CACHE_DIR does not run here; building it again" >&2
fi
PHP_MINOR="$PHP_MINOR" PHP_ZTS="${PHP_ZTS:-0}" sh "$BUILD_PHP"
if [ -n "${PHP_CACHE_DIR:-}" ]; then
mkdir -p "$PHP_CACHE_DIR"
tar -cf "$PHP_CACHE_DIR/php.tar" -C /usr/local .
fi
exit 0
fi

Expand Down
6 changes: 6 additions & 0 deletions .github/turbo-build/build-php.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ if [ -z "$PHP_URL" ] || [ -z "$PHP_SHA256" ]; then
echo "build-php.sh: php.net lists no tar.xz with a sha256 for PHP $PHP_VERSION" >&2
exit 1
fi
# install-alpine-php.sh --resolve: the release alone, for the cache key of
# the PHP it builds
if [ "${PHP_RESOLVE_ONLY:-0}" = "1" ]; then
echo "$PHP_VERSION"
exit 0
fi
echo "build-php.sh: building PHP $PHP_VERSION from $PHP_URL"
export PHP_VERSION
PHP_ZTS="${PHP_ZTS:-0}"
Expand Down
128 changes: 117 additions & 11 deletions .github/workflows/phar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@
PHP_MINOR: ${{ matrix.target.php-version }}
PHP_ZTS: "0"
TURBO_ARTIFACT: "phpstan_turbo_core-${{ matrix.target.name }}"
PGO_PROFILE_ARTIFACT: "turbo-pgo-profile-${{ matrix.target.name }}"
ORIGIN_RUN_ID: ${{ fromJSON(needs.turbo-origins.outputs.origins || '{}')[format('phpstan_turbo_core-{0}', matrix.target.name)] }}

strategy:
Expand Down Expand Up @@ -544,6 +545,17 @@
run-id: ${{ env.ORIGIN_RUN_ID }}
github-token: ${{ github.token }}

# the profile that core was compiled with, uploaded before it (see
# "Upload the core's profile")
- name: "Download the profile of the core compiled from identical build inputs"
if: env.ORIGIN_RUN_ID != ''
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ env.PGO_PROFILE_ARTIFACT }}
path: "turbo-ext"
run-id: ${{ env.ORIGIN_RUN_ID }}
github-token: ${{ github.token }}

- name: "Record the run that compiled the core"
if: env.ORIGIN_RUN_ID != ''
run: echo "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$ORIGIN_RUN_ID" | tee turbo-reused.txt
Expand Down Expand Up @@ -614,8 +626,11 @@
# locally, so a profile-guided core can be reproduced outside CI.
# The strict set itself lives in turbo-ext/Makefile
# (STRICT_WARN_FLAGS), which documents why each exemption is there.
# `make pgo-pack` keeps the core's share of the profile for the pie
# legs of turbo-compile, which build with it instead of training again.
run: |
make SPLIT=1 pgo WARN_FLAGS="$(make -s print-warn-flags)" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"
make SPLIT=1 pgo-pack

# Before signing (stripping invalidates a signature) and before the
# upload. The symbol tables are a quarter of the binary; see `strip`
Expand Down Expand Up @@ -651,6 +666,16 @@
echo "core + extension report: $REPORTED, enabler expects: $EXPECTED"
[ "$REPORTED" = "$EXPECTED" ]

# Before the core, so that a run carrying the core (all that
# find-turbo-origins.sh checks) carries its profile too. A reused one
# is uploaded again, like the core.
- name: "Upload the core's profile"
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ env.PGO_PROFILE_ARTIFACT }}
path: "turbo-ext/pgo-profile.tar"
if-no-files-found: "error"

# A reused core is uploaded again under the same name, so the
# turbo-compile legs find it in this run.
- name: "Upload core artifact"
Expand Down Expand Up @@ -688,6 +713,7 @@
PHP_MINOR: "8.5"
PHP_ZTS: "0"
TURBO_ARTIFACT: "phpstan_turbo_core-linux-musl-arm64"
PGO_PROFILE_ARTIFACT: "turbo-pgo-profile-linux-musl-arm64"
ORIGIN_RUN_ID: ${{ fromJSON(needs.turbo-origins.outputs.origins || '{}')['phpstan_turbo_core-linux-musl-arm64'] }}

steps:
Expand All @@ -696,7 +722,7 @@
with:
egress-policy: audit

# See the same two steps in turbo-compile-core.
# See the same three steps in turbo-compile-core.
- name: "Download the core compiled from identical build inputs"
if: env.ORIGIN_RUN_ID != ''
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
Expand All @@ -706,6 +732,15 @@
run-id: ${{ env.ORIGIN_RUN_ID }}
github-token: ${{ github.token }}

- name: "Download the profile of the core compiled from identical build inputs"
if: env.ORIGIN_RUN_ID != ''
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ env.PGO_PROFILE_ARTIFACT }}
path: "turbo-ext"
run-id: ${{ env.ORIGIN_RUN_ID }}
github-token: ${{ github.token }}

- name: "Record the run that compiled the core"
if: env.ORIGIN_RUN_ID != ''
run: echo "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$ORIGIN_RUN_ID" | tee turbo-reused.txt
Expand Down Expand Up @@ -751,6 +786,7 @@
run: |
docker exec -i -w /work/turbo-ext alpine-build sh -e <<'EOF'
make SPLIT=1 pgo WARN_FLAGS="$(make -s print-warn-flags)" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"
make SPLIT=1 pgo-pack
make SPLIT=1 strip
ls -l phpstan_turbo_core.so phpstan_turbo.so
EOF
Expand All @@ -765,6 +801,13 @@
[ "$REPORTED" = "$EXPECTED" ]
EOF

- name: "Upload the core's profile"
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ env.PGO_PROFILE_ARTIFACT }}
path: "turbo-ext/pgo-profile.tar"
if-no-files-found: "error"

- name: "Upload core artifact"
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
Expand All @@ -791,7 +834,8 @@
# there, and the commit job checks that every leg of a platform carries
# the same core.
# The `pie` variant legs build the same version self-contained instead,
# profile-guided like the core — PIE installs a pre-packaged binary by
# profile-guided with the profile the platform's core was built with
# (`make pgo-from-profile`) — PIE installs a pre-packaged binary by
# copying the one extension file, so the PIE release assets cannot be
# the thin extension (see .github/scripts/turbo-pie-assets.sh). Nothing
# else ships them, so they only run for release tags (turbo-pie-release
Expand Down Expand Up @@ -884,10 +928,27 @@
- name: "Trust the checkout despite the container/host uid mismatch"
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

# The thread-safe legs and 8.2 build PHP from source, minutes on every
# leg; install-alpine-php.sh keeps that build in alpine-php-cache, under
# a key naming exactly what it built (see the script). Nothing to cache
# for a leg installing Alpine's packages (release= is empty).
- name: "Resolve the PHP build to cache (Alpine container)"
id: alpine-php
if: matrix.target.family == 'musl'
run: sh .github/scripts/install-alpine-php.sh --resolve >> "$GITHUB_OUTPUT"

- name: "Restore the PHP build (Alpine container)"
if: steps.alpine-php.outputs.release != ''
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: "alpine-php-cache"
key: "alpine-php-${{ steps.alpine-php.outputs.release }}-${{ hashFiles('.github/turbo-build/build-php.sh') }}"

# The build needs php/php-config on PATH for the matrix PHP version.
- name: "Install PHP and build tools (Alpine container)"
if: matrix.target.family == 'musl'
run: sh .github/scripts/install-alpine-php.sh
# the container's path of the workspace, which github.workspace is not
run: PHP_CACHE_DIR="$GITHUB_WORKSPACE/alpine-php-cache" sh .github/scripts/install-alpine-php.sh

- name: "Install PHP (macOS)"
if: matrix.target.family == 'macos'
Expand All @@ -910,8 +971,21 @@
run: |
make SPLIT=1 extension WARN_FLAGS="$(make -s print-warn-flags)" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"

# The profile the platform's core was compiled with (turbo-compile-core,
# `make pgo-pack`): the core is all but the four version-specific
# sources and compiles to the same code against every version's
# headers, so the self-contained build of this version uses its profile
# instead of training one of its own — which took most of the leg.
- name: "Download the profile of the platform's core"
if: matrix.variant == 'pie'
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: "turbo-pgo-profile-${{ matrix.target.name }}"
path: "turbo-ext"

# see the same steps in turbo-compile-core: the training run of
# `make pgo` runs bin/phpstan on the checkout
# `make pgo` runs bin/phpstan on the checkout (pgo-from-profile falls
# back to it when another compiler recorded the profile)
- uses: "ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda" # v4.0.0
if: matrix.variant == 'pie' && matrix.target.family == 'macos'
with:
Expand All @@ -924,12 +998,12 @@
php composer.phar install --no-interaction --no-progress
rm composer.phar

# A plain `make pgo` builds one self-contained phpstan_turbo.so.
# A plain `make pgo-use` builds one self-contained phpstan_turbo.so.
- name: "Compile the self-contained extension with strict warnings (profile-guided)"
if: matrix.variant == 'pie'
working-directory: "turbo-ext"
run: |
make pgo WARN_FLAGS="$(make -s print-warn-flags)" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"
make pgo-from-profile WARN_FLAGS="$(make -s print-warn-flags)" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"

# Before signing and the upload; a plain `make strip` leaves the core
# (already stripped and signed by its own job) untouched.
Expand Down Expand Up @@ -1027,15 +1101,35 @@
name: "phpstan_turbo_core-linux-musl-arm64"
path: "turbo-ext"

# see turbo-compile
- name: "Download the profile of the platform's core"
if: matrix.variant == 'pie'
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: "turbo-pgo-profile-linux-musl-arm64"
path: "turbo-ext"

- name: "Start the Alpine build container"
env:
ALPINE_IMAGE: ${{ matrix.php-version == '8.6' && matrix.ts == 'nts' && 'alpine:edge' || 'alpine:3.24' }}
run: |
docker run -d --name alpine-build \
-v "$PWD:/work" -w /work \
-e PHP_MINOR -e PHP_ZTS \
-e PHP_MINOR -e PHP_ZTS -e PHP_CACHE_DIR=/work/alpine-php-cache \
"$ALPINE_IMAGE" sleep 7200

# see turbo-compile
- name: "Resolve the PHP build to cache"
id: alpine-php
run: docker exec alpine-build sh .github/scripts/install-alpine-php.sh --resolve >> "$GITHUB_OUTPUT"

- name: "Restore the PHP build"
if: steps.alpine-php.outputs.release != ''
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: "alpine-php-cache"
key: "alpine-php-${{ steps.alpine-php.outputs.release }}-${{ hashFiles('.github/turbo-build/build-php.sh') }}"

- name: "Install PHP and build tools"
run: docker exec alpine-build sh .github/scripts/install-alpine-php.sh

Expand All @@ -1052,8 +1146,8 @@
ls -l phpstan_turbo.so phpstan_turbo_core.so
EOF

# see turbo-compile: the training run of `make pgo` runs bin/phpstan
# on the checkout
# see turbo-compile: the training run of `make pgo`, which
# pgo-from-profile falls back to, runs bin/phpstan on the checkout
- name: "Compile the self-contained extension with strict warnings (profile-guided)"
if: matrix.variant == 'pie'
run: |
Expand All @@ -1062,7 +1156,7 @@
php composer.phar install --no-interaction --no-progress
rm composer.phar
cd turbo-ext
make pgo WARN_FLAGS="$(make -s print-warn-flags)" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"
make pgo-from-profile WARN_FLAGS="$(make -s print-warn-flags)" -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || nproc)"
make strip
ls -l phpstan_turbo.so
EOF
Expand Down Expand Up @@ -2089,9 +2183,21 @@
run: |
docker run -d --name alpine-test \
-v "$PWD:/work" -w /work \
-e PHP_MINOR -e PHP_ZTS \
-e PHP_MINOR -e PHP_ZTS -e PHP_CACHE_DIR=/work/alpine-php-cache \
"$ALPINE_IMAGE" sleep 7200

# see turbo-compile
- name: "Resolve the PHP build to cache"
id: alpine-php
run: docker exec alpine-test sh .github/scripts/install-alpine-php.sh --resolve >> "$GITHUB_OUTPUT"

- name: "Restore the PHP build"
if: steps.alpine-php.outputs.release != ''
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: "alpine-php-cache"
key: "alpine-php-${{ steps.alpine-php.outputs.release }}-${{ hashFiles('.github/turbo-build/build-php.sh') }}"

- name: "Install PHP and tools"
run: docker exec alpine-test sh .github/scripts/install-alpine-php.sh

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ e2e/bashunit
e2e/restart-manager/probe.log
e2e/restart-manager/starts.log
e2e/restart-manager/out.txt
/alpine-php-cache
1 change: 1 addition & 0 deletions turbo-ext/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
/pgo/
/src/*.gcda
/src/parser/*.gcda
/pgo-profile.tar

# the compilation database clang-tidy reads (make lint-turbo), regenerated
# from the Makefile's own flags by bin/generate-compile-commands.php
Expand Down
2 changes: 2 additions & 0 deletions turbo-ext/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,8 @@ make SPLIT=1 # phpstan_turbo_core.so + phpstan_turbo.so (loads t
make SPLIT=1 pgo # the same, profile-guided (what turbo-compile-core runs)
make SPLIT=1 extension # only the extension, against a phpstan_turbo_core.so already here
make SPLIT=1 strip # both; a plain `make strip` leaves the core untouched
make SPLIT=1 pgo-pack # after `pgo`: the core's profile, into pgo-profile.tar
make pgo-from-profile # self-contained, from that profile (CI's pie legs); trains if it does not fit
```

A plain `make`, the phpize build (`config.m4`, what PIE drives) and
Expand Down
Loading
Loading