spc-download #278
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: spc-download | |
| on: | |
| schedule: | |
| - cron: '0 0 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| force_full: | |
| description: "Force a full rebuild of all PHP versions regardless of detected changes" | |
| type: boolean | |
| required: false | |
| default: false | |
| permissions: | |
| contents: read | |
| actions: write | |
| jobs: | |
| download: | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| actions: write | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| PHP_MINORS: "8.2 8.3 8.4 8.5" | |
| steps: | |
| - name: Install PHP and composer | |
| run: | | |
| sudo curl -L https://files.henderkes.com/x86_64-linux/php -o /usr/local/bin/php | |
| sudo chmod +x /usr/local/bin/php | |
| sudo curl -sS https://raw.githubusercontent.com/composer/getcomposer.org/f3108f64b4e1c1ce6eb462b159956461592b3e3e/web/installer | php -- --quiet | |
| sudo mv composer.phar /usr/local/bin/composer | |
| - name: Checkout code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - name: Composer install | |
| run: composer install | |
| - name: Restore previous state | |
| uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: .download-state | |
| key: spc-downloads-lock | |
| - name: Seed empty state if missing | |
| run: | | |
| set -euo pipefail | |
| mkdir -p .download-state | |
| [[ -f .download-state/lock.prev.json ]] || echo '{}' > .download-state/lock.prev.json | |
| [[ -f .download-state/meta.prev.json ]] || echo '{}' > .download-state/meta.prev.json | |
| - name: Download extensions | |
| run: | | |
| php vendor/bin/spc download --shallow-clone frankenphp -e amqp,apcu,ast,bcmath,brotli,bz2,calendar,ctype,curl,dba,decimal,deepclone,dio,dom,ds,ev,event,excimer,exif,ffi,fileinfo,filter,ftp,gd,gettext,gmp,gmssl,grpc,iconv,igbinary,imagick,inotify,intl,ldap,libxml,lz4,maxminddb,mbregex,mbstring,memcache,memcached,mongodb,msgpack,mysqli,mysqlnd,mysqlnd_parsec,mysqlnd_ed25519,odbc,opcache,openssl,opentelemetry,parallel,password-argon2,pcov,pcntl,pdo,pdo_mysql,pdo_odbc,pdo_pgsql,pdo_sqlite,pdo_sqlsrv,pgsql,phar,posix,protobuf,rar,rdkafka,readline,redis,session,shmop,simdjson,simplexml,snappy,soap,sockets,sodium,spx,sqlite3,sqlsrv,ssh2,swoole,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,trader,uuid,uv,xdebug,xhprof,xlswriter,xml,xmlreader,xmlwriter,xsl,xz,yac,yaml,zip,zlib,zstd | |
| - name: Probe upstream releases | |
| id: probe-upstream | |
| run: | | |
| set -euo pipefail | |
| new='{}' | |
| for v in $PHP_MINORS; do | |
| latest=$(curl -fsSL "https://www.php.net/releases/index.php?json=&max=1&version=${v}" \ | |
| | jq -r 'keys[0] // empty') | |
| if [[ -z "$latest" ]]; then | |
| echo "::error::Failed to fetch latest PHP version for ${v}" | |
| exit 1 | |
| fi | |
| echo "PHP ${v} latest: ${latest}" | |
| new=$(jq --arg v "$v" --arg l "$latest" '. + {($v): $l}' <<< "$new") | |
| done | |
| pie_latest=$(curl -fsSL \ | |
| -H "Authorization: Bearer $GITHUB_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/php/pie/releases/latest" \ | |
| | jq -r '.tag_name // empty') | |
| if [[ -z "$pie_latest" ]]; then | |
| echo "::error::Failed to fetch latest pie release" | |
| exit 1 | |
| fi | |
| echo "pie latest: ${pie_latest}" | |
| mkdir -p downloads | |
| jq --argjson v "$new" --arg pie "$pie_latest" -n \ | |
| '{php_versions: $v, pie_version: $pie}' > downloads/.spc-meta.json | |
| - name: Diff state and compute triggers | |
| id: diff | |
| env: | |
| FORCE_FULL: ${{ inputs.force_full }} | |
| run: | | |
| set -euo pipefail | |
| EXT_JSON=vendor/crazywhalecc/static-php-cli/config/ext.json | |
| ext_map=$(jq -c ' | |
| to_entries | |
| | map(select(.value.source != null)) | |
| | map({(.value.source): .key}) | |
| | add // {} | |
| ' "$EXT_JSON") | |
| # frankenphp is a SAPI source; map it 1:1 to its package name. | |
| src_to_pkg=$(jq -nc --argjson e "$ext_map" '$e + {"frankenphp":"frankenphp"}') | |
| changed_sources=$(jq -nc \ | |
| --slurpfile prev .download-state/lock.prev.json \ | |
| --slurpfile cur downloads/.lock.json ' | |
| ($prev[0] // {}) as $p | ($cur[0] // {}) as $c | | |
| [ ($c | keys[]) as $k | |
| | select(($p[$k].hash // "") != ($c[$k].hash // "")) | |
| | $k ] | |
| ') | |
| echo "Changed source keys: $(jq -c . <<< "$changed_sources")" | |
| changed_pkgs=$(jq -nc --argjson s "$changed_sources" --argjson m "$src_to_pkg" ' | |
| $s | map($m[.]) | map(select(. != null)) | unique | |
| ') | |
| # pie is downloaded outside SPC (src/package/pie.php pulls from | |
| # github.com/php/pie releases), so it never appears in lock.json. | |
| # Detect bumps via the version stashed in .spc-meta.json by probe-upstream. | |
| prev_pie=$(jq -r '.pie_version // ""' .download-state/meta.prev.json) | |
| cur_pie=$(jq -r '.pie_version // ""' downloads/.spc-meta.json) | |
| if [[ "$prev_pie" != "$cur_pie" ]]; then | |
| echo "pie version bump: ${prev_pie:-<none>} -> ${cur_pie}" | |
| changed_pkgs=$(jq -c '. + ["pie"] | unique' <<< "$changed_pkgs") | |
| fi | |
| pkgs=$(jq -r 'join(",")' <<< "$changed_pkgs") | |
| echo "Changed packages: ${pkgs:-<none>}" | |
| full_versions=$(jq -nrc \ | |
| --slurpfile prev .download-state/meta.prev.json \ | |
| --slurpfile cur downloads/.spc-meta.json ' | |
| (($prev[0].php_versions) // {}) as $p | | |
| (($cur[0].php_versions) // {}) as $c | | |
| [ ($c | keys[]) as $k | |
| | select($c[$k] != ($p[$k] // "")) | |
| | $k ] | |
| ') | |
| echo "PHP minors with bumped php-src: $(jq -c . <<< "$full_versions")" | |
| all_minors=$(jq -nc --arg s "$PHP_MINORS" '$s | split(" ")') | |
| if [[ "$FORCE_FULL" == "true" ]]; then | |
| echo "force_full input set -> rebuilding all PHP versions" | |
| full_versions="$all_minors" | |
| fi | |
| full_csv=$(jq -r 'join(",")' <<< "$full_versions") | |
| partial_csv="" | |
| if [[ -n "$pkgs" ]]; then | |
| partial_csv=$(jq -nrc \ | |
| --argjson all "$all_minors" \ | |
| --argjson full "$full_versions" ' | |
| $all - $full | join(",") | |
| ') | |
| fi | |
| any="false" | |
| if [[ -n "$pkgs" || -n "$full_csv" ]]; then | |
| any="true" | |
| fi | |
| echo "full_php=$full_csv" | tee -a $GITHUB_OUTPUT | |
| echo "partial_php=$partial_csv" | tee -a $GITHUB_OUTPUT | |
| echo "changed_packages=$pkgs" | tee -a $GITHUB_OUTPUT | |
| echo "any_change=$any" | tee -a $GITHUB_OUTPUT | |
| { | |
| echo "## spc-download summary" | |
| echo "- Full rebuild PHP versions: ${full_csv:-<none>}" | |
| echo "- Partial rebuild PHP versions: ${partial_csv:-<none>}" | |
| echo "- Changed packages: ${pkgs:-<none>}" | |
| echo "- Any change: $any" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Persist current state for next run | |
| if: hashFiles('downloads/.lock.json') != '' | |
| run: | | |
| set -euo pipefail | |
| cp downloads/.lock.json .download-state/lock.prev.json | |
| cp downloads/.spc-meta.json .download-state/meta.prev.json | |
| - name: Delete previous state cache | |
| if: hashFiles('downloads/.lock.json') != '' | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh api -X DELETE \ | |
| "/repos/$GITHUB_REPOSITORY/actions/caches?key=spc-downloads-lock" \ | |
| 2>/dev/null || true | |
| - name: Save state cache | |
| if: hashFiles('downloads/.lock.json') != '' | |
| uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: .download-state | |
| key: spc-downloads-lock | |
| - name: Create tarball (keep permissions) | |
| if: steps.diff.outputs.any_change == 'true' || github.event_name == 'workflow_dispatch' | |
| run: | | |
| tar -czf downloads.tar.gz -C downloads . | |
| - name: Upload downloads directory | |
| if: steps.diff.outputs.any_change == 'true' || github.event_name == 'workflow_dispatch' | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: downloads-tarball | |
| path: downloads.tar.gz | |
| retention-days: 14 | |
| - name: Trigger downstream workflows | |
| if: steps.diff.outputs.any_change == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| FULL_PHP: ${{ steps.diff.outputs.full_php }} | |
| PARTIAL_PHP: ${{ steps.diff.outputs.partial_php }} | |
| CHANGED_PACKAGES: ${{ steps.diff.outputs.changed_packages }} | |
| run: | | |
| set -euo pipefail | |
| workflows=( | |
| build-rpm-modular-packages.yml | |
| build-deb-forgejo.yml | |
| build-apk-forgejo.yml | |
| ) | |
| for wf in "${workflows[@]}"; do | |
| if [[ -n "$FULL_PHP" ]]; then | |
| echo ">> $wf : full rebuild for $FULL_PHP" | |
| gh workflow run "$wf" -f php_versions="$FULL_PHP" | |
| fi | |
| if [[ -n "$CHANGED_PACKAGES" && -n "$PARTIAL_PHP" ]]; then | |
| echo ">> $wf : partial rebuild for $PARTIAL_PHP, packages=$CHANGED_PACKAGES" | |
| gh workflow run "$wf" \ | |
| -f php_versions="$PARTIAL_PHP" \ | |
| -f packages="$CHANGED_PACKAGES" | |
| fi | |
| done |