mirror of
https://github.com/MeshEnvy/mesh-forge.git
synced 2026-05-15 22:05:50 +02:00
292 lines
12 KiB
YAML
292 lines
12 KiB
YAML
name: Repo PlatformIO Build
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
owner:
|
|
required: true
|
|
type: string
|
|
repo:
|
|
required: true
|
|
type: string
|
|
ref:
|
|
required: true
|
|
type: string
|
|
target_env:
|
|
required: true
|
|
type: string
|
|
repo_build_id:
|
|
required: true
|
|
type: string
|
|
build_key:
|
|
required: true
|
|
type: string
|
|
resolved_source_sha:
|
|
required: true
|
|
type: string
|
|
convex_url:
|
|
required: true
|
|
type: string
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
CONVEX_URL: ${{ inputs.convex_url }}
|
|
REPO_BUILD_ID: ${{ inputs.repo_build_id }}
|
|
CONVEX_BUILD_TOKEN: ${{ secrets.CONVEX_BUILD_TOKEN }}
|
|
CI_PROGRESS_TOTAL: "10"
|
|
PLATFORMIO_LIBDEPS_DIR: ${{ github.workspace }}/.pio-libdeps/${{ inputs.owner }}/${{ inputs.repo }}/${{ inputs.target_env }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Notify Convex — running
|
|
shell: bash
|
|
run: |
|
|
curl -sSf -X POST "$CONVEX_URL/ingest-repo-build" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $CONVEX_BUILD_TOKEN" \
|
|
-d "{\"repo_build_id\":\"$REPO_BUILD_ID\",\"state\":\"running\",\"github_run_id\":${{ github.run_id }}}"
|
|
|
|
- name: CI progress — build started
|
|
shell: bash
|
|
env:
|
|
STEP_INDEX: 1
|
|
LABEL: Build started on Actions
|
|
run: python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.x"
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@v1
|
|
with:
|
|
bun-version: latest
|
|
|
|
- name: CI progress — tooling ready
|
|
shell: bash
|
|
env:
|
|
STEP_INDEX: 2
|
|
LABEL: Runner tooling ready
|
|
run: python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
|
|
- name: Download source archive
|
|
shell: bash
|
|
env:
|
|
OWNER: ${{ inputs.owner }}
|
|
REPO: ${{ inputs.repo }}
|
|
REF: ${{ inputs.ref }}
|
|
run: |
|
|
STEP_INDEX=3 LABEL="Downloading source archive" python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
ENC_REF=$(python3 -c "import urllib.parse,os; print(urllib.parse.quote(os.environ['REF'], safe=''))")
|
|
curl -fsSL -o /tmp/src.zip "https://codeload.github.com/${OWNER}/${REPO}/zip/${ENC_REF}"
|
|
|
|
- name: CI progress — restoring PlatformIO global cache
|
|
shell: bash
|
|
env:
|
|
STEP_INDEX: 4
|
|
LABEL: Restoring PlatformIO global cache (toolchains)
|
|
run: python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
|
|
- name: Cache PlatformIO global store
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.platformio/.cache
|
|
~/.platformio/packages
|
|
key: platformio-${{ runner.os }}-v1-${{ hashFiles('.github/workflows/custom_build.yml', '.github/workflows/custom_build_test.yml') }}
|
|
restore-keys: |
|
|
platformio-${{ runner.os }}-v1-
|
|
|
|
- name: CI progress — restoring PlatformIO libdeps cache
|
|
shell: bash
|
|
env:
|
|
STEP_INDEX: 5
|
|
LABEL: Restoring PlatformIO libdeps cache
|
|
run: python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
|
|
# Key is owner/repo/env only (not source SHA); bump …-v2 if lib_deps get out of sync.
|
|
- name: Cache PlatformIO libdeps (per repo + target)
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ${{ github.workspace }}/.pio-libdeps/${{ inputs.owner }}/${{ inputs.repo }}/${{ inputs.target_env }}
|
|
key: pio-libdeps-${{ inputs.owner }}-${{ inputs.repo }}-${{ inputs.target_env }}-v1
|
|
|
|
- name: Extract firmware source
|
|
shell: bash
|
|
run: |
|
|
STEP_INDEX=6 LABEL="Extracting firmware source" python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
set -e
|
|
sudo apt-get update -qq && sudo apt-get install -y -qq unzip
|
|
rm -rf /tmp/src
|
|
mkdir -p /tmp/src
|
|
unzip -q /tmp/src.zip -d /tmp/src
|
|
ROOT=$(find /tmp/src -mindepth 1 -maxdepth 1 -type d | head -1)
|
|
printf '%s\n' "$ROOT" > /tmp/fw-src-root.txt
|
|
mkdir -p "$PLATFORMIO_LIBDEPS_DIR"
|
|
|
|
- name: Install PlatformIO
|
|
shell: bash
|
|
run: |
|
|
STEP_INDEX=7 LABEL="Installing PlatformIO" python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
set -e
|
|
ROOT=$(cat /tmp/fw-src-root.txt)
|
|
cd "$ROOT"
|
|
python -m pip install -q --upgrade pip
|
|
pip install -q platformio adafruit-nrfutil
|
|
|
|
- name: Install PlatformIO dependencies
|
|
shell: bash
|
|
run: |
|
|
STEP_INDEX=8 LABEL="Installing firmware dependencies (PlatformIO)" python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
set -e
|
|
ROOT=$(cat /tmp/fw-src-root.txt)
|
|
cd "$ROOT"
|
|
pio pkg install -e "${{ inputs.target_env }}"
|
|
|
|
- name: Compile firmware (PlatformIO)
|
|
shell: bash
|
|
run: |
|
|
STEP_INDEX=9 LABEL="Compiling firmware (PlatformIO)" python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
set -e
|
|
ROOT=$(cat /tmp/fw-src-root.txt)
|
|
cd "$ROOT"
|
|
MKLITTLEFS_BIN=$(find "$HOME/.platformio/packages" -type f -name mklittlefs 2>/dev/null | head -1)
|
|
if [ -n "$MKLITTLEFS_BIN" ]; then
|
|
export PATH="$(dirname "$MKLITTLEFS_BIN"):$PATH"
|
|
fi
|
|
command -v mklittlefs >/dev/null 2>&1 || echo "WARNING: mklittlefs not on PATH; LittleFS build may fail"
|
|
pio run -e "${{ inputs.target_env }}" 2>&1 | tee /tmp/pio.log
|
|
BUILD_DIR=".pio/build/${{ inputs.target_env }}"
|
|
if [ ! -d "$BUILD_DIR" ]; then
|
|
echo "Build output directory missing"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Download Meshtastic ESP OTA companion (if applicable)
|
|
shell: bash
|
|
run: |
|
|
set -e
|
|
ROOT=$(cat /tmp/fw-src-root.txt)
|
|
cd "$ROOT"
|
|
BUILD_DIR=".pio/build/${{ inputs.target_env }}"
|
|
bash "${{ github.workspace }}/scripts/download-meshtastic-ota.sh" "$ROOT/$BUILD_DIR"
|
|
|
|
- name: Stage or generate nRF52 DFU files (if applicable)
|
|
shell: bash
|
|
run: |
|
|
set -e
|
|
ROOT=$(cat /tmp/fw-src-root.txt)
|
|
BUILD_DIR="$ROOT/.pio/build/${{ inputs.target_env }}"
|
|
|
|
# Path 1: MeshCore produces firmware.zip (Nordic DFU package) when adafruit-nrfutil
|
|
# is available during pio run. Unzip it to get bin/dat/manifest.json at build root.
|
|
if [ -f "$BUILD_DIR/firmware.zip" ]; then
|
|
echo "Found firmware.zip; extracting Nordic DFU files to build root"
|
|
unzip -j "$BUILD_DIR/firmware.zip" "*.bin" "*.dat" "manifest.json" -d "$BUILD_DIR/" || true
|
|
fi
|
|
|
|
# Path 2: firmware/ subdirectory fallback (local builds or alternate PIO layouts).
|
|
if [ -d "$BUILD_DIR/firmware" ] && ! ls "$BUILD_DIR"/*.dat >/dev/null 2>&1; then
|
|
echo "Found firmware/ subdirectory; staging nRF52 DFU files to build root"
|
|
shopt -s nullglob
|
|
for f in "$BUILD_DIR/firmware/"*.bin "$BUILD_DIR/firmware/"*.dat; do
|
|
cp -a "$f" "$BUILD_DIR/"
|
|
done
|
|
shopt -u nullglob
|
|
[ -f "$BUILD_DIR/firmware/manifest.json" ] && cp -a "$BUILD_DIR/firmware/manifest.json" "$BUILD_DIR/"
|
|
fi
|
|
|
|
# Path 3: firmware.bin present but still no .dat (Meshtastic UF2 builds, or MeshCore
|
|
# when the post-build script did not produce firmware.zip). Generate init packet.
|
|
if [ -f "$BUILD_DIR/firmware.bin" ] && ! ls "$BUILD_DIR"/*.dat >/dev/null 2>&1; then
|
|
echo "firmware.bin found but no .dat; generating DFU init packet"
|
|
adafruit-nrfutil dfu genpkg \
|
|
--dev-type 0xFFFF \
|
|
--dev-revision 0xFFFF \
|
|
--application-version 0xFFFFFFFF \
|
|
--sd-req 0xFFFE \
|
|
--application "$BUILD_DIR/firmware.bin" \
|
|
/tmp/nrf52-dfu.zip
|
|
unzip -j /tmp/nrf52-dfu.zip "*.dat" -d "$BUILD_DIR/"
|
|
echo "DFU init packet written: $(ls "$BUILD_DIR"/*.dat 2>/dev/null || echo '(none)')"
|
|
fi
|
|
|
|
- name: Package and upload firmware bundle
|
|
id: pio
|
|
env:
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }}
|
|
shell: bash
|
|
run: |
|
|
STEP_INDEX=10 LABEL="Packaging and uploading firmware bundle" python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
set -e
|
|
ROOT=$(cat /tmp/fw-src-root.txt)
|
|
cd "$ROOT"
|
|
BUILD_DIR=".pio/build/${{ inputs.target_env }}"
|
|
python3 "${{ github.workspace }}/scripts/emit-flash-manifest.py" "$ROOT/$BUILD_DIR" "$ROOT" "${{ inputs.target_env }}"
|
|
ARTIFACT_NAME="firmware-${{ inputs.build_key }}-${{ github.run_id }}.tar.gz"
|
|
STAGE=/tmp/fw-bundle
|
|
rm -rf "$STAGE"
|
|
mkdir -p "$STAGE"
|
|
shopt -s nullglob
|
|
for f in "$BUILD_DIR"/*.bin "$BUILD_DIR"/*.uf2 "$BUILD_DIR"/*.hex "$BUILD_DIR"/*.dat; do
|
|
cp -a "$f" "$STAGE/"
|
|
done
|
|
shopt -u nullglob
|
|
if [ -f "$BUILD_DIR/flash-manifest.json" ]; then
|
|
cp -a "$BUILD_DIR/flash-manifest.json" "$STAGE/"
|
|
fi
|
|
# Nordic DFU manifest (MeshCore and other nRF52 builds)
|
|
if [ -f "$BUILD_DIR/manifest.json" ]; then
|
|
cp -a "$BUILD_DIR/manifest.json" "$STAGE/"
|
|
fi
|
|
bash "${{ github.workspace }}/scripts/stage-fw-bundle-docs.sh" "$ROOT" "$STAGE"
|
|
if [ -z "$(find "$STAGE" -mindepth 1 -maxdepth 1 -type f -print -quit)" ]; then
|
|
echo "No firmware artifacts found in $BUILD_DIR"
|
|
exit 1
|
|
fi
|
|
tar -czf "/tmp/$ARTIFACT_NAME" -C "$STAGE" .
|
|
OBJECT_PATH="${R2_BUCKET_NAME}/${ARTIFACT_NAME}"
|
|
bunx wrangler r2 object put "$OBJECT_PATH" --file "/tmp/$ARTIFACT_NAME" --remote
|
|
STEP_INDEX=10 LABEL="Firmware bundle uploaded" python3 "${{ github.workspace }}/scripts/report-convex-ci-progress.py"
|
|
echo "r2_key=${ARTIFACT_NAME}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Notify Convex — success
|
|
if: success()
|
|
shell: bash
|
|
env:
|
|
R2_KEY: ${{ steps.pio.outputs.r2_key }}
|
|
run: |
|
|
curl -sSf -X POST "$CONVEX_URL/ingest-repo-build" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $CONVEX_BUILD_TOKEN" \
|
|
-d "{\"repo_build_id\":\"$REPO_BUILD_ID\",\"state\":\"succeeded\",\"github_run_id\":${{ github.run_id }},\"r2ObjectKey\":\"$R2_KEY\"}"
|
|
|
|
- name: Notify Convex — failure
|
|
if: failure()
|
|
shell: bash
|
|
env:
|
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
|
run: |
|
|
python3 -c "
|
|
import json, os, urllib.request
|
|
err = open('/tmp/pio.log').read()[-4000:] if os.path.exists('/tmp/pio.log') else 'build failed'
|
|
body = {
|
|
'repo_build_id': os.environ['REPO_BUILD_ID'],
|
|
'state': 'failed',
|
|
'github_run_id': int(os.environ['GITHUB_RUN_ID']),
|
|
'errorSummary': err,
|
|
}
|
|
req = urllib.request.Request(
|
|
os.environ['CONVEX_URL'] + '/ingest-repo-build',
|
|
data=json.dumps(body).encode(),
|
|
headers={'Content-Type': 'application/json', 'Authorization': 'Bearer ' + os.environ['CONVEX_BUILD_TOKEN']},
|
|
method='POST',
|
|
)
|
|
urllib.request.urlopen(req)
|
|
"
|