Skip to content

Refactor the CI system #15

Refactor the CI system

Refactor the CI system #15

Workflow file for this run

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