ci: auto-run the hardware-lab release-QA on each firmware PR #4975
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: Build Firmware | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| changes-firmware: | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| pull-requests: read | |
| outputs: | |
| should-build: ${{ github.event_name == 'push' || steps.filter.outputs.src == 'true' }} | |
| steps: | |
| - uses: dorny/paths-filter@v4 | |
| if: github.event_name == 'pull_request' | |
| id: filter | |
| with: | |
| filters: | | |
| src: | |
| - '.github/actions/**' | |
| - '.github/workflows/build-firmware.yml' | |
| - 'boards/**' | |
| - 'platform/**' | |
| - 'resources/**' | |
| - 'sdk/**' | |
| - 'src/**' | |
| - 'stored_apps/**' | |
| - 'tools/**' | |
| - 'third_party/**' | |
| - 'tools/waf/**' | |
| - 'waf' | |
| - 'wscript' | |
| build-firmware: | |
| needs: changes-firmware | |
| if: needs.changes-firmware.outputs.should-build == 'true' | |
| runs-on: ubuntu-24.04 | |
| container: | |
| image: ghcr.io/coredevices/pebbleos-docker:v6 | |
| strategy: | |
| matrix: | |
| board: | |
| - asterix | |
| - obelix@dvt | |
| - obelix@pvt | |
| - getafix@evt | |
| - getafix@dvt | |
| - getafix@dvt2 | |
| steps: | |
| - name: Mark Github workspace as safe | |
| run: git config --system --add safe.directory "${GITHUB_WORKSPACE}" | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| - name: Rebase onto target branch | |
| uses: ./.github/actions/rebase | |
| with: | |
| submodules: true | |
| - name: Install Python dependencies | |
| run: | | |
| pip install -U pip | |
| pip install -r requirements.txt | |
| - name: Get npm cache directory | |
| id: npm-cache-dir | |
| shell: bash | |
| run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT} | |
| - name: Set artifact board name | |
| id: artifact_board | |
| run: echo "NAME=$(printf '%s' "$BOARD" | tr @ _)" >> "$GITHUB_OUTPUT" | |
| env: | |
| BOARD: ${{ matrix.board }} | |
| - name: Configure | |
| run: ./waf configure --board '${{ matrix.board }}' | |
| - name: Build | |
| run: ./waf build | |
| - name: Bundle | |
| run: ./waf bundle | |
| - name: Store | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: firmware-${{ steps.artifact_board.outputs.NAME }} | |
| path: | | |
| build/**/*.elf | |
| build/**/*.pbz | |
| build/pebbleos_loghash_dict.json | |
| - name: Get Build ID | |
| id: build_id | |
| run: | | |
| echo "BUILD_ID=$(readelf -n build/pebbleos.elf | sed -n -e 's/^.*Build ID: //p')" >> "$GITHUB_OUTPUT" | |
| - name: Upload log hash dictionary | |
| if: ${{ github.event_name == 'push' && github.repository == 'coredevices/PebbleOS' }} | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.LOG_HASH_BUCKET_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.LOG_HASH_BUCKET_SECRET }} | |
| AWS_DEFAULT_REGION: us-east-1 | |
| run: | | |
| pip install awscli | |
| aws s3 cp build/pebbleos_loghash_dict.json \ | |
| "s3://${{ vars.LOG_HASH_BUCKET_NAME }}/${{ steps.build_id.outputs.BUILD_ID }}-${{ github.sha }}-normal.json" \ | |
| --endpoint-url "${{ vars.LOG_HASH_BUCKET_ENDPOINT }}" | |
| # Dual-slot RELEASE PBZ for the hardware-lab release-QA boards. The build-firmware job above is | |
| # single-slot AND a debug build, so it cannot be the "to" of an upgrade test: OTA writes into the | |
| # watch's *inactive* slot (needs both slots), and the watch refuses to apply a debug PBZ over the | |
| # release "from". The release-QA dispatch (coredevices/unicorn) needs a merged dual-slot RELEASE | |
| # PBZ, the same shape as a release asset — so build both slots (release) and merge, in one job per | |
| # board, no cross-job artifact passing. Only the two QA-hardware boards; kept out of | |
| # build-firmware-status so a QA-artifact hiccup never blocks a firmware PR. | |
| build-qa-firmware: | |
| needs: changes-firmware | |
| if: needs.changes-firmware.outputs.should-build == 'true' | |
| runs-on: ubuntu-24.04 | |
| container: | |
| image: ghcr.io/coredevices/pebbleos-docker:v6 | |
| strategy: | |
| matrix: | |
| board: | |
| - obelix@pvt | |
| - getafix@dvt2 | |
| steps: | |
| - name: Mark Github workspace as safe | |
| run: git config --system --add safe.directory "${GITHUB_WORKSPACE}" | |
| - name: Checkout | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| - name: Rebase onto target branch | |
| uses: ./.github/actions/rebase | |
| with: | |
| submodules: true | |
| - name: Install Python dependencies | |
| run: | | |
| pip install -U pip | |
| pip install -r requirements.txt | |
| - name: Set artifact board name | |
| id: artifact_board | |
| run: echo "NAME=$(printf '%s' "$BOARD" | tr @ _)" >> "$GITHUB_OUTPUT" | |
| env: | |
| BOARD: ${{ matrix.board }} | |
| - name: Build both release slots and merge into a dual-slot PBZ | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| name='${{ steps.artifact_board.outputs.NAME }}' | |
| mkdir -p out | |
| for slot in 0 1; do | |
| ./waf configure --board '${{ matrix.board }}' -DCONFIG_FIRMWARE_SLOT=$slot -DCONFIG_RELEASE=y | |
| ./waf build | |
| ./waf bundle | |
| cp build/normal_*_slot${slot}.pbz . | |
| done | |
| s0=$(ls normal_*_slot0.pbz | head -1) | |
| s1=$(ls normal_*_slot1.pbz | head -1) | |
| # Version string is embedded in the bundle name (git describe), e.g. | |
| # normal_obelix_pvt_v4.35.0-2-gdeadbee_slot0.pbz -> v4.35.0-2-gdeadbee. | |
| ver=$(basename "$s0" | sed -E "s/^normal_${name}_//; s/_slot0\.pbz$//") | |
| python3 tools/merge_pbz.py --slot0-pbz "$s0" --slot1-pbz "$s1" \ | |
| --output "out/normal_${name}_${ver}.pbz" | |
| # Ship the loghash dict alongside so the QA fwlog step can dehash release logs. | |
| cp build/pebbleos_loghash_dict.json out/ 2>/dev/null || true | |
| ls -la out | |
| - name: Store merged QA firmware | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: qa-firmware-${{ steps.artifact_board.outputs.NAME }} | |
| path: out/** | |
| # Once the dual-slot QA firmware is built, kick off the hardware-lab release-QA in | |
| # coredevices/unicorn against THIS run's qa-firmware artifact (erase -> PRF -> pair -> OTA -> | |
| # functional pass on a real phone + watch), using the most recently tagged coreapp release. The | |
| # unicorn run posts a `unicorn/release-qa` commit status back on this PR's commit. Internal PRs | |
| # only (forks have no secrets); needs UNICORN_DISPATCH_TOKEN — a PAT with actions:write on unicorn. | |
| dispatch-release-qa: | |
| needs: build-qa-firmware | |
| # Org members / collaborators only: author_association is MEMBER for org members, OWNER for the | |
| # repo owner, COLLABORATOR for added collaborators — external contributors (CONTRIBUTOR/NONE) are | |
| # excluded, so an outside PR can't spend the hardware lab. | |
| if: >- | |
| github.event_name == 'pull_request' && | |
| needs.build-qa-firmware.result == 'success' && | |
| github.event.pull_request.head.repo.full_name == github.repository && | |
| contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association) | |
| runs-on: ubuntu-24.04 | |
| # One in-flight QA per PR: a newer push supersedes the older dispatch instead of queueing extras. | |
| concurrency: | |
| group: release-qa-dispatch-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| steps: | |
| - name: Dispatch the hardware-lab release-QA (coredevices/unicorn) | |
| env: | |
| GH_TOKEN: ${{ secrets.UNICORN_DISPATCH_TOKEN }} | |
| run: | | |
| if [ -z "${GH_TOKEN:-}" ]; then | |
| echo "::warning::UNICORN_DISPATCH_TOKEN not set — add a PAT with actions:write on coredevices/unicorn to run the hardware release-QA automatically" | |
| exit 0 | |
| fi | |
| # to_pbz_run_id = this Build Firmware run (its qa-firmware-<board> artifact is the "to"). | |
| # coreapp_run_id omitted -> unicorn uses the most recently tagged coreapp release. | |
| gh workflow run one-off-pebbleos-test.yml -R coredevices/unicorn \ | |
| -f to_pbz_run_id=${{ github.run_id }} | |
| build-firmware-status: | |
| needs: [changes-firmware, build-firmware] | |
| if: always() | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - if: needs.build-firmware.result == 'failure' || needs.build-firmware.result == 'cancelled' | |
| run: exit 1 |