From 47a6ebb2fd84cf9745692a7084cd0852493336f1 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 23 Sep 2026 18:01:57 -0700 Subject: [PATCH 1/2] ci: seed test-e2e's warm DerivedData from main every 6 hours #14016 lets a test-e2e.yml build start from DerivedData that a main-contained dispatch published, so a test-only change recompiles only the tests. Only such a dispatch publishes it, and focused dispatches test pull request revisions, so none ever did: builds report "no-main-derived-data" and compile the whole app from empty DerivedData (e.g. 12.4 min of build for a 36 ms suite in run 35939043846). Dispatch test-e2e.yml on main's HEAD with one fast suite every 6 hours, skipping a SHA that already has a seed dispatch. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/e2e-derived-data-seed.yml | 54 +++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/e2e-derived-data-seed.yml diff --git a/.github/workflows/e2e-derived-data-seed.yml b/.github/workflows/e2e-derived-data-seed.yml new file mode 100644 index 000000000000..c7398fdbb5ed --- /dev/null +++ b/.github/workflows/e2e-derived-data-seed.yml @@ -0,0 +1,54 @@ +name: E2E DerivedData seed + +# test-e2e.yml builds start from the DerivedData that a main-contained dispatch +# of that workflow published (#14016), so a test-only change recompiles only +# the tests. Focused dispatches test pull request revisions, which never +# publish, and nothing else dispatched main: every build since #14016 reported +# "no-main-derived-data" and compiled the whole app from empty DerivedData. +# +# Every 6 hours this dispatches test-e2e.yml on main's HEAD with one fast suite, +# unless that SHA already has a seed dispatch. It dispatches instead of calling +# so the run keeps the identity e2e_warm_derived_data.py trusts (path +# test-e2e.yml, event workflow_dispatch, branch main). +on: + schedule: + - cron: "41 */6 * * *" + workflow_dispatch: + +permissions: {} + +jobs: + dispatch: + name: Dispatch a main build of test-e2e.yml + if: ${{ github.ref == 'refs/heads/main' }} + runs-on: ${{ vars.LINUX_RUNNER || 'blacksmith-4vcpu-ubuntu-2404' }} + timeout-minutes: 5 + concurrency: + group: e2e-derived-data-seed + cancel-in-progress: false + permissions: + actions: write + contents: read + steps: + - name: Dispatch unless main's HEAD already has a seed + env: + GH_TOKEN: ${{ github.token }} + # 36 ms on CI: the seed exists for its build, not its test. + SEED_FILTER: cmuxTests/CmuxPopoverGroupTests + run: | + set -euo pipefail + head_sha="$(gh api "repos/$GITHUB_REPOSITORY/branches/main" --jq '.commit.sha')" + # test-e2e.yml's run name carries the tested ref and the dispatch id. + existing="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow test-e2e.yml \ + --branch main --event workflow_dispatch --limit 50 --json displayTitle,conclusion \ + --jq "[.[] | select((.displayTitle | contains(\"@ $head_sha\")) and (.displayTitle | contains(\"[e2e-derived-data-seed-\")) and (.conclusion != \"failure\" and .conclusion != \"cancelled\"))] | length")" + if [ "$existing" != "0" ]; then + echo "main's HEAD $head_sha already has a seed dispatch." >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + gh workflow run test-e2e.yml --repo "$GITHUB_REPOSITORY" --ref main \ + -f ref="$head_sha" \ + -f test_filter="$SEED_FILTER" \ + -f dispatch_id="e2e-derived-data-seed-$GITHUB_RUN_ID" \ + -f record_video=false + echo "Dispatched a DerivedData seed build of $head_sha." >> "$GITHUB_STEP_SUMMARY" From fec9d90b9934be115f8f830fc912302ef0e0e077 Mon Sep 17 00:00:00 2001 From: Leo Li Date: Wed, 23 Sep 2026 18:17:07 -0700 Subject: [PATCH 2/2] ci: make seed runs compile, and drop a half-adopted DerivedData A seed dispatch could adopt an exact product, skip the compile, and so publish nothing; seed dispatches now skip product reuse. A step timeout in "Adopt main's DerivedData" kills the restore before its own cleanup, so the compile could start on a half-extracted tree; a failed adoption now clears the DerivedData first. The seed dedupe now looks back 300 runs (50 covered about two hours) and retries only runs that never finished. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/e2e-derived-data-seed.yml | 10 ++++++---- .github/workflows/test-e2e.yml | 12 ++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-derived-data-seed.yml b/.github/workflows/e2e-derived-data-seed.yml index c7398fdbb5ed..aae0fbe8ed68 100644 --- a/.github/workflows/e2e-derived-data-seed.yml +++ b/.github/workflows/e2e-derived-data-seed.yml @@ -3,8 +3,8 @@ name: E2E DerivedData seed # test-e2e.yml builds start from the DerivedData that a main-contained dispatch # of that workflow published (#14016), so a test-only change recompiles only # the tests. Focused dispatches test pull request revisions, which never -# publish, and nothing else dispatched main: every build since #14016 reported -# "no-main-derived-data" and compiled the whole app from empty DerivedData. +# publish, and nothing dispatches main on a schedule: seeds so far came from +# hand dispatches, and each artifact expires after 3 days. # # Every 6 hours this dispatches test-e2e.yml on main's HEAD with one fast suite, # unless that SHA already has a seed dispatch. It dispatches instead of calling @@ -39,9 +39,11 @@ jobs: set -euo pipefail head_sha="$(gh api "repos/$GITHUB_REPOSITORY/branches/main" --jq '.commit.sha')" # test-e2e.yml's run name carries the tested ref and the dispatch id. + # A run that built but failed its test still published, so only runs + # that never finished count as missing. existing="$(gh run list --repo "$GITHUB_REPOSITORY" --workflow test-e2e.yml \ - --branch main --event workflow_dispatch --limit 50 --json displayTitle,conclusion \ - --jq "[.[] | select((.displayTitle | contains(\"@ $head_sha\")) and (.displayTitle | contains(\"[e2e-derived-data-seed-\")) and (.conclusion != \"failure\" and .conclusion != \"cancelled\"))] | length")" + --branch main --event workflow_dispatch --limit 300 --json displayTitle,conclusion \ + --jq "[.[] | select((.displayTitle | contains(\"@ $head_sha\")) and (.displayTitle | contains(\"[e2e-derived-data-seed-\")) and (.conclusion != \"cancelled\" and .conclusion != \"timed_out\" and .conclusion != \"startup_failure\"))] | length")" if [ "$existing" != "0" ]; then echo "main's HEAD $head_sha already has a seed dispatch." >> "$GITHUB_STEP_SUMMARY" exit 0 diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index e2f8677358ab..06284f72744e 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -304,8 +304,11 @@ jobs: id: product-key run: python3 scripts/ci/reuse_app_host_products.py key "$CMUX_DERIVED_DATA_PATH" + # e2e-derived-data-seed.yml dispatches exist to publish DerivedData, + # which only a compile produces, so they never adopt a product. - name: Reuse a compiled product instead of building one id: reuse + if: ${{ !startsWith(inputs.dispatch_id, 'e2e-derived-data-seed-') }} continue-on-error: true env: GH_TOKEN: ${{ github.token }} @@ -400,6 +403,15 @@ jobs: defaults write com.apple.dt.XCBuild IgnoreFileSystemDeviceInodeChanges -bool YES python3 scripts/ci/e2e_warm_derived_data.py restore "$GITHUB_WORKSPACE" "$CMUX_DERIVED_DATA_PATH" "$WARM_KEY" + # A step timeout kills the restore before its own cleanup runs, which + # would leave the compile a half-extracted DerivedData to start from. + - name: Discard a partly adopted DerivedData + if: ${{ steps.warm.outcome == 'failure' }} + run: | + set -euo pipefail + rm -rf -- "$CMUX_DERIVED_DATA_PATH" + mkdir -p "$CMUX_DERIVED_DATA_PATH" + - name: Record build input times id: record-inputs if: ${{ steps.reuse.outputs.hit != 'true' }}