From f5a789a4c017a652e6f6bbbfc61a7cd1e8fa6783 Mon Sep 17 00:00:00 2001 From: PerishCode Date: Thu, 4 Jun 2026 22:10:46 +0800 Subject: [PATCH] scripts: wrap antlr validation with runseal --- .runseal/wrappers/antlr | 3 + AGENTS.md | 22 +++-- .../dev/antlr.Dockerfile => Dockerfile.antlr4 | 0 README.md | 5 +- grammars/AGENTS.md | 6 +- scripts/{dev => cli}/antlr.py | 99 +++++++++++-------- scripts/init.py | 7 +- 7 files changed, 87 insertions(+), 55 deletions(-) create mode 100755 .runseal/wrappers/antlr rename scripts/dev/antlr.Dockerfile => Dockerfile.antlr4 (100%) rename scripts/{dev => cli}/antlr.py (79%) diff --git a/.runseal/wrappers/antlr b/.runseal/wrappers/antlr new file mode 100755 index 0000000..34aa12b --- /dev/null +++ b/.runseal/wrappers/antlr @@ -0,0 +1,3 @@ +#!/usr/bin/env sh +set -eu +exec "$(dirname -- "$0")/../lib/python-module" "$@" diff --git a/AGENTS.md b/AGENTS.md index 60d4035..d4ff709 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,13 +18,13 @@ management. scripts there. - `runseal.toml` and `.runseal/wrappers/` are the repo-local operator entrypoints for support tasks that do not belong in the installable `flavor` product - binary. Current support commands are `runseal :cloudflare`, `runseal :pr`, - and `runseal :release`. + binary. Current support commands are `runseal :antlr`, `runseal :cloudflare`, + `runseal :pr`, and `runseal :release`. - `grammars/` contains the repo-visible `.g4` grammar source of truth plus `metadata.json` contract metadata. Parser backends, facts, diagnostics, and harnesses should align to these files. -- `scripts/` contains both developer helpers (`scripts/dev/`) and the - repo-local uv-managed Python support command tree used by runseal wrappers. +- `scripts/` contains the repo-local uv-managed Python support command tree + used by runseal wrappers. - `.local/` is repo-local private operator state (for example Cloudflare secrets). It must stay gitignored and must not become a source of truth for product behavior. @@ -86,7 +86,8 @@ cargo fmt --all --check cargo clippy --locked --workspace --all-targets -- -D warnings cargo test --locked --workspace cargo run --locked -p flavor-cli -- check --root . --config flavor.toml -python3 scripts/dev/antlr.py check +runseal :antlr init +runseal :antlr check runseal :pr --help ``` @@ -94,9 +95,10 @@ runseal :pr --help `runseal` so repo-local support commands have one entrypoint shape. Use `--force` only when intentionally replacing existing non-init hooks; the script backs them up first. -`python3 scripts/dev/antlr.py check` is an optional Dockerized ANTLR validation -helper. It lazily builds its Docker image when needed, checks `.g4` files under -`grammars/` in ANTLR dependency mode, and does not generate Java artifacts. +`runseal :antlr init` builds the pinned local ANTLR Docker image. +`runseal :antlr check` is an optional Dockerized ANTLR validation helper. It +requires the image prepared by init, checks `.g4` files under `grammars/` in +ANTLR dependency mode, and does not generate Java artifacts. ## Standard Workflow @@ -233,8 +235,8 @@ and `manage.ps1`. Release and smoke scripts should reference those root files. ### Where Do Workflow Helper Scripts Go? Workflow-only helpers belong under `.github/scripts/`. The repository -initialization entrypoint is `scripts/init.py`; additional local developer -harness helpers, if added, belong under `scripts/dev/`. +initialization entrypoint is `scripts/init.py`; additional local support +commands, if added, should use runseal wrappers plus `scripts/cli/`. ### Does Init Replace Existing Hooks? diff --git a/scripts/dev/antlr.Dockerfile b/Dockerfile.antlr4 similarity index 100% rename from scripts/dev/antlr.Dockerfile rename to Dockerfile.antlr4 diff --git a/README.md b/README.md index 2e82bb0..aed165a 100644 --- a/README.md +++ b/README.md @@ -189,13 +189,16 @@ cargo fmt --all --check cargo clippy --locked --workspace --all-targets -- -D warnings cargo test --locked --workspace cargo run --locked -p flavor-cli -- check --root . --config flavor.toml -python3 scripts/dev/antlr.py check # optional Dockerized G4 validation +runseal :antlr init # build the local ANTLR image once +runseal :antlr check # optional Dockerized G4 validation ``` Repo-local operator commands use runseal wrappers rather than the installable `flavor` binary: ```bash +runseal :antlr init [options] +runseal :antlr check [grammars/.../*.g4] runseal :cloudflare [args] runseal :pr [options] runseal :release --channel=stable|beta [options] diff --git a/grammars/AGENTS.md b/grammars/AGENTS.md index e8c6cd7..1593c44 100644 --- a/grammars/AGENTS.md +++ b/grammars/AGENTS.md @@ -19,14 +19,16 @@ files for plugin-facing grammar contracts. ```bash cargo test --locked -p flavor-grammar -python3 scripts/dev/antlr.py check +runseal :antlr init +runseal :antlr check ``` ## Standard Workflow - Define or update grammar shape in `.g4`; keep metadata focused on owner, entrypoint, facts, diagnostics, span mapping, recovery, and backend bindings. -- Use `scripts/dev/antlr.py check` as the Dockerized ANTLR helper. It accepts +- Use `runseal :antlr init` to build the pinned local ANTLR Docker image, then + `runseal :antlr check` as the Dockerized ANTLR helper. The check accepts optional `.g4` files under `grammars/`, groups them by path relative to `grammars/`, and runs ANTLR in dependency mode without Java artifact output. - Harnesses should prove conformance to this contract rather than merely diff --git a/scripts/dev/antlr.py b/scripts/cli/antlr.py similarity index 79% rename from scripts/dev/antlr.py rename to scripts/cli/antlr.py index 4f2d197..d205a15 100644 --- a/scripts/dev/antlr.py +++ b/scripts/cli/antlr.py @@ -22,22 +22,46 @@ def main() -> int: image = args.image or f"flavor-antlr:{args.antlr_version}" require_docker() - ensure_image( - root, - image, - antlr_version=args.antlr_version, - antlr_sha256=args.antlr_sha256, - rebuild=args.rebuild_image, - require_image=args.require_image, - ) - + if args.command == "init": + ensure_image( + root, + image, + antlr_version=args.antlr_version, + antlr_sha256=args.antlr_sha256, + rebuild=args.rebuild_image, + ) + print(f"antlr Docker image ready: {image}") + return 0 + + require_image(image, args.antlr_version) return check_grammars(root, image, args.g4_files) def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) + parser = argparse.ArgumentParser(prog="runseal :antlr", description=__doc__) subparsers = parser.add_subparsers(dest="command", required=True) + init = subparsers.add_parser( + "init", + help="build the local ANTLR Docker image", + description=( + "Build the local ANTLR Docker image used by `runseal :antlr check`. " + "The image downloads a pinned ANTLR complete jar and verifies its " + "SHA256 digest during build." + ), + ) + add_image_args(init) + init.add_argument( + "--antlr-sha256", + default=os.environ.get("ANTLR_SHA256", DEFAULT_ANTLR_SHA256), + help="expected SHA256 of the ANTLR complete jar", + ) + init.add_argument( + "--rebuild-image", + action="store_true", + help="rebuild the Docker image even if it already exists", + ) + check = subparsers.add_parser( "check", help="validate repo grammar files", @@ -53,36 +77,22 @@ def parse_args(argv: list[str]) -> argparse.Namespace: nargs="*", help="optional .g4 files under grammars/", ) - check.add_argument( + add_image_args(check) + + return parser.parse_args(argv) + + +def add_image_args(parser: argparse.ArgumentParser) -> None: + parser.add_argument( "--antlr-version", default=os.environ.get("ANTLR_VERSION", DEFAULT_ANTLR_VERSION), - help=f"ANTLR version to download into the Docker image (default: {DEFAULT_ANTLR_VERSION})", + help=f"ANTLR version for the Docker image tag (default: {DEFAULT_ANTLR_VERSION})", ) - check.add_argument( - "--antlr-sha256", - default=os.environ.get("ANTLR_SHA256", DEFAULT_ANTLR_SHA256), - help="expected SHA256 of the ANTLR complete jar", - ) - check.add_argument( + parser.add_argument( "--image", default=os.environ.get("ANTLR_IMAGE"), - help="Docker image tag to build/use (default: flavor-antlr:)", + help="Docker image tag to use (default: flavor-antlr:)", ) - check.add_argument( - "--rebuild-image", - action="store_true", - help="rebuild the Docker image even if it already exists", - ) - check.add_argument( - "--require-image", - action="store_true", - help="do not build the Docker image; fail if it is missing", - ) - - args = parser.parse_args(argv) - if args.rebuild_image and args.require_image: - raise SystemExit("--rebuild-image and --require-image cannot be combined") - return args def repo_root() -> Path: @@ -117,15 +127,25 @@ def ensure_image( antlr_version: str, antlr_sha256: str, rebuild: bool, - require_image: bool, ) -> None: if not rebuild and image_exists(image): return - if require_image: - raise SystemExit(f"Docker image {image!r} is missing") build_image(root, image, antlr_version, antlr_sha256) +def require_image(image: str, antlr_version: str) -> None: + if image_exists(image): + return + command = ["runseal :antlr init"] + if antlr_version != DEFAULT_ANTLR_VERSION: + command.append(f"--antlr-version {antlr_version}") + if image != f"flavor-antlr:{antlr_version}": + command.append(f"--image {image}") + raise SystemExit( + f"Docker image {image!r} is missing; run `{' '.join(command)}` before `runseal :antlr check`" + ) + + def image_exists(image: str) -> bool: result = subprocess.run( ["docker", "image", "inspect", image], @@ -136,8 +156,7 @@ def image_exists(image: str) -> bool: def build_image(root: Path, image: str, antlr_version: str, antlr_sha256: str) -> None: - dockerfile = root / "scripts" / "dev" / "antlr.Dockerfile" - context_dir = root / "scripts" / "dev" + dockerfile = root / "Dockerfile.antlr4" run_checked( [ "docker", @@ -150,7 +169,7 @@ def build_image(root: Path, image: str, antlr_version: str, antlr_sha256: str) - image, "-f", str(dockerfile), - str(context_dir), + str(root), ], label=f"building {image}", attempts=3, diff --git a/scripts/init.py b/scripts/init.py index 6a852af..d380736 100644 --- a/scripts/init.py +++ b/scripts/init.py @@ -37,6 +37,7 @@ "manage.ps1", "runseal.toml", ".runseal/lib/python-module", + ".runseal/wrappers/antlr", ".runseal/wrappers/cloudflare", ".runseal/wrappers/pr", ".runseal/wrappers/release", @@ -49,7 +50,8 @@ "scripts/cli/cloudflare.py", "scripts/cli/pr.py", "scripts/cli/release.py", - "scripts/dev/antlr.py", + "Dockerfile.antlr4", + "scripts/cli/antlr.py", "scripts/init.py", ) @@ -70,6 +72,7 @@ echo "==> shell syntax" sh -n .runseal/lib/python-module +sh -n .runseal/wrappers/antlr sh -n .runseal/wrappers/cloudflare sh -n .runseal/wrappers/pr sh -n .runseal/wrappers/release @@ -79,7 +82,7 @@ echo "==> python syntax" python3 -m py_compile scripts/init.py -python3 -m py_compile scripts/dev/antlr.py +python3 -m py_compile scripts/cli/antlr.py python3 -m py_compile scripts/cli/cloudflare.py python3 -m py_compile scripts/cli/pr.py python3 -m py_compile scripts/cli/release.py