Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
version: 2

# Only github-actions applies here. There is no Dependabot ecosystem for
# arduino-cli, so the core and library pins in .github/workflows/*.yml
# (PLATFORM_VERSION, ONEWIRE_VERSION, ETHERNET3_VERSION) are NOT tracked below
# and have to be reviewed by hand. That is deliberate rather than an oversight:
# this firmware sits at ~95% of flash, so a toolchain bump can push it over the
# ceiling and wants a human looking at the size delta.
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: monday
time: "09:00"
timezone: America/Los_Angeles

# One PR for everything, rather than one per action.
groups:
github-actions:
patterns:
- "*"
update-types:
- minor
- patch
- major

# Produces "chore(deps): bump the github-actions group with 3 updates".
#
# "chore" is deliberate, not cosmetic. Under release-please's default
# versioning strategy only feat, fix and breaking changes bump a version, so
# a chore commit cannot cause a release on its own -- merging a Dependabot PR
# will not mint a new firmware version. "chore" is also hidden in
# changelog-sections, so action bumps stay out of release notes that are read
# by people deciding whether to reflash a board.
#
# It also has to be *some* conventional prefix: without one, every Dependabot
# PR would fail the check in .github/workflows/commits.yml.
commit-message:
prefix: chore
include: scope

labels:
- dependencies
open-pull-requests-limit: 3
183 changes: 183 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
name: CI

on:
push:
branches: [main, osl-docs]
pull_request:
branches: [main]
workflow_dispatch:

# Pinned deliberately. This firmware fills 95% of a 30720 byte flash, so a
# toolchain or library bump can push it over the edge; we want that to be a
# reviewed change, not a surprise on an unrelated PR.
env:
FQBN: arduino:avr:pro:cpu=8MHzatmega328
PLATFORM_VERSION: 1.8.8
ONEWIRE_VERSION: 2.3.8
ETHERNET3_VERSION: 1.6.0
# Fail the build if fewer than this many bytes of flash are left. The hard
# ceiling is enforced by avr-gcc itself; this is the early warning.
MIN_FREE_FLASH: 512

jobs:
compile:
name: compile (${{ matrix.name }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: stock
flags: ""
- name: pwm
flags: "-DPWM=5"
steps:
- uses: actions/checkout@v7
with:
# build.sh derives GIT_VER from the git log, and the size-delta report
# needs the base ref. Neither works on a shallow clone.
fetch-depth: 0

- name: Derive version string
id: ver
run: |
echo "ver=$(git log -1 --date=short --format=%ad | tr -d -)-$(git rev-parse --short=7 HEAD)" >> "$GITHUB_OUTPUT"

- uses: arduino/compile-sketches@v1.1.3
with:
fqbn: ${{ env.FQBN }}
platforms: |
- name: arduino:avr
version: ${{ env.PLATFORM_VERSION }}
libraries: |
- name: OneWire
version: ${{ env.ONEWIRE_VERSION }}
- name: Ethernet3
version: ${{ env.ETHERNET3_VERSION }}
sketch-paths: |
- ./prc
cli-compile-flags: |
- --build-property
- build.extra_flags=${{ matrix.flags }} -DGIT_VER="${{ steps.ver.outputs.ver }}" -DBUILD_SET="${{ env.FQBN }}"
enable-deltas-report: true
enable-warnings-report: true
sketches-report-path: sketches-reports

- uses: actions/upload-artifact@v7
with:
name: sketches-report-${{ matrix.name }}
path: sketches-reports
if-no-files-found: error

headroom:
name: flash headroom + artifact
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

- uses: arduino/setup-arduino-cli@v2.0.0

- name: Install pinned toolchain
run: |
arduino-cli core update-index
arduino-cli core install "arduino:avr@${PLATFORM_VERSION}"
arduino-cli lib install "OneWire@${ONEWIRE_VERSION}"
arduino-cli lib install "Ethernet3@${ETHERNET3_VERSION}"

- name: Build
id: build
run: |
VER="$(git log -1 --date=short --format=%ad | tr -d -)-$(git rev-parse --short=7 HEAD)"
echo "ver=${VER}" >> "$GITHUB_OUTPUT"
arduino-cli compile \
--fqbn "${FQBN}" \
--build-property "build.extra_flags=-DGIT_VER=\"${VER}\" -DBUILD_SET=\"${FQBN}\"" \
--output-dir dist \
prc 2>&1 | tee build.log

- name: Check flash headroom
run: |
# "Sketch uses 29068 bytes (94%) of program storage space. Maximum is 30720 bytes."
read -r USED MAX < <(
sed -n 's/^Sketch uses \([0-9]*\) bytes .*Maximum is \([0-9]*\) bytes\.$/\1 \2/p' build.log
)
if [ -z "${USED}" ]; then
echo "::error::could not parse the size line out of build.log"
exit 1
fi
FREE=$(( MAX - USED ))
echo "flash: ${USED} / ${MAX} used, ${FREE} free (threshold ${MIN_FREE_FLASH})"
{
echo "### Flash usage"
echo ""
echo "| used | max | free |"
echo "|---:|---:|---:|"
echo "| ${USED} | ${MAX} | **${FREE}** |"
} >> "$GITHUB_STEP_SUMMARY"
if [ "${FREE}" -lt "${MIN_FREE_FLASH}" ]; then
echo "::error::only ${FREE} bytes of flash left, below the ${MIN_FREE_FLASH} byte threshold"
exit 1
fi

- name: Name the artifact after the build
run: |
mv dist/prc.ino.hex "dist/proc-v1-${{ steps.build.outputs.ver }}.hex"
rm -f dist/prc.ino.with_bootloader.hex
sha256sum dist/*.hex > dist/SHA256SUMS

- uses: actions/upload-artifact@v7
with:
name: firmware-${{ steps.build.outputs.ver }}
path: dist
if-no-files-found: error

links:
name: docs links
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

# Cross-repo links must be absolute URLs. A relative "../prc/..." resolves
# only in a workspace where both repos are siblings, and silently breaks
# for anyone who clones this repo on its own.
- name: Check relative links in markdown resolve
run: |
python3 - <<'PY'
import pathlib, re, sys
bad = 0
for md in sorted(pathlib.Path('.').rglob('*.md')):
if '.git' in md.parts:
continue
for m in re.finditer(r'\[([^\]]*)\]\(([^)\s]+)\)', md.read_text(encoding='utf-8')):
target = m.group(2)
if target.startswith(('http://', 'https://', '#', 'mailto:')):
continue
if not (md.parent / target.split('#')[0]).exists():
print(f"BROKEN {md}: [{m.group(1)}]({target})")
bad += 1
print(f"\n{bad} broken relative link(s)")
sys.exit(1 if bad else 0)
PY

format:
name: astyle
runs-on: ubuntu-latest
# Blocking. Confirmed on run 30223491125 that the runner's astyle produces no
# diff against a tree formatted with astyle 3.1 and tools/formatter.conf, so
# this cannot fail spuriously on a version difference alone.
steps:
- uses: actions/checkout@v7

- name: Install astyle
run: sudo apt-get update && sudo apt-get install -y astyle

- name: Check formatting
run: |
astyle --version
astyle --options=tools/formatter.conf --suffix=none prc/prc.ino
if ! git diff --exit-code -- prc/prc.ino; then
echo "::warning::astyle reformatted the sketch; run tools/format.sh and commit"
exit 1
fi
72 changes: 72 additions & 0 deletions .github/workflows/commits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Commits

on:
pull_request:
types: [opened, synchronize, reopened]

jobs:
lint:
name: conventional + sign-off
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0

# Every commit is preserved on main (we do not squash), so every commit is
# what release-please parses and what the changelog is built from. A
# non-conventional subject does not fail at release time -- the change just
# silently never appears in the changelog. So it is checked here instead.
- name: Check every commit in this PR
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
run: |
python3 - <<'PY'
import os, re, subprocess, sys

TYPES = "feat|fix|perf|docs|build|ci|refactor|test|chore|revert"
SUBJECT = re.compile(rf"^({TYPES})(\([a-z0-9._/-]+\))?!?: .+")

rng = f"{os.environ['BASE']}..{os.environ['HEAD']}"
shas = subprocess.run(
["git", "rev-list", "--no-merges", rng],
capture_output=True, text=True, check=True,
).stdout.split()

# Bots cannot sign off: Dependabot has no DCO option. Their commits still
# have to be conventional -- Dependabot uses chore(deps), which keeps it
# out of the changelog and stops it triggering a release, but it still
# has to parse.
BOTS = ("dependabot[bot]", "github-actions[bot]", "release-please[bot]")

bad = []
for sha in shas:
msg = subprocess.run(
["git", "log", "-1", "--format=%B", sha],
capture_output=True, text=True, check=True,
).stdout
author = subprocess.run(
["git", "log", "-1", "--format=%an <%ae>", sha],
capture_output=True, text=True, check=True,
).stdout.strip()
subject = msg.splitlines()[0] if msg.strip() else ""
short = sha[:8]
is_bot = any(b in author for b in BOTS)
if not SUBJECT.match(subject):
bad.append(f"{short} not conventional: {subject!r}")
elif subject[len(subject.split(':')[0]) + 2:][:1].isupper():
bad.append(f"{short} subject should start lowercase: {subject!r}")
if subject.endswith("."):
bad.append(f"{short} subject should not end with a period: {subject!r}")
if not is_bot and "Signed-off-by:" not in msg:
bad.append(f"{short} missing Signed-off-by (commit with -s): {subject!r}")

print(f"checked {len(shas)} commit(s) in {rng}")
for line in bad:
print(f"::error::{line}")
if bad:
print("\nFix with: git rebase -i --signoff " + os.environ["BASE"])
sys.exit(1)
print("all commits OK")
PY
31 changes: 31 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: release-please

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v5.0.0
id: release
with:
# Falls back to GITHUB_TOKEN when the secret is not set, so this
# workflow works either way. But a PR created with GITHUB_TOKEN does
# not trigger workflows, so its required checks never report and it
# shows as blocked by branch protection -- mergeable only by an admin.
# Setting RELEASE_PLEASE_TOKEN (see CONTRIBUTING.md) makes release PRs
# run CI like any other and removes the need for that bypass.
token: ${{ secrets.RELEASE_PLEASE_TOKEN || github.token }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

outputs:
released: ${{ steps.release.outputs.release_created }}
tag: ${{ steps.release.outputs.tag_name }}
Loading
Loading