|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Finds, for every turbo extension binary the compile jobs in phar.yml |
| 3 | +# produce, the newest earlier run on $BRANCH that compiled it from build |
| 4 | +# inputs identical to HEAD's, and writes {"<artifact name>": <run id>} to |
| 5 | +# the `origins` step output. A compile leg whose artifact is listed |
| 6 | +# downloads it from that run instead of building it again (see the |
| 7 | +# turbo-origins job in phar.yml). |
| 8 | +# |
| 9 | +# Only these runs qualify, each condition checked here rather than trusted |
| 10 | +# from the API query: |
| 11 | +# - runs of .github/workflows/phar.yml in this repository, triggered by a |
| 12 | +# push to $BRANCH — never a pull request, whose code nobody reviewed yet |
| 13 | +# (a fork's branch can be named $BRANCH too, hence the event and head |
| 14 | +# repository checks) |
| 15 | +# - whose head commit is an ancestor of HEAD |
| 16 | +# - whose build inputs (BUILD_INPUT_PATHS, and the compile jobs' definitions |
| 17 | +# in phar.yml) are identical to HEAD's |
| 18 | +# - for each artifact separately: the run uploaded it (the upload is the |
| 19 | +# last step of a compile leg, after the differential tests) and did not |
| 20 | +# mark it as reused itself (turbo-reused-<artifact name>) — so a binary is |
| 21 | +# always taken from the run that compiled it, never from a chain of runs |
| 22 | +# that passed it along, and it stops being reused when that run's |
| 23 | +# artifacts expire. |
| 24 | +# |
| 25 | +# Anything unexpected (an API error, a commit missing from the checkout) |
| 26 | +# disqualifies the run or ends the search; nothing here fails the job, since |
| 27 | +# an empty map only means every leg compiles. |
| 28 | + |
| 29 | +BUILD_INPUT_PATHS=( |
| 30 | + turbo-ext/src |
| 31 | + turbo-ext/Makefile |
| 32 | + turbo-ext/config.m4 |
| 33 | + turbo-ext/config.w32 |
| 34 | + turbo-ext/bin/pgo-train.sh |
| 35 | + .github/turbo-build |
| 36 | + .github/scripts/install-php86-windows.sh |
| 37 | +) |
| 38 | +WORKFLOW_PATH=".github/workflows/phar.yml" |
| 39 | +# Workflow-level env and the three compile jobs, as JSON: comment-only edits |
| 40 | +# do not change it. |
| 41 | +COMPILE_JOBS_QUERY='[.env, .jobs["turbo-compile"], .jobs["turbo-compile-musl-arm64"], .jobs["turbo-compile-windows"]]' |
| 42 | +RUNS_LIMIT=100 |
| 43 | + |
| 44 | +origins='{}' |
| 45 | +finish() { |
| 46 | + echo "origins=$origins" >> "${GITHUB_OUTPUT:-/dev/stdout}" |
| 47 | + exit 0 |
| 48 | +} |
| 49 | + |
| 50 | +if [ -z "${BRANCH:-}" ] || [ -z "${GITHUB_REPOSITORY:-}" ]; then |
| 51 | + echo "::warning::BRANCH and GITHUB_REPOSITORY must be set; compiling everything" |
| 52 | + finish |
| 53 | +fi |
| 54 | + |
| 55 | +head="$(git rev-parse HEAD)" || finish |
| 56 | +if ! head_jobs="$(git show "$head:$WORKFLOW_PATH" | yq -o=json "$COMPILE_JOBS_QUERY")"; then |
| 57 | + echo "::warning::could not read the compile jobs of $WORKFLOW_PATH; compiling everything" |
| 58 | + finish |
| 59 | +fi |
| 60 | + |
| 61 | +# The branch filter alone: combined with event=push the endpoint returns a |
| 62 | +# stale subset of the runs (observed 2026-09-25: 21 runs, the newest a week |
| 63 | +# old). The event is checked below instead. |
| 64 | +if ! runs="$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/phar.yml/runs?branch=$BRANCH&per_page=$RUNS_LIMIT" \ |
| 65 | + | jq -r --arg repo "$GITHUB_REPOSITORY" --arg branch "$BRANCH" --arg path "$WORKFLOW_PATH" ' |
| 66 | + .workflow_runs[] |
| 67 | + | select(.event == "push" |
| 68 | + and .head_branch == $branch |
| 69 | + and .path == $path |
| 70 | + and .repository.full_name == $repo |
| 71 | + and .head_repository.full_name == $repo) |
| 72 | + | "\(.id) \(.head_sha)"')"; then |
| 73 | + echo "::warning::could not list the runs on $BRANCH; compiling everything" |
| 74 | + finish |
| 75 | +fi |
| 76 | + |
| 77 | +while read -r run_id sha; do |
| 78 | + [ -n "$run_id" ] || continue |
| 79 | + if ! git cat-file -e "$sha^{commit}" 2> /dev/null; then |
| 80 | + echo "run $run_id: $sha is not in the checkout" |
| 81 | + continue |
| 82 | + fi |
| 83 | + if ! git merge-base --is-ancestor "$sha" "$head"; then |
| 84 | + echo "run $run_id: $sha is not an ancestor of HEAD" |
| 85 | + continue |
| 86 | + fi |
| 87 | + if ! git diff --quiet "$sha" "$head" -- "${BUILD_INPUT_PATHS[@]}"; then |
| 88 | + echo "run $run_id: build inputs differ at $sha" |
| 89 | + continue |
| 90 | + fi |
| 91 | + if ! run_jobs="$(git show "$sha:$WORKFLOW_PATH" | yq -o=json "$COMPILE_JOBS_QUERY")" || [ "$run_jobs" != "$head_jobs" ]; then |
| 92 | + echo "run $run_id: compile jobs differ at $sha" |
| 93 | + continue |
| 94 | + fi |
| 95 | + if ! artifacts="$(gh api --paginate "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/artifacts?per_page=100" \ |
| 96 | + --jq '.artifacts[] | select(.expired | not) | .name')"; then |
| 97 | + echo "::warning::could not list the artifacts of run $run_id; stopping the search" |
| 98 | + break |
| 99 | + fi |
| 100 | + if ! origins="$(jq -c --argjson run "$run_id" --arg artifacts "$artifacts" ' |
| 101 | + ($artifacts | split("\n")) as $names |
| 102 | + | reduce ($names[] | select(startswith("phpstan_turbo-"))) as $name (.; |
| 103 | + if has($name) or ($names | any(. == "turbo-reused-" + $name)) then . else .[$name] = $run end) |
| 104 | + ' <<< "$origins")"; then |
| 105 | + origins='{}' |
| 106 | + finish |
| 107 | + fi |
| 108 | + echo "run $run_id: identical build inputs at $sha" |
| 109 | +done <<< "$runs" |
| 110 | + |
| 111 | +jq -r 'to_entries[] | "\(.key) <- run \(.value)"' <<< "$origins" |
| 112 | +finish |
0 commit comments