From 350005c434f6809249689f6f9bccc95bcb3cb38d Mon Sep 17 00:00:00 2001 From: Jacob Williams Date: Thu, 23 Jul 2026 19:58:05 -0400 Subject: [PATCH 1/2] Add XRPLib firmware publishing automation Add a GitHub Actions workflow that automatically publishes XRPLib releases to the XRP_Firmware repository. Includes a Python script that assembles versioned bundles by copying XRPLib and XRPExamples from the release, carrying forward ble/ and phew/ from the previous version, generating files.json, and registering the version in index.json. Opens a pull request against the firmware repository for review. Largely untested! Needs repository secret FIRMWARE_TOKEN: a fine-grained PAT (or GitHub App token) with Contents: write and Pull requests: write on Open-STEM/XRP_Firmware --- .github/scripts/build_xrplib_version.py | 110 ++++++++++++++++++++++ .github/workflows/publish-to-firmware.yml | 79 ++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 .github/scripts/build_xrplib_version.py create mode 100644 .github/workflows/publish-to-firmware.yml diff --git a/.github/scripts/build_xrplib_version.py b/.github/scripts/build_xrplib_version.py new file mode 100644 index 0000000..2f52f09 --- /dev/null +++ b/.github/scripts/build_xrplib_version.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +""" +Assemble a new XRPLib version directory inside a checked-out XRP_Firmware repo. + +Called by .github/workflows/publish-to-firmware.yml on a XRP_MicroPython release. +It copies this release's XRPLib/ and XRPExamples/ into boards/XRPLib//, +carries ble/ and phew/ forward from the previous version (they do not live in this +repo), regenerates files.json, and registers the version in index.json. + +The files.json format and device-path convention are defined in the firmware repo's +boards/spec.md; this reproduces the generator that normally lives in XRPWeb. +""" + +import json +import os +import shutil +import sys + +# Directories whose files land under /lib on the robot; everything else keeps its path. +LIB_DIRS = ("XRPLib", "AgXRPLib", "ble", "phew") +# Never bundled: version.py is synthesized on-device from the registry version. +EXCLUDE_NAMES = ("version.py",) +EXCLUDE_DIRS = ("__pycache__",) + + +def die(msg): + print("ERROR: {}".format(msg), file=sys.stderr) + sys.exit(1) + + +def copy_tree(src, dst): + if not os.path.isdir(src): + die("expected source directory does not exist: {}".format(src)) + shutil.copytree( + src, dst, + ignore=shutil.ignore_patterns(*EXCLUDE_DIRS, "*.pyc", *EXCLUDE_NAMES), + ) + + +def device_path(rel_path): + # rel_path is POSIX-style, relative to the version dir, e.g. "XRPLib/board.py". + top = rel_path.split("/", 1)[0] + return "/lib/" + rel_path if top in LIB_DIRS else "/" + rel_path + + +def generate_files_json(version_dir): + pairs = [] + for root, dirs, files in os.walk(version_dir): + dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS] + for name in files: + if name in EXCLUDE_NAMES or name.endswith(".pyc") or name == "files.json": + continue + abs_path = os.path.join(root, name) + rel = os.path.relpath(abs_path, version_dir).replace(os.sep, "/") + pairs.append([device_path(rel), rel]) + # files.json is a plain ascending sort by source path (see spec.md / existing releases). + pairs.sort(key=lambda pair: pair[1]) + with open(os.path.join(version_dir, "files.json"), "w", newline="\n") as f: + json.dump(pairs, f, indent=4) + f.write("\n") + + +def main(): + version = os.environ["XRPLIB_VERSION"].strip() + src_repo = os.environ.get("SRC_REPO", ".") + firmware = os.environ["FIRMWARE_REPO"] + + store = os.path.join(firmware, "boards", "XRPLib") + index_path = os.path.join(store, "index.json") + version_dir = os.path.join(store, version) + + with open(index_path) as f: + index = json.load(f) + + version_id = "xrplib-{}".format(version) + if any(v.get("id") == version_id or v.get("dir") == version for v in index["versions"]): + print("Version {} already registered in index.json; nothing to do.".format(version)) + return + + if os.path.exists(version_dir): + die("{} already exists but is not in index.json; refusing to overwrite".format(version_dir)) + + if not index["versions"]: + die("no existing versions to source ble/ and phew/ from; seed one manually first") + previous_dir = os.path.join(store, index["versions"][0]["dir"]) + + # This release supplies XRPLib and XRPExamples; ble/phew are carried forward unchanged. + os.makedirs(version_dir) + copy_tree(os.path.join(src_repo, "XRPLib"), os.path.join(version_dir, "XRPLib")) + copy_tree(os.path.join(src_repo, "XRPExamples"), os.path.join(version_dir, "XRPExamples")) + for carried in ("ble", "phew"): + copy_tree(os.path.join(previous_dir, carried), os.path.join(version_dir, carried)) + + generate_files_json(version_dir) + + index["versions"].insert(0, { + "id": version_id, + "name": "XRPLib {}".format(version), + "dir": version, + "version": version, + }) + with open(index_path, "w", newline="\n") as f: + json.dump(index, f, indent=4) + f.write("\n") + + print("Created {} and registered {}".format(version_dir, version_id)) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/publish-to-firmware.yml b/.github/workflows/publish-to-firmware.yml new file mode 100644 index 0000000..c44d6dd --- /dev/null +++ b/.github/workflows/publish-to-firmware.yml @@ -0,0 +1,79 @@ +name: Publish XRPLib to Firmware + +# When a release is published here, assemble the released XRPLib as a new versioned +# bundle in Open-STEM/XRP_Firmware and open a pull request against it for review. +# +# Requires a repository secret FIRMWARE_TOKEN: a fine-grained PAT (or GitHub App token) +# with Contents: write and Pull requests: write on Open-STEM/XRP_Firmware. The default +# GITHUB_TOKEN cannot push to or open PRs on another repository. + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: "XRPLib version to publish (e.g. 2.2.4). Defaults to the release tag." + required: false + +env: + FIRMWARE_REPO_SLUG: Open-STEM/XRP_Firmware + +jobs: + publish: + name: Add version to XRP_Firmware + runs-on: ubuntu-latest + # Skip pre-releases; only full releases become firmware versions. + if: ${{ github.event_name == 'workflow_dispatch' || !github.event.release.prerelease }} + + steps: + - name: Resolve version + id: version + run: | + RAW="${{ github.event.inputs.version || github.event.release.tag_name }}" + if [ -z "$RAW" ]; then + echo "No version available from input or release tag." >&2 + exit 1 + fi + # Strip a leading 'v' (v2.2.4 -> 2.2.4) + VER="${RAW#v}" + echo "version=$VER" >> "$GITHUB_OUTPUT" + echo "Publishing XRPLib $VER" + + - name: Checkout XRP_MicroPython at the released version + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name || github.ref }} + path: source + + - name: Checkout XRP_Firmware + uses: actions/checkout@v4 + with: + repository: ${{ env.FIRMWARE_REPO_SLUG }} + token: ${{ secrets.FIRMWARE_TOKEN }} + path: firmware + + - name: Assemble version bundle + env: + XRPLIB_VERSION: ${{ steps.version.outputs.version }} + SRC_REPO: source + FIRMWARE_REPO: firmware + run: python source/.github/scripts/build_xrplib_version.py + + - name: Open pull request against XRP_Firmware + uses: peter-evans/create-pull-request@v6 + with: + path: firmware + token: ${{ secrets.FIRMWARE_TOKEN }} + add-paths: boards/XRPLib + branch: xrplib-${{ steps.version.outputs.version }} + delete-branch: true + commit-message: "Add XRPLib ${{ steps.version.outputs.version }} from XRP_MicroPython release" + title: "Add XRPLib ${{ steps.version.outputs.version }}" + body: | + Automated from the XRP_MicroPython release + [${{ github.event.release.tag_name || steps.version.outputs.version }}](${{ github.event.release.html_url }}). + + Adds `boards/XRPLib/${{ steps.version.outputs.version }}/` (XRPLib + XRPExamples + from the release, `ble`/`phew` carried forward from the previous version) with a + generated `files.json`, and registers the version in `boards/XRPLib/index.json`. From d8c8d8021886d5e2fe142cafd46056074ed7fc41 Mon Sep 17 00:00:00 2001 From: Jacob Williams Date: Thu, 23 Jul 2026 20:00:41 -0400 Subject: [PATCH 2/2] Document release workflow to XRP_Firmware Add a maintainer-facing section to README --- README.md | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d0b56b6..661a95c 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,42 @@ $~$ -### That's it! Happy Learning! \ No newline at end of file +### That's it! Happy Learning! + +$~$ + +## For Maintainers: Releasing to XRP_Firmware + +Publishing a GitHub release here automatically proposes the same XRPLib as a new +versioned bundle in [Open-STEM/XRP_Firmware](https://github.com/Open-STEM/XRP_Firmware), +via the `Publish XRPLib to Firmware` workflow +([.github/workflows/publish-to-firmware.yml](.github/workflows/publish-to-firmware.yml)). + +**What it does.** On a published (non-pre-) release it checks out this repo at the +release tag, assembles `boards/XRPLib//` in XRP_Firmware, and opens a pull +request there for a maintainer to review and merge. The bundle contains: + +- `XRPLib/` and `XRPExamples/` copied from this release, +- `ble/` and `phew/` carried forward unchanged from the previous version (they are not + stored in this repo), +- a generated `files.json`, and +- a new entry in `boards/XRPLib/index.json`. + +The version is taken from the release tag with any leading `v` stripped +(`v2.2.4` → `2.2.4`), and becomes the directory name and the `xrplib-` id. + +**One-time setup.** Add a repository secret named `FIRMWARE_TOKEN` — a fine-grained +PAT or GitHub App token with **Contents: write** and **Pull requests: write** on +Open-STEM/XRP_Firmware. Contents: write is needed to push the PR's branch (not to +merge or write to `main`); the default `GITHUB_TOKEN` cannot reach another repository. +Protect `main` on XRP_Firmware so nothing merges without review. + +**Manual runs and backfills.** Trigger the workflow by hand from the Actions tab +(`Run workflow`) and optionally supply a `version` to (re)publish a specific one. Re-runs +are safe: if that version already exists in `index.json` on XRP_Firmware the workflow +makes no changes, and an open PR for the same version is updated rather than duplicated. + +**Note.** The `files.json` generator in +[.github/scripts/build_xrplib_version.py](.github/scripts/build_xrplib_version.py) +reproduces the convention documented in XRP_Firmware's `boards/spec.md`; if that spec +changes, update the script to match. \ No newline at end of file