How to cut a new release of no.sikt:graphitron-tree-sitter-natives, when
to bump which version digit, and what the release workflow verifies.
This is internal-to-the-rewrite operational documentation, not a user-facing chapter. The published artifact is consumed only by graphitron-lsp; consumers of graphitron’s generator surface never see it directly.
What the artifact is
A single jar that ships two per-platform tree-sitter libraries under
lib/<os>-<arch>/: the GraphQL grammar and the tree-sitter runtime.
| Platform | Grammar | Runtime |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
macOS is Apple-silicon-only. Sikt graphitron-lsp developers all run M1
or newer, the macos-13 (Intel) GitHub-hosted runner is the slowest queue
on the platform, and graphitron-lsp is internal-only per the spec’s
"Out of scope" list. Adding macos-x86_64 back means re-enabling that
matrix entry and bumping the jar-layout assertion expectations.
The grammar binary is the vendored bkegley tree-sitter-graphql grammar
(commit 5e66e96), built with the upstream tree-sitter build CLI against
the pinned parser ABI. It exports the grammar’s tree_sitter_graphql entry
point only, imports no ts_* symbols, and so loads independently of the
runtime.
The runtime binary is the upstream tree-sitter runtime
(libtree-sitter, exporting the ts_* symbols) built from the pinned
source tag, not the system package. graphitron-lsp’s BundledLibraryLookup
extracts both binaries from this jar and composes grammar.or(runtime) into
jtreesitter’s SPI lookup, so the LSP has no native system dependency at all.
Bundling the runtime reverses the module’s original grammar-only split. That
split made the runtime a system dependency (brew install tree-sitter,
pacman -S, vcpkg install, or a from-source build on Debian/Ubuntu, whose
apt libtree-sitter0 is 0.20.x and too old). The recurring cost of that
install path, especially on Windows and Debian/Ubuntu, is what R401 retired
by bundling the runtime; the tree-sitter project still does not publish a
prebuilt libtree-sitter asset, so we build it ourselves on native runners
from the pinned source (see "Cutting a release" below).
The grammar build uses the upstream tree-sitter build CLI rather than a
hand-rolled cc / cl.exe invocation. The CLI handles MSVC export
annotations on Windows uniformly, which is what gives us a working Windows
DLL with the grammar entry point correctly exported (the bonede route
fails this on Windows, exporting Java_org_treesitter_* JNI wrappers
only). The runtime build does not use the CLI: it builds libtree-sitter
from the cloned source lib/ (POSIX make; Windows MinGW-w64 make with
static-linked gcc runtime), with a transitive-dep allowlist enforced as a
release gate.
Versioning
Format: <runtime-version>-<build-n>. First bundled-runtime release:
0.26.9-1. The <runtime-version> portion is the actual tree-sitter
runtime version shipped in the jar: the upstream source tag both the grammar
ABI and the bundled libtree-sitter are built from. It must match the
tree-sitter-cli-version workflow input (tag v0.26.9 for 0.26.9-1).
Before 0.26.9-1, <runtime-version> meant the parser ABI the grammar was
built against and the runtime was a system dependency; from 0.26.9-1 it
names the runtime we ship.
| Change | Version bump |
|---|---|
Grammar update (new |
|
Build-flag change |
|
Recompile / security rebuild with no source change |
|
Retarget to a newer upstream tree-sitter tag |
|
Snapshots are not used. The natives module ships releases only, and a release that turns out to be broken on one platform after deploy stays on Central as a tombstone; graphitron-lsp pins to the working build that follows.
Cutting a release
Three steps, the second being the GitHub Actions trigger.
-
Bump the version in
graphitron-tree-sitter-natives/pom.xml. If the parser ABI moved, also update the matching pin insrc/main/native/grammars/graphql/UPSTREAM.mdand the module-levelUPSTREAM.md. Commit on a normal feature branch and push. -
Trigger the workflow. In the GitHub Actions UI for
sikt-no/graphitron, run.github/workflows/tree-sitter-natives-release.ymlviaworkflow_dispatch. The single input is the upstreamtree-sittertag (e.g.v0.26.9); it pins both the CLI that builds the grammar and the source checkout the bundled runtime is built from, and must match the<runtime-version>portion of the pom version. -
Watch the three stages. The workflow only deploys if every stage succeeds:
-
Build matrix (four native runners). Each runner builds two binaries. The grammar: download the pinned
tree-sitterCLI, runtree-sitter buildagainst the vendored grammar, assert it exportstree_sitter_graphql. The runtime: clone the pinned source tag and buildlibtree-sitterfromlib/(POSIXmake; Windows MinGW-w64makewith-static -static-libgcc), then run the release gates: it must exportts_language_abi_versionand the corets_parser_*symbols, and its transitive deps must fall within the per-platform allowlist (ldd/otool -L/dumpbin /dependents; a straylibgcc_s_seh-1.dllor third-party.sofails the release). Symbol checks usenm -gUon POSIX anddumpbin /exportson Windows. Both binaries upload asnative-<platform>. -
Package + deploy (ubuntu-latest). Downloads all four artifacts, stages grammar + runtime under
target/classes/lib/<os>-<arch>/, runsmvn package, and asserts the produced jar contains exactly the eightlib/<os>-<arch>/…entries (four grammar + four runtime) and no others. Thenmvn deploysigns with GPG and publishes viacentral-publishing-maven-pluginwithautoPublish=true. -
Post-deploy load+parse matrix (four native runners). After deploy succeeds, each runner resolves the freshly-published jar from a clean local m2 (with retries against Central’s propagation delay), then runs a tiny FFM-based Java verifier with no system
libtree-sitterinstalled. The verifier extracts both the grammar and the runtime from the jar, composesgrammar.or(runtime), callsLanguage.load(symbols, "tree_sitter_graphql"), parses"type Query { hello: String }", and asserts the root issource_filewith no errors. A pass with no system runtime present is the proof that the jar is self-sufficient on that platform.
-
If stage 3 fails on any platform, bump <build-n> and re-release. The
failing version stays on Central; downstream graphitron-lsp pins to
the next working <build-n>.
Why the build infrastructure looks the way it does
Three design choices worth knowing about for anyone maintaining this:
-
Standalone pom, no parent inheritance. The natives module lives in the repo root alongside the reactor modules but its
pom.xmldoes not declare<parent>graphitron-rewrite-parent</parent>. Trunk’s parent runs at10-SNAPSHOTand declares no<snapshotRepository>; inheriting would publish a natives jar whose parent reference is unresolvable for any Central consumer. The release plumbing (maven-gpg-plugin,central-publishing-maven-plugin,maven-source-plugin,maven-javadoc-plugin,maven-deploy-plugin) is inlined into the module’s pom for the same reason. The parent’s<modules>list also does not include this module, so the regular reactor build (mvn install -Plocal-db) pays zero cost for it. -
No CI build per PR. The binaries don’t change per PR (they’re a published artifact, not a per-build output), so expanding
.github/workflows/rewrite-build.ymlto a four-platform matrix would mostly re-verify a stable input. Instead, the verification matrix lives in this workflow and runs against the actual published artifact at release time. In normal CI, only thelinux-x86_64arm ofNativeLibraryBundleTestexecutes per PR (the three others are gated by@EnabledOnOsto their respective hosts and run on this workflow’s post-deploy matrix). -
Built with the upstream CLI, not hand-rolled compiler invocations.
tree-sitter buildknows how to do MSVC_declspec(dllexport)for Windows, which the upstream tree-sitter C sources don’t carry on their own. The bonedetree-sitter-graphqljar we previously evaluated ships a Windows DLL that exportsJava_org_treesitter*JNI wrappers but not the baretree_sitter_graphqlentry point jtreesitter expects, which is why the natives module exists at all.
Where the binaries live before deploy
Not committed. The eight binaries (four grammar + four runtime) are produced
fresh on four native runners per release and merged into the jar during the
package+deploy stage. The vendored grammar sources under
graphitron-tree-sitter-natives/src/main/native/grammars/graphql/
are the grammar’s only git-resident input; the runtime is built from the
pinned upstream source tag cloned at release time, not vendored in git. Its
provenance (source tag, per-platform build invocation and flags, the
transitive-dep allowlist, and the update procedure) is recorded in the
module-level UPSTREAM.md "Runtime provenance" section.