Skip to content

chore: release v0.5.3 - #101

Closed
tycho wants to merge 29 commits into
masterfrom
release/2026-07-29T02-51-41Z
Closed

chore: release v0.5.3#101
tycho wants to merge 29 commits into
masterfrom
release/2026-07-29T02-51-41Z

Conversation

@tycho

@tycho tycho commented Jul 29, 2026

Copy link
Copy Markdown
Owner

🤖 New release

  • gloam: 0.5.2 -> 0.5.3 (✓ API compatible changes)
Changelog

0.5.3 - 2026-07-29

Added

  • (generator) per-spec module layout for Rust crates
  • (generator) EGL Rust loader
  • (generator) Vulkan Rust loader (phased, scope-aware)
  • (generator) Vulkan type printer for the Rust backend
  • (generator) derive Rust GL base types from registry payloads
  • (parse) structured TypePayload for spec type bodies
  • (parse) typed C declarator IR (TypeRef) for command params and returns
  • (ci) run cargo hack with --feature-powerset flag
  • (regen) add gloam regen subcommand for in-place tree regeneration
  • (generator) add Rust backend implementation

Fixed

  • (generator) refuse multi-spec requests in the Rust backend
  • (resolve) deterministic ordering for dependency-pulled extensions
  • (ci) skip running certain workflows on non-GitHub hosting
  • (cargo) unbreak build on Rust 1.88, hold rusqlite at 0.39

Other

  • (cargo) bump rust-version to 1.95
  • (examples) regenerate loader trees via gloam regen
  • update Rust-backend spec-support probes
  • (examples) regenerate loader trees via gloam regen
  • (examples) regenerate loader trees via gloam regen
  • (examples) regenerate loader trees via gloam regen
  • (examples) regenerate loader trees via gloam regen
  • (cargo) update base64 and rusqlite
  • document the rust and regen subcommands
  • (examples) regenerate loader trees via gloam regen
  • regenerate example trees in the weekly bundled-update workflow
  • (xtask) make regen a thin wrapper over gloam regen
  • (bundled) embed specs/headers as DEFLATE blobs (~6 MiB smaller binary)


This PR was generated with release-plz.

tycho added 29 commits July 27, 2026 00:22
This is a work-in-progress implementation of a rust output.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Every generated tree records the command line that produced it, but the
recorded --out-path is relative to whatever cwd the original invocation
used, which is unknowable later — so replaying the recorded command only
worked from that exact directory.

`gloam regen [paths...]` replays each tree's recorded command with the
current gloam, deriving the effective output path from the manifest's own
location (<tree>/.gloam/manifest.json) and re-recording the original
command line verbatim.  Trees therefore regenerate correctly from any
working directory, and regeneration never rewrites the recorded command.
A path may be a tree root, a directory to search recursively (default:
.), or a manifest file itself; bare `gloam lock` snapshots regenerate in
place, unrelated files named manifest.json are skipped, and .git/target
directories are never descended into.

By default each replay is pinned to the tree's recorded provenance
(--lock semantics), so a diff shows only the effect of gloam changes;
--fresh re-resolves sources instead, advancing the tree.  The suggested
review filter now matches the current preamble stamps (C ` * @generated`,
Rust `// @generated`) and additionally ignores the output BOM's 6-space
"blob" lines, which track stamp churn byte-for-byte; that is safe because
a provenance pin's blob never moves without its unfiltered sibling
"commit" line moving too.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Discovery, path handling, and the locked-vs-fresh semantics now live in
`gloam regen` itself, so the xtask just builds the working-copy gloam and
runs it.  The tree root defaults to the current directory, so a bare
`cargo xtask regen` regenerates every tree in the repo — including
examples/rust, which the old replay-with-tree-root-cwd scheme could not
handle (its recorded --out-path was relative to the repo root, the C
examples' to examples/c; no single cwd satisfied both).

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
After re-blessing the golden snapshots, replay each example tree's
recorded command against the refreshed bundle (`gloam regen --fresh`;
locked regen would refuse pins the new bundle no longer satisfies) and
include examples/ in the PR.

Regenerated files embed gloam's own version/commit, so a regen at a new
HEAD always produces cosmetic churn; the regen is kept only when the diff
is significant under the same filter `gloam regen` prints for human
review (preamble stamps, the manifest's gloam meta block, and the output
BOM's file hashes).  A real change always registers: generated-file
content shows its own diff, and a provenance pin advance moves the
unfiltered 6-space "commit" line — so a no-op bundle refresh leaves the
PR without example churn, while both spec-driven and generator-driven
output changes are committed alongside the bundle they came from.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Locked regeneration with the current gloam: the only substantive change
is the C preamble's stamp catching up from the 0.5.1-era "Generated by"
wording to "@generated by"; everything else is version/commit stamp
churn.  The recorded command lines are preserved verbatim.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
README gains `rust` and `regen` entries in the command reference, a
"Rust output (experimental)" section showing the generated crate's shape
and load API, and a "Regenerating a tree with gloam regen" section under
the provenance material.  docs/manifest.md points at `gloam regen` as
the automated form of the --lock replay it describes.  CONTRIBUTING's
pipeline sketch and module map pick up generator/rust/ and regen.rs, and
CLAUDE.md's design philosophy no longer claims there is no active non-C
backend.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Signed-off-by: Steven Noonan <steven@uplinklabs.net>
…eturns

The registries express parameter/return types as fragments of C
declarator syntax. gloam previously carried them as opaque text, and the
Rust backend translated them by string-munging (counting '*'s, applying
one const to every pointer level). Parse them instead into a TypeRef IR
via the closed declarator grammar (const/struct qualifiers, multi-word
scalar bases, per-level pointer constness, array dims, bitfield widths):

- parse/ctype.rs: tokenizer + parser + grammar unit tests. Anything
  outside the grammar is a hard generation error naming the offending
  declarator, and a corpus test parses every command param/return across
  all bundled XMLs (>10k declarators), so a registry update introducing
  a new construct fails loudly at test time instead of flowing through
  as text a non-C backend would mistranslate.
- resolve: Param.ty / Command.return_ty carry the typed view
  (serde-skipped; the C templates keep rendering raw text, so C output
  is byte-identical — all goldens unchanged).
- generator/rust: print types from TypeRef with per-level pointer
  mutability. This corrects `const T **` parameters (glShaderSourceARB,
  the IBM *PointerListIBM family) from `*const *const T` to
  `*mut *const T`. ABI-identical; the rust golden configs do not select
  those extensions, so rust goldens are also unchanged, and the printer
  behavior is pinned by unit tests instead. Also drops the module doc's
  reference to the removed docs/rust-backend.md.

Deliberately not marked as a generated-API break: the Rust backend is
experimental and the affected signatures were semantically wrong.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Picks up the TypeRef-based Rust type printer: `const T **` parameters in
examples/rust/gl-triangle (glShaderSourceARB, the IBM *PointerListIBM
family) correct from `*const *const T` to `*mut *const T`. The C trees
change only in generation stamps.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Keep the structured view of every <type> body alongside the assembled C
text, instead of discarding it after synthesis:

- struct/union: per-member records (raw C fragment, XML-declared name,
  TypeRef declarator, values= attribute for sType defaults), built in the
  same walk that synthesizes the C text, so the two views cannot drift
- funcpointer: return + parameter signature, from the structured
  <proto>/<param> format and from old inline-text typedefs alike
- typedefs: one arm per preprocessor branch (GLhandleARB's #ifdef
  __APPLE__ split, the synthesized macOS ptrdiff_t guard), plain
  typedefs as a single unconditional arm — detected by text shape since
  the GL-family registries carry no category attribute
- Vulkan handles: dispatchable vs non-dispatchable

Bodies outside the known shapes degrade to Opaque rather than erroring;
a corpus test pins full structured coverage for every struct/union and
funcpointer across all bundled XMLs, so a registry update that falls
outside the grammar fails with a listing. Spot checks pin the cases
hand-maintained type tables get wrong (GLhandleARB is void* on Apple,
unsigned int elsewhere — the Rust backend's hardcoded table says u32
unconditionally; RT-3 fixes that by printing from these payloads).

resolve::TypeDef carries the payload (serde-skipped): the C templates
still render raw_c verbatim, so C output is byte-identical — all goldens
unchanged, gloam-pregen regen audit clean (stamp-only churn).

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Replace the Rust backend's hand-maintained BASE_TYPES table with
emission derived from the structured TypePayloads (RT-2): type aliases
print from typedef arms, callbacks from funcpointer signatures, and
forward-declared C structs synthesize deduplicated zero-sized opaque
types. The only hand-written layer left is the ABI scalar map
(khronos_*/fixed-width C spellings -> Rust), which encodes platform-ABI
facts no XML records; an unknown scalar or preprocessor condition is now
a hard generation error instead of a silently wrong hand-copied alias.

Rust output changes (goldens re-blessed, diff reviewed):
- GLhandleARB is now cfg-split: *mut c_void on target_vendor="apple",
  u32 elsewhere — matching gl.xml's #ifdef __APPLE__ arms. The old
  hardcoded u32 was wrong on Apple platforms.
- The macOS ptrdiff_t guard arms collapse to a single isize alias
  (identical Rust mapping), as before.
- Aliases follow registry order; GLDEBUGPROCARB/KHR are full signatures
  instead of aliases (identical types); GLclampx is now emitted (the
  hand table had silently omitted it).

Generated crate verified to compile (cargo check, merged gl+gles2
config). C output untouched — all C goldens byte-identical.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Picks up the derived GL base types in examples/rust/gl-triangle:
registry-ordered aliases, the GLhandleARB apple/other cfg split, full
DEBUGPROC signatures, and grouped opaque struct types. The C trees
change only in generation stamps.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
New generator/rust/vk_types.rs prints the resolved Vulkan type surface
as Rust from the structured payloads and enum groups:

- API constants from flat enums via a closed literal grammar ((~0U),
  (~0ULL), floats, extension NAME strings as &CStr) — unknown forms are
  hard errors
- enum groups: non-bitmask groups become #[repr(transparent)] newtypes
  over i32 with free constants (unknown driver values are data, never
  Rust-enum UB); bitmask *FlagBits groups alias their *Flags carrier so
  constants combine with | and assign to fields exactly as in C. The
  bitmask distinction now flows from <enums type="bitmask"> through
  RawEnumGroup/EnumGroup (was parsed away; C-output-neutral, goldens
  unchanged)
- handles: dispatchable = newtype over *mut c_void, non-dispatchable =
  newtype over u64 (the one shape that is ABI-correct on every target)
- structs/unions: #[repr(C)] from member records; consecutive bitfield
  members merge into a u32 carrier field with shift/mask accessor
  methods (the acceleration-structure instance structs)
- funcpointers as Option<unsafe extern "system" fn>, platform protects
  as #[cfg(feature)] guards, #define aliases as type aliases
- a platform-handle table declares the ABI shape of every WSI type that
  C gets from platform headers (windows.h, Xlib, xcb, wayland, Metal,
  OpenHarmony, NvSci, ...); only referenced names are emitted

Corpus test: every type in bundled vk.xml either emits or names a
StdVideo* dependency (vk_video header types are a documented hard error
until they get structured parsing). Spot checks pin handles, sType
shapes, unions, bitfield packing math, and platform collection.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
The dependency-following pass collected prerequisite extension names into
a HashSet and pushed them into the selection in its iteration order —
random per process. Selection order determines extArray indices and
(via consumer signatures) command ordering, so any build whose
--extensions list pulls in dependencies (e.g. VK_KHR_acceleration_structure
pulling VK_KHR_maintenance3 and VK_KHR_device_group) generated differently
from run to run, violating the deterministic-output guarantee.

Latent until now: no golden or pregen config selects a dependency-pulling
extension list, which is also why all existing goldens are unchanged by
the fix. Surfaced by the new rust_vk_ext golden flaking between bless and
verify. IndexSet preserves discovery order.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Un-gate Spec::Vk in the Rust backend. generator/rust/vk_loader.rs emits
a crate mirroring the C backend's phased Vulkan loading contract:

- initialize(gipa): store vkGetInstanceProcAddr, load Global-scope
  commands (vkCreateInstance, vkEnumerateInstance*)
- load_instance(instance, api_version, enabled_extensions): feature
  flags from the requested version, extension flags from the enabled
  list (XXH3 + binary search against the pre-baked table, identical to
  C detection), Global+Instance-scope loading
- load_physical_device_extensions(...): pre-load Instance-scope queries
  of device extensions before a VkDevice exists (flags untouched)
- load_device(device, physical_device, enabled_extensions): resolve
  vkGetDeviceProcAddr, cap features at the device apiVersion
  (variant-masked), reload all scopes so Device-scope commands get the
  direct-dispatch fast path

Scope-aware loading consults a COMMAND_SCOPES table (the C backend's
kCommandScopes equivalent); null results never clobber loaded slots, so
phases are additive. Dispatch methods reuse the shared emit_method with
a Vulkan type mapper (parameter arrays decay as in C); platform-
protected commands and types carry #[cfg(feature = "VK_USE_PLATFORM_*")]
guards with the features declared in the generated Cargo.toml. A prelude
provides VK_MAKE_API_VERSION/VK_API_VERSION_* as const fns and the
PFN_vkGet*ProcAddr types.

New rust_vk_ext golden pins the full surface (including win32_surface
for the cfg paths); the generated crate compile-checks clean with no
features and with --all-features across a platform-rich extension set.
Vulkan constant grammar extended for (~1U)/(~2U) forms; struct members
named `type` sanitize to `type_`; StdVideo-referencing (video-codec)
extensions are a documented hard error pointing at --extensions.

GL output is untouched (all GL goldens byte-identical); --mx-global on
Vulkan is rejected for now.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
examples/rust/vk-info: headless smoke test of the gloam_vk crate's
phased loading contract. Bootstraps vkGetInstanceProcAddr via
libloading, then initialize -> CreateInstance -> load_instance ->
CreateDevice -> load_device, proving Device-scope dispatch by obtaining
a queue through the vkGetDeviceProcAddr fast path. Prints instance and
device versions, the loader's capped version, and feature/extension
flags. Exit codes 0/1/77 match the C example conventions.

Verified passing on an RTX 5090 (instance 1.4, device api 1.4): all
version features detected, all five enabled extensions flagged, queue
obtained.

The checked-in gloam/ crate regenerates via cargo xtask regen examples
(now 4 recorded trees).

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Stamp-only refresh across all four trees after the vk-info example
landed; content is identical.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Un-gate EGL in the Rust backend. The generated gloam_egl crate mirrors
the C backend's EGL contract: Egl::load_egl(display, loader) bootstraps
eglQueryString, parses EGL_VERSION, gates feature loading on the parsed
version, detects extensions from the client string (EGL_NO_DISPLAY)
plus the display string for a real display — tokenized and matched via
the same pre-baked XXH3 tables as C — then loads extension ranges and
resolves aliases. Passing EGL_NO_DISPLAY is a supported phase-0 (EGL
1.5 client semantics), which is what ANGLE platform selection needs
before any display exists.

Supporting changes:
- emit_base_types is spec-parameterized (newtype-skip list + platform
  table hook + command-signature sweep); EGL's native handle types and
  EGLint live in eglplatform.h, so they get per-target declarations
  mirroring its platform arms
- rust_type maps raw C scalar spellings through the ABI table: a bare C
  `char` passed through verbatim names RUST's 4-byte char and silently
  breaks the ABI (eglQueryString returns `const char *`;
  EGL_MESA_image_dma_buf_export passes `int *`)
- inline struct bodies in raw text (EGLClientPixmapHI) parse into
  member payloads and emit as #[repr(C)] structs
- `#define PFN...PROC` naming shims and category-less `#include`
  carriers are skipped: dispatch methods replace PFN typedefs
- EGL constants: EGL_CAST(T,V) forms emit as `V as T`; enum-shaped
  values as EGLenum (a plain u32 alias), 64-bit values as u64
- abi map additions: intptr_t/ssize_t/uintptr_t and the khronos
  nanosecond-timestamp types

New rust_egl golden pins the full-extension surface (the production
shape); the generated crate compile-checks clean. GL and Vulkan output
are unchanged (all goldens byte-identical).

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
examples/rust/egl-info: headless smoke test of the gloam_egl crate,
mirroring a real ANGLE application's startup. Phase-0
load_egl(EGL_NO_DISPLAY) detects the client extensions; with
EGL_ANGLE_platform_angle present, each ANGLE render backend (D3D11,
Vulkan, desktop OpenGL, OpenGL ES) is selected via
eglGetPlatformDisplay(EGL_PLATFORM_ANGLE_ANGLE, ...) with the
EGL_PLATFORM_ANGLE_TYPE_* attributes, initialized, re-loaded against
the live display (exercising display-extension detection), and
reported. Plain EGL implementations (Mesa) fall back to the default
display. libEGL comes from the EGL_LIBRARY env var or platform default
names; ANGLE binaries are never committed.

Verified against an ANGLE build on an RTX 5090: all four backends
initialize and report EGL 1.5 (vendor Google Inc./NVIDIA), exit PASS.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Stamp-only refresh across all five trees after the egl-info example
landed; content is identical.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Every feature set the Rust backend generates writes the same crate
paths (Cargo.toml, src/lib.rs), so a request like
--api gl:core,gles2,egl,vk resolved to several loaders and silently
kept only the last one — the earlier specs' crates were generated and
then overwritten, and the manifest recorded files that no longer
matched the tree. Hard-error with the resolved spec list instead. A
per-spec module layout (src/gl.rs, src/egl.rs, src/vk.rs under one
crate, mirroring the C backend's per-spec .c/.h files) is the intended
future shape.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
rust_backend_rejects_non_gl_specs used EGL as its unsupported-spec
probe, which the Rust backend now accepts (along with Vulkan) — probe
with WGL instead, which remains unsupported. Also cover the multi-spec
refusal: several resolved loaders in one invocation must fail rather
than clobber one crate's files.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
The Rust backend now generates one crate covering every resolved
feature set, with one module file per spec — src/gl.rs, src/egl.rs,
src/vk.rs — mirroring the C backend's per-spec .c/.h files in a shared
tree. lib.rs carries the crate-level attributes (#![no_std]) and module
declarations; Cargo.toml is emitted once, its crate name joining the
spec stems (gloam_gl_egl_vk) and its features the union of every
module's needs. This makes --api gl:core,gles2,egl,vk a single
dependency whose modules can feed each other (EGL's GetProcAddress into
the GL loader), exactly like the C tree.

Single-spec crates keep their names (gloam_gl) and re-export the module
at the root, so existing consumers compile unchanged — both examples
rebuild and pass without source edits. Multi-spec crates do not
re-export: loader types (each module's LoadError) collide.

The golden harness snapshots every file under src/ individually, and
the rust goldens re-blessed to the new layout (the module bodies are
the previous lib.rs contents minus the crate-level attribute). The
multi-spec refusal from the clobber fix is replaced by structure tests
for both layouts.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
Picks up the per-spec module layout in the three Rust example crates
(src/gl.rs / src/egl.rs / src/vk.rs plus a thin lib.rs re-exporting the
module); example sources are unchanged. C trees change only in stamps.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
rusqlite 0.40 requires it, and 1.95 is a few months old, so it seems an
okay version bump to accept.

Signed-off-by: Steven Noonan <steven@uplinklabs.net>
@tycho tycho added the release label Jul 29, 2026
@tycho tycho closed this Jul 29, 2026
@tycho
tycho deleted the release/2026-07-29T02-51-41Z branch July 29, 2026 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant