Build action for fizzy plugins.
A reusable GitHub Actions workflow that builds a Fizzy
plugin for every supported target, hashes each binary, assembles the author manifest.json, and
publishes both as GitHub Release assets.
A Fizzy plugin is a native dylib valid for exactly one (abi_fingerprint, os-arch) pair, so a
release ships one binary per target plus a manifest the in-app store reads — indirectly, via the
fizzyedit/plugins aggregator, which folds every
author's manifest into a static catalog served at https://plugins.fizzyed.it/catalog/. This
action automates the build matrix so you don't hand-build, hand-hash, and hand-write the
manifest each release.
tag v0.1.0 ──► build.yml ──► per-target dylib + sha256
│
▼ assemble_manifest.py (accumulates prior releases)
release assets: pixi-macos-aarch64.dylib, …, manifest.json
│
▼ registry/<id>.json points manifest_url at releases/latest
fizzyedit/plugins aggregator ──► catalog/ (summary.json + per-fingerprint
releases.json) ──► Fizzy store
For a tag like v0.1.0, the release gets:
<id>-macos-aarch64.dylib <id>-macos-x86_64.dylib
<id>-linux-x86_64.so <id>-linux-aarch64.so
<id>-windows-x86_64.dll <id>-windows-aarch64.dll
manifest.json ← references the binaries above (url + sha256), accumulating older releases
-
Pin the Fizzy SDK by URL in your
build.zig.zon— not a localpath. CI has no sibling checkout, so a.path = "../../fizzy"dependency fails there. Use a fetchable archive:.fizzy = .{ .url = "https://github.com/fizzyedit/fizzy/archive/<commit>.tar.gz", .hash = "<zig-package-hash>", },
That pin is the source of truth for
fizzy_sdk_versionand the ReleaseFastabi_fingerprint— the action reads both from the built dylib. You never copy them into workflow YAML. See fizzy'sdocs/PLUGINS.md§5 for the fingerprint distinction. -
Add identity-only
plugin.zig.zonat the repo root (id/name/version/min_sdk_version). The pushed tag (vX.Y.Z) must equal.version. -
Make
zig buildinstall the plugin dylib tozig-out/<id>.<ext>— viafizzy.plugin.create+.install(generated dylib root; no authorroot.zig). If your build installs elsewhere, setartifact-path. -
Add the release workflow. Copy
examples/release.ymlto your plugin repo as.github/workflows/release.yml:name: Release on: push: tags: ["v*"] jobs: build: uses: fizzyedit/plugin-build-action/.github/workflows/build.yml@v3 permissions: contents: write with: zig-version: "0.16.0"
-
Register once in
fizzyedit/plugins— open a PR addingregistry/<id>.jsonwithmanifest_urlpointing at your latest release manifest:{ "id": "pixi", "name": "Pixi", "description": "Pixel-art editor for Fizzy.", "author": "foxnne", "homepage": "https://github.com/fizzyedit/pixi", "tags": ["editor", "pixel-art"], "manifest_url": "https://github.com/fizzyedit/pixi/releases/latest/download/manifest.json" }
git tag v0.1.0 && git push origin v0.1.0The workflow builds all targets, publishes the release with the binaries + manifest.json, and
the next fizzyedit/plugins aggregation (on merge, its 6-hourly cron, or a manual run) pulls your
plugin into the catalog. Subsequent releases need no registry PR — just tag again.
Bump the fizzy pin in build.zig.zon when you want a new SDK; the next tag's manifest picks up
the new fizzy_sdk_version / abi_fingerprint automatically. The manifest accumulates
releases (keyed by version + abi_fingerprint), so users on older SDKs keep matching an older
binary instead of seeing "needs a rebuild."
- Derive
fizzy_sdk_version+abi_fingerprintfromzig-out/sdk-meta.json(emitted byfizzy.plugin.install) — removed as workflow inputs. - Cross-compile all 6 targets from
ubuntu-latest(plugins are unsigned; no macOS/Windows runners or code signing). - Read
id/version/min_sdk_versionfromplugin.zig.zon; release version defaults to the triggeringv*tag (must match the zon). - Caller
release.ymlonly needszig-version(optional overrides forid/version/artifact-path/targetsremain).
- Pre-fetch Zig package dependencies with retries before building, working around Zig 0.16
HttpConnectionClosingflakes when fetching GitHub deps (especially on Windows CI). - Use a workspace-local
ZIG_GLOBAL_CACHE_DIRand pre-create cachetmp/(fixes cold-cache zip-fetchFileNotFoundon CI).
Initial release.
| Input | Required | Default | Description |
|---|---|---|---|
zig-version |
no | 0.16.0 |
Zig toolchain version. |
id |
no | plugin.zig.zon .id |
Override plugin id (must match the zon if set). |
version |
no | tag without v |
Override release version (must match plugin.zig.zon .version). |
artifact-path |
no | zig-out/<id> |
Built dylib path (relative to repo root) without extension. |
targets |
no | all 6 | Comma-separated os_arch subset to build. |
- Setup reads
plugin.zig.zon, checks the tag/versioninput against.version, and builds the target matrix. - One build per target across all 6 host arches, all cross-compiled with
-Dtarget=fromubuntu-latest. Plugins are unsigned pure Zig + vendored C (optional prebuilt.a/.libdeps are selected per target), so there is no macOS/Windows runner or signing step. - Every target collects
zig-out/sdk-meta.json(written by the fizzy pin at build time). Publish requires all targets to agree on sdk/fingerprint, thenscripts/assemble_manifest.pymerges the new release into the priormanifest.json.
| Path | Role |
|---|---|
.github/workflows/build.yml |
The reusable (workflow_call) build + publish workflow. |
scripts/assemble_manifest.py |
Merges per-target sha256 fragments into manifest.json. |
scripts/read_plugin_zon.py |
Parses identity from plugin.zig.zon. |
scripts/read_plugin_exports.py |
Optional local helper: dlopen a native plugin and print exports. |
examples/release.yml |
Drop-in caller for a plugin repo. |