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
110 changes: 110 additions & 0 deletions .github/scripts/build_xrplib_version.py
Original file line number Diff line number Diff line change
@@ -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/<version>/,
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()
79 changes: 79 additions & 0 deletions .github/workflows/publish-to-firmware.yml
Original file line number Diff line number Diff line change
@@ -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`.
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,42 @@

$~$

### That's it! Happy Learning!
### 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/<version>/` 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-<version>` 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.