test(mobile): run the generated apps on a simulator and an emulator #778
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: Binary Size | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: binary-size-${{ github.head_ref || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| # Maximum allowed binary size in bytes (16MB = 16777216). | |
| # | |
| # Baseline: the CLI measured ~1.9MB until Aug 2026, when 8556d46 made | |
| # craft's first-party zig-js runtime part of the default build. That is a | |
| # deliberate subsystem, not bloat — see the js-runtime option in | |
| # packages/zig/build.zig (~+6.5MB in ReleaseFast; the ReleaseSafe binary | |
| # we ship and measure here lands at ~13.9MB, already stripped, with no | |
| # unwind tables or error traces). The gate sits ~15% above that baseline | |
| # so it still catches accidental growth; opt-out `-Djs-runtime=false` | |
| # builds remain around 2MB. | |
| MAX_BINARY_SIZE: 16777216 | |
| # Warning threshold (14.5MB = 15204352), just above the ~13.9MB baseline | |
| # so creep surfaces as a warning well before the hard limit. | |
| WARN_BINARY_SIZE: 15204352 | |
| defaults: | |
| run: | |
| shell: bash | |
| jobs: | |
| track: | |
| runs-on: ubuntu-latest | |
| # The last step posts the size report as a pull request comment, and the | |
| # default token here is read-only, so it failed with "Resource not | |
| # accessible by integration" — after the build and the measuring had all | |
| # succeeded. Requested explicitly rather than left to the repository | |
| # default, which is what made this invisible. | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Zig | |
| uses: pantry-pm/pantry/packages/action@235036fa0f48bae99b2293df5a3dc35c809b1777 # pinned: last SHA whose bundled typescript resolves on linux-x64 | |
| - name: Install Linux dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev | |
| - name: Cache Zig artifacts | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cache/zig | |
| packages/zig/zig-cache | |
| key: size-${{ runner.os }}-zig-master-${{ hashFiles('packages/zig/build.zig') }} | |
| restore-keys: | | |
| size-${{ runner.os }}-zig-master- | |
| - name: First-party Zig dependencies | |
| uses: ./.github/actions/first-party-zig-deps | |
| - name: Build release binary | |
| working-directory: packages/zig | |
| run: | | |
| zig build -Doptimize=ReleaseSafe | |
| - name: Measure binary size | |
| id: size | |
| working-directory: packages/zig | |
| run: | | |
| BINARY_PATH="zig-out/bin/craft" | |
| if [ -f "$BINARY_PATH" ]; then | |
| SIZE=$(wc -c < "$BINARY_PATH" | tr -d ' ') | |
| SIZE_KB=$((SIZE / 1024)) | |
| SIZE_MB=$(echo "scale=2; $SIZE / 1048576" | bc) | |
| echo "size=$SIZE" >> $GITHUB_OUTPUT | |
| echo "size_kb=$SIZE_KB" >> $GITHUB_OUTPUT | |
| echo "size_mb=$SIZE_MB" >> $GITHUB_OUTPUT | |
| echo "Binary size: ${SIZE_KB}KB (${SIZE_MB}MB)" | |
| else | |
| echo "Binary not found at $BINARY_PATH" | |
| exit 1 | |
| fi | |
| - name: Check size limits | |
| run: | | |
| SIZE=${{ steps.size.outputs.size }} | |
| SIZE_MB=${{ steps.size.outputs.size_mb }} | |
| if [ "$SIZE" -gt "$MAX_BINARY_SIZE" ]; then | |
| echo "::error::Binary size (${SIZE_MB}MB) exceeds maximum allowed size ($(echo "scale=2; $MAX_BINARY_SIZE / 1048576" | bc)MB)" | |
| exit 1 | |
| elif [ "$SIZE" -gt "$WARN_BINARY_SIZE" ]; then | |
| echo "::warning::Binary size (${SIZE_MB}MB) is approaching the limit" | |
| fi | |
| - name: First-party Zig dependencies | |
| uses: ./.github/actions/first-party-zig-deps | |
| - name: Get baseline size (main branch) | |
| if: github.event_name == 'pull_request' | |
| id: baseline | |
| run: | | |
| git fetch origin main | |
| # Anchored at the workspace and `|| true`-guarded on purpose: the trap | |
| # fires at the end of the step, by which point `cd packages/zig` below | |
| # has moved us, so a relative pathspec resolves against the wrong | |
| # directory and fails. Under `set -e` that failing restore became the | |
| # step's exit status, so every pull request got a red Binary Size | |
| # check for a cleanup step rather than for its binary size. | |
| trap 'git -C "$GITHUB_WORKSPACE" restore --staged --worktree --source=HEAD -- packages/zig || true' EXIT | |
| git checkout origin/main -- packages/zig | |
| cd packages/zig | |
| zig build -Doptimize=ReleaseSafe | |
| if [ -f "zig-out/bin/craft" ]; then | |
| BASELINE=$(wc -c < "zig-out/bin/craft" | tr -d ' ') | |
| echo "baseline=$BASELINE" >> $GITHUB_OUTPUT | |
| else | |
| echo "::error::Baseline binary was not generated" | |
| exit 1 | |
| fi | |
| - name: Calculate size difference | |
| if: github.event_name == 'pull_request' | |
| id: diff | |
| run: | | |
| CURRENT=${{ steps.size.outputs.size }} | |
| BASELINE=${{ steps.baseline.outputs.baseline }} | |
| if [ "$BASELINE" -gt 0 ]; then | |
| DIFF=$((CURRENT - BASELINE)) | |
| DIFF_KB=$((DIFF / 1024)) | |
| PERCENT=$(echo "scale=2; ($DIFF * 100) / $BASELINE" | bc) | |
| echo "diff=$DIFF" >> $GITHUB_OUTPUT | |
| echo "diff_kb=$DIFF_KB" >> $GITHUB_OUTPUT | |
| echo "percent=$PERCENT" >> $GITHUB_OUTPUT | |
| if [ "$DIFF" -gt 0 ]; then | |
| echo "direction=increased" >> $GITHUB_OUTPUT | |
| elif [ "$DIFF" -lt 0 ]; then | |
| echo "direction=decreased" >> $GITHUB_OUTPUT | |
| else | |
| echo "direction=unchanged" >> $GITHUB_OUTPUT | |
| fi | |
| fi | |
| - name: Generate size report | |
| run: | | |
| echo "## Binary Size Report" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Current Size | ${{ steps.size.outputs.size_kb }}KB (${{ steps.size.outputs.size_mb }}MB) |" >> $GITHUB_STEP_SUMMARY | |
| if [ "${{ github.event_name }}" == "pull_request" ] && [ "${{ steps.baseline.outputs.baseline }}" -gt 0 ]; then | |
| BASELINE_KB=$(( ${{ steps.baseline.outputs.baseline }} / 1024 )) | |
| echo "| Baseline (main) | ${BASELINE_KB}KB |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Difference | ${{ steps.diff.outputs.diff_kb }}KB (${{ steps.diff.outputs.percent }}%) |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Status | ${{ steps.diff.outputs.direction }} |" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Limits" >> $GITHUB_STEP_SUMMARY | |
| echo "- Warning threshold: $(echo "scale=2; $WARN_BINARY_SIZE / 1048576" | bc)MB" >> $GITHUB_STEP_SUMMARY | |
| echo "- Maximum allowed: $(echo "scale=2; $MAX_BINARY_SIZE / 1048576" | bc)MB" >> $GITHUB_STEP_SUMMARY | |
| - name: Comment on PR | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const size = '${{ steps.size.outputs.size_kb }}'; | |
| const sizeMB = '${{ steps.size.outputs.size_mb }}'; | |
| const diff = '${{ steps.diff.outputs.diff_kb }}'; | |
| const percent = '${{ steps.diff.outputs.percent }}'; | |
| const direction = '${{ steps.diff.outputs.direction }}'; | |
| const warnMB = (Number(process.env.WARN_BINARY_SIZE) / 1048576).toFixed(2); | |
| const maxMB = (Number(process.env.MAX_BINARY_SIZE) / 1048576).toFixed(2); | |
| let emoji = '✅'; | |
| if (direction === 'increased' && parseInt(diff) > 10) emoji = '⚠️'; | |
| if (direction === 'decreased') emoji = '📉'; | |
| const body = `## ${emoji} Binary Size Report | |
| | Metric | Value | | |
| |--------|-------| | |
| | Current Size | ${size}KB (${sizeMB}MB) | | |
| | Change | ${diff}KB (${percent}%) ${direction} | | |
| <details> | |
| <summary>Size limits</summary> | |
| - Warning: ${warnMB}MB | |
| - Maximum: ${maxMB}MB | |
| </details>`; | |
| // Find existing comment | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const existingComment = comments.find(c => | |
| c.user.login === 'github-actions[bot]' && | |
| c.body.includes('Binary Size Report') | |
| ); | |
| if (existingComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body: body | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: body | |
| }); | |
| } | |
| - name: Restore size history | |
| if: github.ref == 'refs/heads/main' | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # The history file lives only in the uploaded artifact (never in the | |
| # repo), so pull the previous run's copy before appending — without | |
| # this each artifact holds a single entry and no history accumulates. | |
| mkdir -p .github/metrics | |
| PREV_RUN=$(gh run list --workflow "Binary Size" --branch main --status success --limit 1 --json databaseId --jq '.[0].databaseId // empty') | |
| if [ -n "$PREV_RUN" ]; then | |
| gh run download "$PREV_RUN" -n binary-size-metrics -D .github/metrics || true | |
| fi | |
| - name: Save size history | |
| if: github.ref == 'refs/heads/main' | |
| run: | | |
| mkdir -p .github/metrics | |
| DATE=$(date -u +%Y-%m-%d) | |
| COMMIT=$(git rev-parse --short HEAD) | |
| echo "{\"date\": \"$DATE\", \"commit\": \"$COMMIT\", \"size\": ${{ steps.size.outputs.size }}}" >> .github/metrics/binary-size.jsonl | |
| - name: Upload metrics | |
| if: github.ref == 'refs/heads/main' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: binary-size-metrics | |
| path: .github/metrics/ | |
| retention-days: 90 |