Skip to content

feat: Add prebuilt tree-sitter grammars for Semble - #1

Merged
Pringled merged 23 commits into
mainfrom
initial-code
Aug 1, 2026
Merged

Pringled merged 23 commits into
mainfrom
initial-code

Conversation

@Pringled

@Pringled Pringled commented Jul 17, 2026

Copy link
Copy Markdown
Member

This PR bundles 56 pinned tree-sitter grammars for macOS, Linux, and Windows so Semble can parse files without runtime downloads. It also:

  • Provides language and parser loading APIs
  • Extracts native libraries into a local cache safely
  • Builds and verifies platform-specific wheels
  • Records upstream sources, versions, and licenses in sources.json

Tested in Semble (MinishLab/semble@main...grammars) and it produces the exact same chunks on our benchmarks (while being a bit faster as well).

Pringled added 11 commits July 17, 2026 07:38
…lti-platform wheels

Replaces the template placeholders with a working package: a manifest-driven
tree-sitter loader (get_language/get_parser) that extracts a bundled,
platform-specific grammar archive into a local cache with no runtime network
access. tools/build_grammars.py compiles 45 grammars from pinned upstream
commits (with license/provenance tracking) for macOS, Linux x86_64/arm64,
and Windows (cross-compiled via mingw-w64); tools/build_wheels.py packages
one platform-tagged wheel per architecture. CI now builds grammars before
testing, and the release workflow builds real per-platform wheels.

Compiled grammar archives are intentionally not committed (see .gitignore):
builds aren't byte-reproducible, so tracking them would bloat history with a
fresh multi-MB binary diff on every language addition. They're rebuilt by CI
and by tools/build_grammars.py on demand.
… warnings

- tests/test_loader.py: cache-reuse test hardcoded .dylib, which would fail
  on Linux (.so) and Windows (.dll) CI runners; derive the filename from the
  platform manifest instead.
- pyproject.toml: tree-sitter>=0.21,<1.0 allowed versions whose Language()
  API is incompatible with the loader's single-arg PyCapsule call (verified
  0.21.3-0.23.0 fail, 0.23.1+ works); tightened to >=0.23.1,<0.27.
- pyproject.toml: package-data glob referenced _grammars/ and
  _grammars/licenses/ without declaring them as included packages, which
  setuptools warned about on every wheel build; fixed via packages.find
  with namespaces=true.
- setup.py: import setuptools' own integrated bdist_wheel command instead of
  the deprecated wheel.bdist_wheel; bump the setuptools floor to 70.1.
- Makefile: add missing lint/typecheck targets (make lint was failing).
- tools/: drop file-level module docstrings and "# --- batch N ---" section
  separators per house style.
- release.yaml: smoke-test each built wheel (tools/smoke_test.py) before
  publishing, instead of publishing straight after build.
- semble_grammars/ -> src/semble_grammars/: standard src-layout, avoids
  accidentally importing from the working directory instead of the
  installed package.
- _cache.py -> cache.py, _loader.py -> loader.py, _platform.py -> platform.py:
  drop the leading-underscore "private module" convention in favor of plain
  names. platform.py's own `import platform` still resolves to the stdlib
  module (Python 3 imports are absolute-only), verified working.
- Updated all imports, tool script paths (tools/build_grammars.py,
  tools/build_wheels.py), pyproject.toml packaging config
  (package-dir/packages.find), Makefile, pre-commit mypy args, .gitignore,
  and README references accordingly.
…ader

Language additions (45 -> 56), per repo-frequency-vs-build-cost review:
sql, csharp, jinja2, json5, powershell, scss, astro, starlark, gotmpl, rst,
groovy, plus terraform as an alias for hcl (same syntax, no separate
grammar). GrammarSpec gained symbol_override (csharp's compiled symbol is
tree_sitter_c_sharp, not tree_sitter_csharp) and generate=True, which runs
`tree-sitter generate` for grammars whose repo doesn't commit a pre-built
parser.c (needed for sql).

Docker cross-build image moved from debian:bookworm-slim to
debian:trixie-slim: bookworm's glibc (2.36) is too old for the tree-sitter
CLI's prebuilt binary (needs 2.39+). Verified this doesn't affect the
manylinux2014 wheel tag's promise - the compiled grammars themselves only
require up to GLIBC_2.14, checked via objdump -T on the built .so files.

loader.py: keep every dlopen'd library's CDLL handle alive in a module-level
dict alongside the Language built from it, and raise a clear
GrammarLoadError instead of proceeding with a null/invalid pointer on a
missing symbol or failed load. ctypes.CDLL has no __del__ and empirically
doesn't unload on GC, so this wasn't an active bug, but it's cheap to stop
relying on that being ctypes' behavior forever.
…mpile

The first real CI run (PR #1) failed all 5 windows-latest jobs:
clang: error: unsupported option '-fPIC' for target 'x86_64-pc-windows-msvc'

Plain `clang` on Windows targets MSVC by default, which rejects the
GNU-style flags this build relies on and doesn't export symbols the way
ctypes expects without extra __declspec annotations we can't add to
third-party grammar sources. Switched build_native() to use MinGW-w64 gcc
on Windows specifically - the same compiler already proven to work via
build_windows_cross()'s cross-compilation path - and added a
`choco install mingw` step to both ci.yaml and release.yaml's Windows jobs.

macOS/Linux builds are unaffected (still plain clang).
…rgv[0]

Second CI failure after the mingw fix: all 45 pre-existing grammars now
compiled fine on windows-latest, but sql's `tree-sitter generate` step hit
FileNotFoundError (WinError 2). npm's global install puts a .cmd shim on
Windows, and subprocess/CreateProcess won't resolve that extension from a
bare "tree-sitter" argv[0] the way a shell would. Use shutil.which() to
resolve the actual executable path, same pattern already used for the
pre-flight existence check.
…indows rename race

macos-latest/3.10: tree-sitter generate hit the 60s CLONE_TIMEOUT_SECONDS
(reused from the git-fetch timeout) under runner load. Gave it its own
longer budget (GENERATE_TIMEOUT_SECONDS = 180) instead of sharing one meant
for a shallow git fetch.

windows-latest/3.11 and /3.14: test_extract_atomic_survives_concurrent_first_use
failed with PermissionError('Access is denied') from os.replace. Unlike
POSIX, Windows enforces mandatory file locking, so multiple threads racing
os.replace(tmp, dest_path) onto the same destination can transiently deny
all but the winner. windows-latest/3.10 and /3.12 passed in the same run,
confirming this is a race, not a broken build. extract_atomic now treats a
failed replace as non-fatal if dest_path exists afterward - some other
caller's replace already produced an equally valid file.

windows-latest/3.10 and /3.12 already passing in this same run confirms the
mingw compiler fix and tree-sitter CLI path fix (previous two commits) are
both correct - these are new, narrower issues the first clean build exposed.
@Pringled Pringled changed the title Scaffold semble-grammars: local grammar loader, 56-language build, multi-platform wheels feat: Add prebuilt tree-sitter grammars for Semble Jul 17, 2026
Pringled added 3 commits July 17, 2026 12:06
…RM64 builds

get_parser() enforces tree-sitter's ABI compatibility check, which
get_language() defers; 25 of 56 bundled grammars are ABI 15 and fail
under tree-sitter<0.25 with "Incompatible Language version". Raise
the lower bound accordingly and switch the all-language test to
get_parser() so this regresses loudly.

Also pin tree-sitter-cli to a known-good version across all build/CI
paths for reproducible codegen, and register QEMU before the Linux
release job's ARM64 Docker cross-build (previously unregistered,
likely to fail on GitHub-hosted x86_64 runners).
README and the missing-CLI error message still told developers to
install tree-sitter-cli unpinned; align with the CI/release pins.
@stephantul
stephantul self-requested a review July 20, 2026 08:39
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

The remaining Linux compatibility issue should be fixed before merging.

  • Linux libraries are compiled on Debian trixie but published with manylinux2014 tags.
  • The manylinux smoke test loads only one bundled grammar, so it does not validate every native library.

scripts/build_grammars.py and .github/workflows/release.yaml

Reviews (2): Last reviewed commit: "Fix release platform compatibility [skip..." | Re-trigger Greptile

Comment thread scripts/build_grammars.py
Comment thread scripts/build_grammars.py Outdated
Pringled added 6 commits July 21, 2026 10:09
Fills gaps in mainstream config/prose/language coverage (70 -> 75
bundled grammars). Also accept LICENCE (British spelling) when
collecting upstream license files, needed by one of the new sources.
Fix platform list ordering and drop the redundant Development section.
ci.yaml previously only fully built+tested linux-x86_64 and
windows-x86_64, with macos-arm64 build-only and path-filtered to
grammar-touching PRs. linux-arm64, windows-arm64, and macos-x86_64 had
zero coverage until the release workflow itself ran. Since the release
workflow's build jobs mirror what's added here, extend the same
patterns into ci.yaml so every platform is exercised on every push,
not just at release time. Folds macos-build.yaml's job into ci.yaml
(now unconditional and tested) and removes the now-redundant file.
- Regenerate THIRD_PARTY_NOTICES.md: it had gone stale (72/75 rows,
  missing fortran/objc/perl) after an earlier local experiment
  rebuilt-then-reverted sources.json without re-syncing this
  generated file.
- Track uv.lock instead of ignoring it, so the dev/CI toolchain
  resolves reproducibly. Unpinned dev deps let `uv sync` pick up an
  incompatible pydoclint/docstring_parser_fork pairing (or a
  corrupted cache entry) that broke `make lint` locally while the
  isolated pre-commit hook, pinned separately, stayed green.
- Extend the release smoke test to load every bundled language via
  get_language(), not just python. Verified empirically: all 75
  linux-x86_64 libraries built in debian:trixie-slim actually load
  fine under a real manylinux2014 (glibc 2.17) container today, but
  nothing was checking that beyond the python grammar, so a future
  regression could ship undetected.
windows-arm64 was silently building x86_64 DLLs (the mingw gcc fallback is
x86_64-only), which then failed to load into the ARM64 Python interpreter
with WinError 193. Use cl via ilammy/msvc-dev-cmd for both Windows
architectures, with an explicit VSCMD_ARG_TGT_ARCH preflight check so a
target/environment mismatch fails fast instead of producing a wrong-arch
binary.
QEMU-emulated ARM64 clang compiling generated parser C files at -O2 is
extremely slow and timed out tree-sitter generate for the sql grammar.
ubuntu-24.04-arm gives native ARM64 execution for both the grammar build
and the manylinux2014_aarch64 smoke test, so the docker --platform flags
now match the host instead of triggering emulation.
@Pringled
Pringled merged commit d983abd into main Aug 1, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants