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
199 changes: 199 additions & 0 deletions .github/workflows/build-plugins.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
name: Build Plugins

# Produces drop-in plugin folders for a machine that has no Rust toolchain: the
# bench downloads a bundle, copies its folders into ~/.augur/plugins/, and hits
# "Scan for New Plugins". Pull requests get workflow artifacts; main also
# publishes a rolling release so the download needs no GitHub login.

on:
push:
branches:
- main
pull_request:
workflow_dispatch:
inputs:
augur_rs_ref:
description: "augur-rs ref to build against (branch, tag or SHA)"
required: false
default: fix/gui-layout-and-alignment

concurrency:
group: build-plugins-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
CARGO_TERM_COLOR: always
# The host branch this repository actually compiles against. augur-rs `main`
# does not carry the TableSchema, host-view and dataset-descriptor API these
# plugins use, so defaulting to `main` would be a guaranteed red build and
# would never hand the bench a bundle. Move this back to `main` in the same
# commit that the host API lands there.
AUGUR_RS_REF: ${{ inputs.augur_rs_ref || 'fix/gui-layout-and-alignment' }}

jobs:
build:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
name: macOS (arm64)
bundle: macos-arm64
- os: macos-13
name: macOS (x86_64)
bundle: macos-x86_64
- os: ubuntu-latest
name: Linux (x86_64)
bundle: linux-x86_64
- os: windows-latest
name: Windows (x86_64)
bundle: windows-x86_64

defaults:
run:
# The repo drives its builds through two bash scripts; use the same
# shell on Windows so there is exactly one code path to reason about.
shell: bash

steps:
# This workspace depends on the host by path (../augur-rs/augur-core), so
# CI has to reproduce the two-sibling-checkout layout, not clone one repo.
- name: Check out augur-plugins
uses: actions/checkout@v5
with:
path: augur-plugins

- name: Check out augur-rs
uses: actions/checkout@v5
with:
repository: muthmann/augur-rs
ref: ${{ env.AUGUR_RS_REF }}
path: augur-rs

- name: Pin the host revision and disarm the source patch
run: |
echo "AUGUR_RS_SHA=$(git -C augur-rs rev-parse HEAD)" >> "$GITHUB_ENV"
# build-runtime-plugins.sh patches [patch."…/augur-rs.git"] whenever a
# sibling augur-rs *git checkout* exists. This workspace already
# depends on it by path, so that patch matches nothing in the crate
# graph β€” it only costs cargo a fetch of the checkout. Removing .git
# makes the script's detection fail and the path deps win outright.
rm -rf augur-rs/.git

- name: Resolve the pinned Rust toolchain
run: |
channel="$(sed -n 's/^channel *= *"\(.*\)"$/\1/p' augur-plugins/rust-toolchain.toml | head -n 1)"
if [[ -z "${channel}" ]]; then
echo "No channel found in augur-plugins/rust-toolchain.toml" >&2
exit 1
fi
echo "RUST_CHANNEL=${channel}" >> "$GITHUB_ENV"

- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: ${{ env.RUST_CHANNEL }}
cache-workspaces: augur-plugins
# The action injects RUSTFLAGS="-D warnings" by default. That is right
# for a lint job and wrong here: this job ships artifacts, and a dead-
# code warning in one plugin must not deny the bench a bundle for all
# of them. Lint gating belongs in its own job, not in the build.
rustflags: ""

- name: Install Linux system dependencies
if: runner.os == 'Linux'
# Only what the plugin crates actually link. augur-gui's own dependency
# script is deliberately not reused: it is a superset (X11/Wayland/GL for
# the GUI, which no plugin links) and it does not exist on every augur-rs
# revision this job can be pointed at, so borrowing it made the Linux
# build fail on the value of augur_rs_ref. serialport needs libudev.
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends pkg-config libudev-dev

- name: Build runtime plugins
working-directory: augur-plugins
run: bash scripts/build-runtime-plugins.sh --profile release

- name: Stage installable plugin folders
working-directory: augur-plugins
run: bash scripts/install-built-plugins.sh --profile release --dest "dist/${{ matrix.bundle }}"

- name: Write build provenance
working-directory: augur-plugins
run: |
{
echo "bundle: ${{ matrix.bundle }}"
echo "built_at: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "augur_plugins: $(git rev-parse HEAD)"
echo "augur_rs_ref: ${AUGUR_RS_REF}"
echo "augur_rs_sha: ${AUGUR_RS_SHA}"
echo "rustc: $(rustc --version)"
echo
echo "Copy the plugin folders next to this file into ~/.augur/plugins/,"
echo "then use Plugins -> Scan for New Plugins in augur-gui."
} > "dist/${{ matrix.bundle }}/BUILD-INFO.txt"

- name: Upload plugin bundle
uses: actions/upload-artifact@v4
with:
name: augur-plugins-${{ matrix.bundle }}
path: augur-plugins/dist/${{ matrix.bundle }}
if-no-files-found: error

release:
name: Publish rolling release
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Download every plugin bundle
uses: actions/download-artifact@v4
with:
path: bundles
pattern: augur-plugins-*

- name: Package one archive per platform
run: |
set -euo pipefail
mkdir -p dist
for bundle_dir in bundles/augur-plugins-*/; do
bundle="$(basename "${bundle_dir%/}")"
(cd "${bundle_dir}" && zip -qr "${GITHUB_WORKSPACE}/dist/${bundle}.zip" .)
echo "Packaged ${bundle}.zip"
done
(cd dist && sha256sum ./*.zip > SHA256SUMS.txt)
ls -l dist

- name: Publish rolling release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
tag="plugins-latest"
# Delete and recreate rather than upload --clobber: it retags at the
# new commit and guarantees no asset from an older build survives.
gh release delete "${tag}" --yes --cleanup-tag || true
gh release create "${tag}" dist/* \
--title "Prebuilt plugins (latest main)" \
--notes "$(printf '%s\n' \
"Prebuilt AugurRS plugins, rebuilt on every push to \`main\`." \
"" \
"- augur-plugins: \`${GITHUB_SHA}\`" \
"- built against augur-rs \`${AUGUR_RS_REF}\`" \
"" \
"Download the archive for your platform, unpack it, and copy the" \
"plugin folders inside into \`~/.augur/plugins/\`. Then open augur-gui," \
"go to **Plugins**, and click **Scan for New Plugins**." \
"" \
"\`BUILD-INFO.txt\` in each archive records the exact revisions and" \
"compiler the libraries were built with. Verify downloads against" \
"\`SHA256SUMS.txt\`.")"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
/dist
Cargo.lock
*.swp
*.swo
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[workspace]
members = [
"evesmlm-types",
"stage-a-io",
"stage-a-plugin-contract",
"plugins/stage-a-a1",
Expand Down Expand Up @@ -27,6 +28,7 @@ augur-core = { path = "../augur-rs/augur-core" }
augur-plugin-api = { path = "../augur-rs/augur-plugin-api" }
augur-plugin-types = { path = "../augur-rs/augur-plugin-types" }
egui = "0.27"
evesmlm-types = { path = "evesmlm-types" }
rustfft = "6"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,31 @@ The plugin crates under `plugins/` are under active development and not yet read

## Quick Start

### Download Prebuilt Plugins (no toolchain needed)

Every push to `main` publishes freshly built plugins for macOS (arm64 and x86_64),
Linux and Windows to the rolling
[`plugins-latest`](https://github.com/muthmann/augur-plugins/releases/tag/plugins-latest)
release. This is the recommended route for a measurement machine.

```bash
curl -LO https://github.com/muthmann/augur-plugins/releases/download/plugins-latest/augur-plugins-macos-arm64.zip
unzip augur-plugins-macos-arm64.zip -d augur-plugins-bundle
mkdir -p ~/.augur/plugins
cp -R augur-plugins-bundle/*/ ~/.augur/plugins/
```

Pick the archive matching the machine: `macos-arm64`, `macos-x86_64`,
`linux-x86_64`, or `windows-x86_64`. Then open `augur-gui`, go to **Plugins**, and
click **Scan for New Plugins**.

Each archive contains a `BUILD-INFO.txt` recording the `augur-rs` revision and the
`rustc` version the libraries were built against β€” quote it in any ABI-mismatch
report. Verify downloads against `SHA256SUMS.txt` from the same release.

Pull requests build the same bundles as workflow artifacts. See
[CI Prebuilt Plugin Bundles](./docs/features/ci-prebuilt-plugin-bundles.md).

### Build One Plugin

```bash
Expand Down Expand Up @@ -145,6 +170,7 @@ augur-plugins/

- [Plugin API Notes](./docs/plugin-api.md) β€” repo-local summary of the current runtime contract
- [Installing Plugins](./docs/installing-plugins.md) β€” build, copy, reload, and troubleshoot installed plugins
- [CI Prebuilt Plugin Bundles](./docs/features/ci-prebuilt-plugin-bundles.md) β€” how the downloadable per-platform bundles are built and published
- [Architecture Notes](./docs/architecture.md) β€” repository role, execution model, host views, and shared settings
- [augur-rs Plugin Authoring Guide](https://github.com/muthmann/augur-rs/blob/main/docs/features/plugin-authoring-guide.md) β€” canonical host/runtime authoring guide
- [augur-rs Global Settings Guide](https://github.com/muthmann/augur-rs/blob/main/docs/features/global-settings-menu.md) β€” host-owned settings published to plugins
Expand Down
90 changes: 90 additions & 0 deletions docs/adr/030-prebuilt-plugin-bundles-from-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# ADR 030 β€” Prebuilt plugin bundles are produced by CI, not by the bench

**Status:** accepted
**Date:** 2026-08-04
**Feature brief:** [CI Prebuilt Plugin Bundles](../features/ci-prebuilt-plugin-bundles.md)

## Context

A runtime plugin is a `cdylib` plus a `plugin.toml`. Getting one onto a machine
required a Rust toolchain, a sibling `augur-rs` checkout, and `cargo`, because
this workspace depends on the host by path. That made the measurement PC a
development machine by necessity: every plugin fix had to be compiled where it
was used.

Two properties of the plugin model make "just compile it there" worse than it
looks. Plugins are dlopened into the host process, so the compiler that builds a
plugin and the compiler that builds `augur-gui` have to agree β€” and this
repository pinned no toolchain at all while `augur-rs` pinned `1.95.0`. And the
installed folder is not just a library: A1 ships operator-facing `protocols/`
examples, and macOS copies need their dylib id rewritten or Plugin Manager
reloads resolve back into Cargo's build tree.

## Decision

CI builds the runtime plugins on every pull request and every push to `main`, for
macOS arm64, macOS x86_64, Linux x86_64 and Windows x86_64, and publishes the
result as a folder that is copied verbatim into `~/.augur/plugins/`.

Three things follow from that, and they are the actual decision:

1. **This repository pins the host's toolchain.** `rust-toolchain.toml` carries
the same `1.95.0` as `augur-rs`, and the workflow reads the channel out of
that file instead of naming a version in YAML. A bundle built by a different
compiler than the host is not a bundle, it is a load failure waiting to
happen, and the pin is the only thing that makes that guarantee checkable.

2. **CI runs the repository's own build and install scripts.** It does not
reimplement plugin discovery, library naming, the `protocols/` copy or the
macOS install-name rewrite in YAML. The scripts are the single definition of
what an installed plugin is; CI is one more caller of them, with
`--dest dist/<bundle>` instead of `~/.augur/plugins`.

3. **`main` publishes a rolling release, not just artifacts.** Workflow artifacts
need a GitHub login and expire after 90 days. The bench is the consumer, and
it should be able to `curl` a URL. The tag `plugins-latest` is deleted and
recreated on every push to `main`, so its assets can never be a mixture of two
builds.

Every bundle carries a `BUILD-INFO.txt` recording the `augur-plugins` commit, the
`augur-rs` ref and SHA, and the exact `rustc` version.

## Consequences

- The measurement PC needs no toolchain, no checkout, and no `cargo`.
- An ABI-mismatch report from the bench is now answerable: the provenance file
says which host revision and compiler the installed library came from.
- Local builds in this repository move from whatever `rustc` is on `PATH` to the
pinned `1.95.0` β€” but only for people whose `cargo` is the rustup shim. A
Homebrew `cargo` earlier on `PATH` ignores `rust-toolchain.toml` entirely and
will keep producing plugins for a compiler the host does not use.
- The macOS bundles are per-architecture while `augur-gui` ships universal, so
the download page has one more choice on it than the host's does.
- `main` gains a permanent release tag. The repository had no releases before, so
`releases/latest` now resolves to `plugins-latest`; a future versioned release
scheme would have to account for that.

## Alternatives considered

**Publish only workflow artifacts.** Simplest, and rejected: it puts a GitHub
login between the bench and a fix, and the artifact disappears after 90 days.

**Build against the newest `augur-rs` release tag, or against `main`.** Both were
rejected by fact rather than by preference: `augur-rs` `main` does not carry the
`TableSchema`, host-view or dataset-descriptor API these plugins already use, so
either choice is a guaranteed red build. The default host ref is therefore the
open host branch that does carry it, and `BUILD-INFO.txt` records the exact ref
and SHA behind every library so the coupling stays visible. This is temporary by
construction: the default moves to `main` in the same commit that the host API
lands there.

**Reimplement the install layout in the workflow.** Would have avoided calling
shell scripts from YAML, at the cost of a second, silently divergent definition
of what an installed plugin contains. The `protocols/` folder and the macOS
install-name rewrite were both added to the script after the fact; a YAML copy
would have missed both.

**`lipo` the two macOS builds into universal libraries.** Attractive, since the
host is universal, but `install-built-plugins.sh` reads `target/<profile>` only
and a cross-build lands in `target/<triple>/<profile>`. Deferred rather than
special-cased in CI, since it belongs in the script if it is worth doing.
Loading
Loading