Author: Unknown; original provenance is not recorded. Status: Active release preparation. Downstream consumer validation is present; the first BCR publication awaits a maintainer-approved release.
This doc is the single source of truth for cutting a new Donner release on the Bazel Central Registry. It's tuned for quick execution, not exhaustive explanation — the "why" lives in the companion docs and PRs linked at the bottom.
Release sequence
- Bump module(name = "donner", version = "X.Y.Z") in MODULE.bazel
- Run the pre-release checklist below, make sure it's green
- After maintainer approval, merge the release PR, create vX.Y.Z, and publish its GitHub Release
- .github/workflows/release.yml builds, verifies, and attests the CLI binaries once
- After the release artifacts are green, use the reviewed .bcr/ templates to prepare the BCR PR manually
- Watch BCR presubmit CI on that PR, iterate on .bcr/presubmit.yml if anything fails, ping a BCR maintainer, wait for merge
What's on BCR (and what isn't)
The initial BCR module is donner, with tiny-skia + text-base as its default. A separate tiny-skia-cpp module is outside this initial v0.8 scope. Consumers need a C++20 compiler and standard library, including std::format; the validation matrix uses Ubuntu 24.04 and macOS with Bazel 7 and 8. The intended published surface is:
| Feature | On BCR? | How BCR consumers get it |
| SVG parser, CSS, DOM, computed style | ✅ | default, no flags needed |
| Tiny-skia software renderer | ✅ | default backend |
| Text rendering via stb_truetype (text-base) | ✅ | default text tier |
| Filter effects (all 17 primitives) | ✅ | built-in |
| Removed full-Skia backend (legacy) | ❌ | Historical note only; power users previously needed git_override |
| text-full (HarfBuzz + WOFF2) | ❌ | Power users via git_override; also tracked as a future follow-up BCR module |
| Geode (WebGPU + Slug) backend | ❌ | Not BCR-published: its wgpu-native / WebGPU deps are non-BCR (dev_dependency overrides). Geode itself is a supported backend (the editor's default), just not a BCR-consumable config |
The mechanism that keeps the non-BCR features invisible to BCR consumers is the dev_dependency = True module extension at third_party/bazel/non_bcr_deps.bzl. BCR strips dev-only extensions when Donner is consumed as a bazel_dep, so downstream users simply never see the former full-Skia repo, @harfbuzz, @woff2, or @wgpu_native_*.
Every Donner target that references one of those hidden repos must be guarded by target_compatible_with on the relevant config_setting (e.g. //donner/svg/renderer:text_full_enabled, //donner/svg/renderer:renderer_backend_skia, //donner/svg/renderer/geode:geode_enabled). If a BCR consumer's bazel build @donner//... ever tries to resolve one of those repos, the gating is broken — see the checklist below.
Per-release checklist
Do these in order. Each step is either a command to run or a one-line visual check.
Pre-flight
- Working tree on main, clean, up to date
- docs/release_checklist.md and the current release scope have no unresolved release blockers
- RELEASE_NOTES.md drafted for the version being cut
Version bump
- MODULE.bazel → module(name = "donner", version = "X.Y.Z") matches the tag being pushed
- No stale references to the previous version in docs/ or README.md
Dev-config build matrix (local or CI)
- bazel build //... — default config (tiny-skia + text-base)
- Historical note: the removed full-Skia backend used to build in the dev matrix
- bazel build --config=text-full //... — HarfBuzz + WOFF2 text shaping still builds
- bazel test //... on at least the default config green
BCR-consumer simulation (most important)
Run from a separate module so Donner's development-only dependencies and root .bazelrc do not apply. The fixture supplies its own C++20 flags and compiles, links, and renders through the public API.
- Run the downstream smoke test for every BCR matrix entry: sh
cd examples/bazel_consumer
bazel test //:render_svg
The local source override permits testing before publication. Remove that override and add the published version to bazel_dep when testing a registry entry.
- Build the public base, CSS, SVG, and renderer target list in .bcr/presubmit.yml with its explicit C++20 flags. Avoid package wildcards: they also select internal tests and fuzzers whose dependencies are intentionally development-only.
- Inspect the default renderer's configured dependency closure from the consumer module: sh
bazel cquery 'deps(@donner//donner/svg/renderer:renderer)'
It must include tiny-skia and exclude Geode, wgpu-native, HarfBuzz, WOFF2, and development fixtures.
- Repeat the consumer check against the extracted source archive, including its vendored tiny-skia sources. A checkout-only build does not qualify archive packaging.
- .bcr/source.template.json integrity and strip prefix match the exact reviewed archive bytes.
The BCR test-module mechanism runs examples/bazel_consumer via the bcr_test_module section of .bcr/presubmit.yml.
Scaffolding sanity
- .bcr/config.yml, .bcr/metadata.template.json, .bcr/source.template.json, .bcr/presubmit.yml all valid YAML/JSON
- .github/workflows/release.yml builds from released dependency tags with lockfile generation disabled, verifies both platform manifests, and attests the exact binaries before upload
Ship
- Root-cause the previous BCR attempt before retrying — pull up the last release's BCR PR on bazelbuild/bazel-central-registry (modules/donner/<prev>/), identify exactly why it failed, and confirm the fix is already in the checked-in .bcr/ templates and presubmit.yml targets. Do not retry blind. Cross-check the common-failures table below.
- After explicit approval, merge the release PR and create the intended tag and GitHub Release; the release workflow runs when the release is published
- Watch Actions tab: linux + macos CLI binary jobs run, then the verified artifact publication job
- Prepare the BCR entry from the reviewed release source and .bcr/ templates only after artifact publication is green
- Present the final registry diff and destination for explicit maintainer approval before submitting it
- Iterate on BCR presubmit failures via the common-failures table below
- Ping a BCR maintainer in the PR comments when presubmit CI goes green
Registry submission
BCR publication is intentionally separate from the credentialed release workflow. The release workflow produces and attests immutable CLI artifacts, but it does not rebuild source or invoke a third-party BCR publisher. After those artifacts are green, prepare the registry entry from the reviewed tag and the checked-in .bcr/metadata.template.json, .bcr/source.template.json, and .bcr/presubmit.yml. Verify the source archive digest and obtain explicit approval of the final registry diff before opening the BCR pull request from the maintainer fork. This keeps registry publication operator-initiated and prevents a moved tag or workflow rerun from silently substituting source.
Maintainer setup
- Fork bazelbuild/bazel-central-registry to jwmcglynn/bazel-central-registry.
- Use a dedicated maintainer identity for the fork and BCR pull request when release ownership is shared. Do not place a BCR write token in the release workflow.
Where to watch
Common failures & fixes
Update this section with real-world lessons as they happen.
| Symptom | Cause | Fix |
| BCR presubmit: missing external repository | A public target or wildcard selects development-only dependencies | Use explicit public targets and keep optional features behind their configuration guards |
| BCR PR presubmit: target not found | New top-level library added under //donner since last release | Add it to .bcr/presubmit.yml build_targets |
| BCR PR presubmit: integrity hash mismatch | GitHub regenerated the source tarball or the tag moved | Re-upload the release tarball verbatim; never force-push tags |
| source.template.json URL 404 | The source URL, tag, or release asset is missing | Verify the reviewed source URL and release asset; archive strip-prefix errors occur after download |
Adding a new top-level library
When you create a new top-level library under //donner/..., the BCR presubmit allowlist won't know about it automatically.
- Add the specific public library target to .bcr/presubmit.yml build_targets. Do not use package wildcards that also select tests or fuzzers.
- Re-run the BCR-consumer simulation cquery above to confirm your new library doesn't transitively pull in any non-BCR dep.
- If it does (and that's intentional — e.g. it's text-full-specific), gate the offending target with target_compatible_with on the relevant config_setting, same as text_backend_full and woff2_parser.
Future BCR scope expansion
Things that are deliberately out of scope for the first few BCR releases but may land later:
- text-full on BCR — vendor HarfBuzz + WOFF2 via git subtree (~1–2 days of BUILD.harfbuzz work), or ship a sibling donner-text-full module that layers on top of donner and brings its own HB/WOFF2. Blocked on: deciding whether to own an additional BCR module or vendor.
- Separate tiny-skia-cpp BCR module — it already has its own MODULE.bazel in third_party/tiny-skia-cpp; could be published independently and then consumed as a BCR bazel_dep from Donner. Blocked on: deciding the dev vs publish trade-off.
- Geode / wgpu-native on BCR — wgpu-native is distributed only as a prebuilt binary release (no upstream Bazel rules; no public source build on BCR), so the Geode backend stays git_override-only for the foreseeable future. Revisit post-v1.0 if someone puts up a donner-geode BCR module that pulls the http_archive in itself.
References
- Publish-to-BCR — background on registry publication tooling
- bazelbuild/bazel-central-registry — the BCR repository
- rules_foreign_cc/.bcr/ — reference .bcr/ layout for a C++ library
- docs/design_docs/0011-v0_5_release.md — v0.5 release scope
- third_party/bazel/non_bcr_deps.bzl — the dev-only extension that hides non-BCR deps
- docs/release_checklist.md — generic release checklist (pairs with this BCR-specific runbook)