Refactor the CI system #15
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: CI | |
| on: | |
| push: | |
| branches: ["main"] | |
| pull_request: | |
| branches: ["main"] | |
| workflow_dispatch: | |
| concurrency: | |
| cancel-in-progress: true | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| format: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - run: .github/ci/format.sh | |
| check: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-check | |
| - run: .github/ci/check.sh | |
| test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-test | |
| - run: .github/ci/test.sh | |
| # Discover buildable examples for the matrix | |
| discover: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| stable: ${{ steps.discover.outputs.stable }} | |
| esp: ${{ steps.discover.outputs.esp }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - id: discover | |
| run: .github/ci/discover.sh | |
| # Build each stable-toolchain example in parallel. | |
| # Entries with bloat==true also compare binary sizes against the base branch. | |
| build: | |
| needs: discover | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| # Leave runner slots for format/check/test/build-esp. | |
| max-parallel: 15 | |
| matrix: | |
| include: ${{ fromJson(needs.discover.outputs.stable) }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| # Bloat entries need full history for git worktree to base revision. | |
| # Shallow clone is fast for this repo, so always fetch full history. | |
| fetch-depth: 0 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-build-${{ matrix.target }} | |
| - name: Install target and tools | |
| run: | | |
| rustup target add ${{ matrix.target }} | |
| cargo install flip-link 2>/dev/null || true | |
| - name: Build | |
| working-directory: ${{ matrix.dir }} | |
| run: cargo build --release | |
| # ── Bloat: size + bloaty diff HEAD vs BASE, upload data ── | |
| - name: Install bloat tools | |
| if: github.event_name == 'pull_request' && matrix.bloat | |
| run: | | |
| rustup component add llvm-tools | |
| cargo install cargo-binutils 2>/dev/null || true | |
| # bloaty isn't in apt; extract the pre-built binary from the Docker image | |
| # used by carlosperate/bloaty-action. | |
| id=$(docker create ghcr.io/carlosperate/bloaty-action:latest) | |
| sudo docker cp "$id:/usr/local/bin/bloaty" /usr/local/bin/bloaty | |
| docker rm "$id" >/dev/null | |
| - name: Measure sizes and compare with base | |
| if: github.event_name == 'pull_request' && matrix.bloat | |
| working-directory: ${{ matrix.dir }} | |
| env: | |
| BLOAT_BINS: ${{ matrix.bloat_bins }} | |
| run: | | |
| set -euo pipefail | |
| # Determine which binaries to size. | |
| if [[ -n "$BLOAT_BINS" ]]; then | |
| IFS=',' read -ra bins <<< "$BLOAT_BINS" | |
| else | |
| bins=(default) | |
| fi | |
| # ── Build BASE ── | |
| base_sha=$(git -C "$GITHUB_WORKSPACE" merge-base HEAD origin/main) | |
| worktree=$(mktemp -d) | |
| git -C "$GITHUB_WORKSPACE" worktree add --detach "$worktree" "$base_sha" | |
| trap 'git -C "$GITHUB_WORKSPACE" worktree remove --force "$worktree" 2>/dev/null; rm -rf "$worktree"' EXIT | |
| base_dir="$worktree/${{ matrix.dir }}" | |
| if [[ -d "$base_dir" ]]; then | |
| (cd "$base_dir" && cargo build --release --target ${{ matrix.target }}) | |
| fi | |
| # ── Output directory ── | |
| safe_name=$(echo "${{ matrix.dir }}" | tr '/' '_') | |
| outdir="$GITHUB_WORKSPACE/.bloat-data/$safe_name" | |
| mkdir -p "$outdir" | |
| for bin in "${bins[@]}"; do | |
| bin_flag="" | |
| bin_label="" | |
| bin_suffix="" | |
| if [[ "$bin" != "default" ]]; then | |
| bin_flag="--bin $bin" | |
| bin_label="$bin" | |
| bin_suffix="-$bin" | |
| fi | |
| # cargo size for HEAD | |
| cargo size --release $bin_flag 2>/dev/null > "$outdir/size-head${bin_suffix}.txt" | |
| head_size=$(awk 'NR==2 {print $4}' "$outdir/size-head${bin_suffix}.txt") | |
| # cargo size + bloaty for BASE | |
| if [[ -d "$base_dir" ]]; then | |
| (cd "$base_dir" && cargo size --release $bin_flag 2>/dev/null) > "$outdir/size-base${bin_suffix}.txt" | |
| base_size=$(awk 'NR==2 {print $4}' "$outdir/size-base${bin_suffix}.txt") | |
| # Find ELF paths for bloaty | |
| head_target_dir="target/${{ matrix.target }}/release" | |
| base_target_dir="$base_dir/target/${{ matrix.target }}/release" | |
| if [[ -n "$bin_label" ]]; then | |
| head_elf="$head_target_dir/$bin_label" | |
| base_elf="$base_target_dir/$bin_label" | |
| else | |
| # Single-binary: find the ELF by package name | |
| head_elf=$(find "$head_target_dir" -maxdepth 1 -type f -executable ! -name "*.d" ! -name "*.rlib" ! -name "build-script-*" | head -1) | |
| base_elf=$(find "$base_target_dir" -maxdepth 1 -type f -executable ! -name "*.d" ! -name "*.rlib" ! -name "build-script-*" | head -1) | |
| fi | |
| if [[ -f "$head_elf" && -f "$base_elf" ]]; then | |
| bloaty "$head_elf" -- "$base_elf" > "$outdir/bloaty${bin_suffix}.txt" 2>&1 || true | |
| fi | |
| else | |
| base_size=0 | |
| echo "(base not available)" > "$outdir/size-base${bin_suffix}.txt" | |
| fi | |
| echo "${{ matrix.dir }}|${bin_label}|${base_size}|${head_size}" >> "$outdir/size-data.txt" | |
| done | |
| echo "Size data:" | |
| cat "$outdir/size-data.txt" | |
| - name: Upload bloat data | |
| if: github.event_name == 'pull_request' && matrix.bloat | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: bloat-${{ hashFiles('.bloat-data/*/size-data.txt') }} | |
| path: .bloat-data/ | |
| retention-days: 1 | |
| # Assemble bloat report from matrix artifacts and post as PR comment | |
| bloat-report: | |
| if: github.event_name == 'pull_request' | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| path: .bloat-artifacts | |
| pattern: bloat-* | |
| merge-multiple: true | |
| - name: Generate report | |
| id: report | |
| run: | | |
| report=$(.github/ci/bloat-report.sh .bloat-artifacts) | |
| # Save for the comment step (handle multi-line) | |
| echo "REPORT<<EOF" >> "$GITHUB_ENV" | |
| echo "$report" >> "$GITHUB_ENV" | |
| echo "EOF" >> "$GITHUB_ENV" | |
| - name: Post PR comment | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const marker = '## Size Report'; | |
| const body = process.env.REPORT; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| }); | |
| const existing = comments.find(c => c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| comment_id: existing.id, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body, | |
| }); | |
| } | |
| # Build ESP/xtensa examples (separate toolchain) | |
| build-esp: | |
| needs: discover | |
| if: fromJson(needs.discover.outputs.esp)[0] != null | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-build-esp | |
| - run: .github/ci/build.sh |