mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2026-11-13 10:27:37 +00:00
186 lines
8.3 KiB
YAML
186 lines
8.3 KiB
YAML
# Build & analyze: build the firmware on each PR, on pushes to dev, and on-demand
|
|
# ("Run workflow"); publish it as a downloadable artifact, and on PRs post a unified
|
|
# report comment (build status + flash budget + public-API changes). Push/dispatch
|
|
# runs (no PR) put the same report — minus the PR-only public-API section — in the
|
|
# Actions job summary only.
|
|
#
|
|
# Scope (deliberately minimal):
|
|
# * Builds the f7 "clean" flavor only (firmware + in-tree apps). The default/extra
|
|
# app packs are PREBUILT binaries from xMasterX/all-the-plugins, NOT compiled
|
|
# from the PR, so bundling them validates nothing here.
|
|
# * Flash budget comes from the firmware ELF's `.free_flash` section MINUS the
|
|
# BLE coprocessor radio stack + FUS region that sits at the top of flash (the
|
|
# linker counts it as free, but it isn't usable). See fw_size_report.py.
|
|
# * The clean build also gates API drift: fbt fails if the exported API surface
|
|
# changes without targets/*/api_symbols.csv being updated + version-bumped.
|
|
#
|
|
# Artifacts/reports are named by branch + short SHA, so manual runs from dev work too.
|
|
# The PR report comment is posted by a separate workflow (pr-comment.yml) triggered on
|
|
# `workflow_run`: this job only builds and uploads the report as an artifact, so it needs
|
|
# no write access and works for fork PRs (whose token here is read-only). The same report
|
|
# is always written to the Actions job summary regardless.
|
|
|
|
name: Build & analyze
|
|
|
|
on:
|
|
push:
|
|
branches: [dev] # dev builds (active once this workflow is merged to dev)
|
|
pull_request:
|
|
workflow_dispatch: # manual "Run workflow" button (appears once this is on the default branch)
|
|
|
|
# Cancel an in-flight run when superseded: per PR for PRs, else per ref (pushes to dev
|
|
# and manual dispatches).
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read # commenting is delegated to pr-comment.yml (workflow_run); no write needed here
|
|
|
|
env:
|
|
FBT_NO_SYNC: 0
|
|
FORCE_NO_DIRTY: "yes"
|
|
FBT_GIT_SUBMODULE_SHALLOW: 1
|
|
WORKFLOW_BRANCH_OR_TAG: ${{ github.head_ref || github.ref_name }}
|
|
|
|
jobs:
|
|
build:
|
|
name: f7 firmware
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- name: Checkout (PR merge ref)
|
|
uses: actions/checkout@v6
|
|
with:
|
|
submodules: recursive
|
|
clean: "true"
|
|
|
|
- name: Compute version vars
|
|
id: vars
|
|
shell: bash
|
|
env:
|
|
HEAD_REF: ${{ github.head_ref }}
|
|
REF_NAME: ${{ github.ref_name }}
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
SHA="${{ github.event.pull_request.head.sha }}"
|
|
REF="$HEAD_REF"
|
|
else
|
|
SHA="${{ github.sha }}"
|
|
REF="$REF_NAME"
|
|
fi
|
|
SHORT="${SHA:0:8}"
|
|
# Branch names may contain '$', '(', ')' and backticks, and these outputs are
|
|
# consumed by later steps, so reduce to a charset that is inert everywhere
|
|
# rather than only replacing '/' for artifact names.
|
|
SAFE_REF="$(printf '%s' "$REF" | tr -c 'A-Za-z0-9._-' '-')"
|
|
echo "sha8=$SHORT" >> "$GITHUB_OUTPUT"
|
|
echo "ref=$SAFE_REF" >> "$GITHUB_OUTPUT"
|
|
echo "dist=${SAFE_REF}-${SHORT}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Build firmware (minimal / clean)
|
|
id: build
|
|
shell: bash
|
|
env:
|
|
DIST_SUFFIX: ${{ steps.vars.outputs.sha8 }}
|
|
run: |
|
|
find applications/main/extra_resources/resources/apps/ -mindepth 1 -maxdepth 1 ! -name Scripts -exec rm -rf {} + || true
|
|
./fbt COMPACT=1 DEBUG=0 FBT_NO_SYNC="${FBT_NO_SYNC}" updater_package
|
|
|
|
- name: Upload firmware artifact
|
|
if: success()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: unleashed-fw-${{ steps.vars.outputs.dist }}
|
|
path: dist/f7-C/*
|
|
retention-days: 14
|
|
if-no-files-found: error
|
|
|
|
# ---------- Flash budget: DFU size + usable free flash ----------
|
|
# "Usable free" subtracts the BLE coprocessor (core2) radio stack + FUS region
|
|
# that sits at the top of flash — the linker's .free_flash counts it as free but
|
|
# it is not usable by our firmware. Resolve the configured stack from fbt_options.
|
|
- name: Measure firmware size
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
SIZE_BIN="$(find toolchain -type f -path '*/bin/arm-none-eabi-size' 2>/dev/null | head -1)"
|
|
[ -z "$SIZE_BIN" ] && SIZE_BIN="$(command -v arm-none-eabi-size || true)"
|
|
ELF="$(find build -name firmware.elf -type f 2>/dev/null | head -1)"
|
|
DFU="$(find build -name firmware.dfu -type f 2>/dev/null | head -1)"
|
|
COPRO_BIN="$(python -c 'import fbt_options as o; print(f"{o.COPRO_STACK_BIN_DIR}/{o.COPRO_STACK_BIN}")' 2>/dev/null || true)"
|
|
COPRO_LABEL="$(python -c 'import fbt_options as o; print(o.COPRO_STACK_TYPE)' 2>/dev/null || true)"
|
|
[ -f "$COPRO_BIN" ] || COPRO_BIN=""
|
|
echo "size-bin=$SIZE_BIN | elf=$ELF | dfu=$DFU | copro=$COPRO_BIN ($COPRO_LABEL)"
|
|
python .github/scripts/fw_size_report.py \
|
|
--size-bin "$SIZE_BIN" --elf "$ELF" --dfu "$DFU" \
|
|
--copro-bin "$COPRO_BIN" --copro-label "$COPRO_LABEL" \
|
|
--out size_report.md || true
|
|
[ -f size_report.md ] || echo "_Size report unavailable._" > size_report.md
|
|
|
|
# ---------- Public API change report (PRs only) ----------
|
|
- name: Fetch PR base for API diff
|
|
if: always() && github.event_name == 'pull_request'
|
|
shell: bash
|
|
run: git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.sha }}"
|
|
|
|
- name: Generate API change report
|
|
if: always() && github.event_name == 'pull_request'
|
|
shell: bash
|
|
run: |
|
|
python .github/scripts/api_symbols_report.py \
|
|
--base-ref "${{ github.event.pull_request.base.sha }}" \
|
|
--out api_report.md
|
|
|
|
# ---------- Assemble + publish the unified PR report ----------
|
|
- name: Assemble PR report
|
|
if: always()
|
|
shell: bash
|
|
env:
|
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
# Derived from the branch name, so bind rather than interpolate (see #1052).
|
|
REF: ${{ steps.vars.outputs.ref }}
|
|
SHA8: ${{ steps.vars.outputs.sha8 }}
|
|
DIST: ${{ steps.vars.outputs.dist }}
|
|
BUILD_OUTCOME: ${{ steps.build.outcome }}
|
|
run: |
|
|
{
|
|
echo "<!-- unleashed-pr-report -->"
|
|
echo "## 🤖 Build & analyze — \`$REF\` @ \`$SHA8\`"
|
|
echo ""
|
|
if [ "$BUILD_OUTCOME" = "success" ]; then
|
|
echo "✅ **Firmware built** — artifact \`unleashed-fw-$DIST\` ([run]($RUN_URL))"
|
|
else
|
|
echo "❌ **Firmware build failed** — see the [run log]($RUN_URL)"
|
|
fi
|
|
echo ""
|
|
cat size_report.md 2>/dev/null || true
|
|
echo ""
|
|
cat api_report.md 2>/dev/null || true
|
|
} > pr_report.md
|
|
cat pr_report.md >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
# ---------- Hand the report off to the privileged commenter ----------
|
|
# This job's token is read-only on fork PRs, so it can't comment itself. It uploads
|
|
# the assembled report + PR number; pr-comment.yml (triggered on workflow_run) posts
|
|
# it with a read-write token from the base-repo context. The PR number must travel
|
|
# via the artifact — github.event.workflow_run.pull_requests is empty for fork PRs.
|
|
- name: Save PR number
|
|
if: always() && github.event_name == 'pull_request'
|
|
run: echo "${{ github.event.pull_request.number }}" > pr_number.txt
|
|
|
|
# continue-on-error: a report/upload hiccup must not redden an otherwise-green build
|
|
# (the whole point of moving commenting out of this job). if-no-files-found still
|
|
# records the failure on the step; pr-comment.yml warns if the artifact never arrives.
|
|
- name: Upload PR report artifact
|
|
if: always() && github.event_name == 'pull_request'
|
|
continue-on-error: true
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: pr-report
|
|
path: |
|
|
pr_report.md
|
|
pr_number.txt
|
|
retention-days: 1
|
|
if-no-files-found: error
|