diff --git a/.github/workflows/spc-download.yml b/.github/workflows/spc-download.yml index 6bc1748..4f422d1 100644 --- a/.github/workflows/spc-download.yml +++ b/.github/workflows/spc-download.yml @@ -4,6 +4,12 @@ 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 @@ -18,18 +24,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + PHP_MINORS: "8.2 8.3 8.4 8.5" steps: - - name: Set architecture variables + - name: Install PHP and composer run: | - if [[ "${{ matrix.arch }}" == "arm64" ]]; then - echo "RPM_ARCH=aarch64" >> $GITHUB_ENV - else - echo "RPM_ARCH=x86_64" >> $GITHUB_ENV - fi - - - name: Install composer - run: | - sudo curl -L https://files.henderkes.com/${RPM_ARCH}-linux/php -o /usr/local/bin/php + 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 @@ -42,41 +41,176 @@ jobs: - 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 -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 latest PHP versions + id: probe-php + 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 + mkdir -p downloads + jq --argjson v "$new" -n '{php_versions: $v}' > 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 + src_to_ext=$(jq -c ' + to_entries + | map(select(.value.source != null)) + | map({(.value.source): .key}) + | add // {} + ' "$EXT_JSON") + echo "Source->ext map size: $(jq 'length' <<< "$src_to_ext")" + + 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 // "")) ] + ') + echo "Changed source keys: $(jq -c . <<< "$changed_sources")" + + changed_exts=$(jq -nc --argjson s "$changed_sources" --argjson m "$src_to_ext" ' + $s | map($m[.]) | map(select(. != null)) | unique + ') + pkgs=$(jq -r 'join(",")' <<< "$changed_exts") + echo "Changed packages: ${pkgs:-}" + + 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] // "")) ] + ') + 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:-}" + echo "- Partial rebuild PHP versions: ${partial_csv:-}" + echo "- Changed packages: ${pkgs:-}" + 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' run: | tar -czf downloads.tar.gz -C downloads . - name: Upload downloads directory + if: steps.diff.outputs.any_change == 'true' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: downloads-tarball path: downloads.tar.gz - retention-days: 2 + retention-days: 14 - - name: Trigger build-rpm-modular-packages workflow - if: success() - run: gh workflow run build-rpm-modular-packages.yml + - name: Trigger downstream workflows + if: steps.diff.outputs.any_change == 'true' env: - GH_TOKEN: ${{ secrets.GH_PAT }} # use our own user as the triggering user - - #- name: Trigger build-gcc-deb-packages workflow - # if: success() - # run: gh workflow run build-gcc-deb-packages.yml - # env: - # GH_TOKEN: ${{ secrets.GH_PAT }} # use our own user as the triggering user - - - name: Trigger build-deb-forgejo workflow - if: success() - run: gh workflow run build-deb-forgejo.yml - env: - GH_TOKEN: ${{ secrets.GH_PAT }} # use our own user as the triggering user - - - name: Trigger build-apk-forgejo workflow - if: success() - run: gh workflow run build-apk-forgejo.yml - env: - GH_TOKEN: ${{ secrets.GH_PAT }} # use our own user as the triggering user + 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