feat(runtime): implement instant launch fast paths #6037
Workflow file for this run
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: Construct Image | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: construct-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| # Single source of truth for the digest directory — `build` writes | |
| # into it, `publish-manifest` reads from it. | |
| DIGEST_DIR: /tmp/jackin-construct-digests | |
| # Single source of truth for the registry coordinate — keeps the | |
| # two buildcache refs in sync. | |
| REGISTRY_IMAGE: projectjackin/construct | |
| # NOTE: BUILDX_BUILDER is set at the `build` job level only, not | |
| # workflow-wide. Setting it workflow-wide leaks into `publish-manifest`, | |
| # which has no buildx builder and would then fail with | |
| # `ERROR: no builder "jackin-construct" found` because docker buildx | |
| # treats BUILDX_BUILDER as the default-builder selection. | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| construct: ${{ steps.dispatch.outputs.construct || steps.filter.outputs.construct }} | |
| construct_image: ${{ steps.dispatch.outputs.construct_image || steps.filter.outputs.construct_image }} | |
| # Single source of truth for "should this run produce / publish | |
| # registry artifacts" — true on push-to-main and on | |
| # workflow_dispatch from main, false everywhere else (PRs, | |
| # dispatch from feature branches). Downstream steps gate on | |
| # `needs.changes.outputs.is_publish` instead of restating the | |
| # same boolean five times. | |
| is_publish: ${{ steps.flags.outputs.is_publish }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Force construct=true on workflow_dispatch | |
| id: dispatch | |
| if: github.event_name == 'workflow_dispatch' | |
| shell: bash | |
| run: | | |
| echo "construct=true" >> "$GITHUB_OUTPUT" | |
| echo "construct_image=true" >> "$GITHUB_OUTPUT" | |
| - name: Classify changed paths | |
| id: filter | |
| if: github.event_name != 'workflow_dispatch' | |
| uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | |
| with: | |
| filters: | | |
| # Build the construct image when build plumbing changes, even if the | |
| # change should not publish a new immutable construct tag. | |
| construct: | |
| - '.github/workflows/construct.yml' | |
| - 'docker-bake.hcl' | |
| - 'docker/construct/**' | |
| - 'crates/jackin-xtask/**' | |
| # Require an unpublished docker/construct/VERSION only when inputs | |
| # baked into the construct image changed. | |
| construct_image: | |
| - 'docker-bake.hcl' | |
| - 'docker/construct/Dockerfile' | |
| - 'docker/construct/fish-config.fish' | |
| - 'docker/construct/versions.env' | |
| - 'docker/construct/zshrc' | |
| - name: Compute publish flag | |
| id: flags | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| REF: ${{ github.ref }} | |
| run: | | |
| if [ "$EVENT_NAME" = "push" ] || { [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "$REF" = "refs/heads/main" ]; }; then | |
| echo "is_publish=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "is_publish=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| # amd64 and arm64 share every step except runner label and platform | |
| # string. Matrix collapse keeps the per-platform fail-fast=false | |
| # behavior (one arch failing must not cancel the other) while | |
| # eliminating ~60 lines of cross-job copy-paste. | |
| build: | |
| name: ${{ matrix.platform }} | |
| needs: changes | |
| if: needs.changes.outputs.construct == 'true' | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| env: | |
| # Scoped to the build job only. Setting it workflow-wide leaks | |
| # into `publish-manifest`, which has no buildx builder and would | |
| # then fail with `ERROR: no builder "jackin-construct" found`. | |
| BUILDX_BUILDER: jackin-construct | |
| # `mise run` activates the whole mise.toml tool set and, by default, | |
| # installs every missing one before the task. This job only needs the | |
| # rust toolchain mise-action already installed to compile `cargo xtask`; | |
| # without this, an unrelated tool's install (e.g. a flaky rustup | |
| # component download for cargo-zigbuild) reds the construct build. | |
| MISE_TASK_RUN_AUTO_INSTALL: "false" | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: amd64 | |
| runner: ubuntu-24.04 | |
| - platform: arm64 | |
| runner: ubuntu-24.04-arm | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Cache Rust toolchain | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: | | |
| ~/.rustup/toolchains | |
| ~/.rustup/update-hashes | |
| key: rustup-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('rust-toolchain.toml') }} | |
| restore-keys: | | |
| rustup-${{ runner.os }}-${{ runner.arch }}- | |
| - name: Cache Cargo registry | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: | | |
| ~/.cargo/registry/cache | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/src | |
| ~/.cargo/git/db | |
| key: cargo-registry-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| cargo-registry-${{ runner.os }}-${{ runner.arch }}- | |
| - id: sccache | |
| uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 | |
| continue-on-error: true | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| - name: Enable sccache | |
| if: steps.sccache.outcome == 'success' | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV" | |
| echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" | |
| - name: Setup mise | |
| uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4 | |
| with: | |
| install_args: "rust" | |
| github_token: ${{ secrets.GH_READONLY_TOKEN }} | |
| - name: Cache Cargo build artifacts | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: target | |
| key: cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-${{ matrix.platform }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }} | |
| restore-keys: | | |
| cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-${{ matrix.platform }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }}- | |
| cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-${{ matrix.platform }}-${{ hashFiles('Cargo.lock') }}- | |
| cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-${{ matrix.platform }}- | |
| - name: Install Docker Buildx | |
| uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 | |
| with: | |
| name: ${{ env.BUILDX_BUILDER }} | |
| driver: docker-container | |
| - name: Log in to Docker Hub | |
| if: needs.changes.outputs.is_publish == 'true' && needs.changes.outputs.construct_image == 'true' | |
| uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Bootstrap buildx | |
| run: mise run construct-init-buildx | |
| - name: Expose GitHub Actions cache runtime | |
| if: needs.changes.outputs.is_publish != 'true' || needs.changes.outputs.construct_image != 'true' | |
| uses: crazy-max/ghaction-github-runtime@04d248b84655b509d8c44dc1d6f990c879747487 # v4.0.0 | |
| - name: Build ${{ matrix.platform }} without push | |
| if: needs.changes.outputs.is_publish != 'true' || needs.changes.outputs.construct_image != 'true' | |
| env: | |
| # Registry buildcache (written by main) is the primary warm source — avoids a cold | |
| # GHA start when the scope is empty or evicted. GHA scope is secondary: persists | |
| # layers written by earlier runs of this PR so mid-PR iterations stay warm too. | |
| CACHE_FROM: | | |
| type=registry,ref=${{ env.REGISTRY_IMAGE }}:buildcache-${{ matrix.platform }} | |
| type=gha,scope=construct-pr-${{ matrix.platform }} | |
| CACHE_TO: type=gha,scope=construct-pr-${{ matrix.platform }},mode=max | |
| run: mise run construct-build-platform ${{ matrix.platform }} | |
| - name: Push ${{ matrix.platform }} image by digest | |
| if: needs.changes.outputs.is_publish == 'true' && needs.changes.outputs.construct_image == 'true' | |
| env: | |
| CACHE_FROM: type=registry,ref=${{ env.REGISTRY_IMAGE }}:buildcache-${{ matrix.platform }} | |
| CACHE_TO: type=registry,ref=${{ env.REGISTRY_IMAGE }}:buildcache-${{ matrix.platform }},mode=max | |
| run: mise run construct-push-platform ${{ matrix.platform }} | |
| - name: Upload ${{ matrix.platform }} digest | |
| if: needs.changes.outputs.is_publish == 'true' && needs.changes.outputs.construct_image == 'true' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: construct-digest-${{ matrix.platform }} | |
| path: ${{ env.DIGEST_DIR }}/${{ matrix.platform }}.digest | |
| if-no-files-found: error | |
| retention-days: 1 | |
| publish-manifest: | |
| name: publish manifest | |
| if: needs.changes.outputs.is_publish == 'true' && needs.changes.outputs.construct_image == 'true' | |
| needs: [changes, build] | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| env: | |
| # Keep `mise run` on the rust toolchain mise-action installed instead of | |
| # installing the whole mise.toml tool set for the rust-only xtask task. | |
| MISE_TASK_RUN_AUTO_INSTALL: "false" | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Cache Rust toolchain | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: | | |
| ~/.rustup/toolchains | |
| ~/.rustup/update-hashes | |
| key: rustup-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('rust-toolchain.toml') }} | |
| restore-keys: | | |
| rustup-${{ runner.os }}-${{ runner.arch }}- | |
| - name: Cache Cargo registry | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: | | |
| ~/.cargo/registry/cache | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/src | |
| ~/.cargo/git/db | |
| key: cargo-registry-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| cargo-registry-${{ runner.os }}-${{ runner.arch }}- | |
| - id: sccache | |
| uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 | |
| continue-on-error: true | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| - name: Enable sccache | |
| if: steps.sccache.outcome == 'success' | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV" | |
| echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" | |
| - name: Setup mise | |
| uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4 | |
| with: | |
| install_args: "rust" | |
| github_token: ${{ secrets.GH_READONLY_TOKEN }} | |
| - name: Cache Cargo build artifacts | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: target | |
| key: cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-manifest-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }} | |
| restore-keys: | | |
| cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-manifest-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }}- | |
| cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-manifest-${{ hashFiles('Cargo.lock') }}- | |
| cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-manifest- | |
| # publish-manifest only runs `docker buildx imagetools create` | |
| # and `inspect`, both registry-side ops. They use the buildx | |
| # CLI plugin (bundled with Docker on ubuntu-24.04) and need no | |
| # local builder container, so no setup-buildx-action / | |
| # construct-init-buildx here. | |
| - name: Log in to Docker Hub | |
| uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Download platform digests | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: construct-digest-* | |
| path: ${{ env.DIGEST_DIR }} | |
| merge-multiple: true | |
| - name: Publish multi-platform manifest | |
| run: mise run construct-publish-manifest | |
| # PR-time rehearsal of `publish-manifest`'s setup. Runs the same | |
| # docker buildx CLI plumbing publish-manifest depends on, without | |
| # logging in to Docker Hub or writing a manifest to the registry, | |
| # so a workflow- or job-level env-var leak that breaks | |
| # `docker buildx` resolution surfaces in PR rather than | |
| # post-merge on main. Catches the exact failure class from | |
| # jackin-project/jackin#266 — `docker buildx ls` evaluates | |
| # BUILDX_BUILDER at startup and exits non-zero on a missing-builder | |
| # reference, regardless of whether the rest of the command would | |
| # have hit the network. The rehearsal runs on the events | |
| # publish-manifest itself skips: PR and feature-branch dispatch | |
| # that touch construct paths. | |
| publish-manifest-rehearsal: | |
| name: publish manifest (rehearsal) | |
| if: needs.changes.outputs.construct == 'true' && needs.changes.outputs.is_publish != 'true' | |
| needs: [changes, build] | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: read | |
| env: | |
| # Keep `mise run` on the rust toolchain mise-action installed instead of | |
| # installing the whole mise.toml tool set for the rust-only xtask task. | |
| MISE_TASK_RUN_AUTO_INSTALL: "false" | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Cache Rust toolchain | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: | | |
| ~/.rustup/toolchains | |
| ~/.rustup/update-hashes | |
| key: rustup-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('rust-toolchain.toml') }} | |
| restore-keys: | | |
| rustup-${{ runner.os }}-${{ runner.arch }}- | |
| - name: Cache Cargo registry | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: | | |
| ~/.cargo/registry/cache | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/src | |
| ~/.cargo/git/db | |
| key: cargo-registry-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| cargo-registry-${{ runner.os }}-${{ runner.arch }}- | |
| - id: sccache | |
| uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 | |
| continue-on-error: true | |
| env: | |
| SCCACHE_GHA_ENABLED: "true" | |
| - name: Enable sccache | |
| if: steps.sccache.outcome == 'success' | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> "$GITHUB_ENV" | |
| echo "RUSTC_WRAPPER=sccache" >> "$GITHUB_ENV" | |
| - name: Setup mise | |
| uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4 | |
| with: | |
| install_args: "rust" | |
| github_token: ${{ secrets.GH_READONLY_TOKEN }} | |
| - name: Cache Cargo build artifacts | |
| uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 | |
| with: | |
| path: target | |
| key: cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-manifest-rehearsal-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }} | |
| restore-keys: | | |
| cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-manifest-rehearsal-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }}- | |
| cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-manifest-rehearsal-${{ hashFiles('Cargo.lock') }}- | |
| cargo-target-${{ runner.os }}-${{ runner.arch }}-construct-manifest-rehearsal- | |
| - name: Verify docker buildx CLI resolves under workflow env | |
| # `docker buildx ls` reads BUILDX_BUILDER (and the rest of | |
| # buildx's env-driven config) and fails fast on a missing | |
| # builder reference. This is the exact error shape from | |
| # jackin-project/jackin#266 — running it on PR + feature-branch | |
| # dispatch surfaces a leak that publish-manifest would | |
| # otherwise only hit post-merge on main. | |
| run: docker buildx ls | |
| - name: Verify imagetools CLI plugin loads | |
| # Exercises the same plugin path publish-manifest's | |
| # `docker buildx imagetools create` / `inspect` commands rely | |
| # on. Cheap, network-free smoke that the plugin is bundled | |
| # and loadable on the runner. | |
| run: docker buildx imagetools --help >/dev/null | |
| - name: Verify construct VERSION is not already published | |
| if: needs.changes.outputs.construct_image == 'true' | |
| # PR-time mirror of publish-manifest's version-bump guard. The | |
| # build job already ran, so reaching here means docker/construct/** | |
| # content changed; an anonymous public-registry inspect needs no | |
| # Docker Hub login, so the "changed the image but didn't bump | |
| # docker/construct/VERSION" failure surfaces on the PR instead of | |
| # on main, where publish-manifest is the first job to evaluate it. | |
| run: mise run construct-assert-version-unpublished | |
| construct-required: | |
| if: always() | |
| needs: [changes, build, publish-manifest, publish-manifest-rehearsal] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - uses: ./.github/actions/aggregate-needs | |
| with: | |
| needs-json: ${{ toJSON(needs) }} | |
| workflow-label: construct |