Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/actions/binding-tools/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Prepare binding tools
description: Select targets, install the exact compilers and prerequisites, and build the shared generator
inputs:
targets:
description: Comma-separated package-key:go, package-key:python, or package-key:typescript targets, or all
description: Comma-separated package-key:go, package-key:python, package-key:typescript, or package-key:rust targets, or all
required: true
publication:
description: Include the committed package dependency closure for publication
Expand All @@ -23,6 +23,12 @@ outputs:
has-typescript:
description: Whether a TypeScript target is selected
value: ${{ steps.configure.outputs.has-typescript }}
rust-version:
description: Exact catalog Rust version, empty when no Rust target is selected
value: ${{ steps.configure.outputs.rust-version }}
has-rust:
description: Whether a Rust target is selected
value: ${{ steps.configure.outputs.has-rust }}
source-commit:
description: Checked-out source commit
value: ${{ steps.configure.outputs.source-commit }}
Expand Down Expand Up @@ -60,6 +66,7 @@ runs:
python tooling/extension-bindings/ci/generate-go.py configure --targets "$INPUT_TARGETS"
python tooling/extension-bindings/ci/generate-python.py configure --targets "$INPUT_TARGETS"
python tooling/extension-bindings/ci/generate-typescript.py configure --targets "$INPUT_TARGETS"
python tooling/extension-bindings/ci/generate-rust.py configure --targets "$INPUT_TARGETS"
fi
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
Expand Down Expand Up @@ -129,6 +136,15 @@ runs:
shell: bash
working-directory: tooling/extension-bindings/emitters/typescript
run: npm ci --ignore-scripts && npm run build
- name: Install the pinned Rust toolchain
if: steps.configure.outputs.has-rust == 'true'
shell: bash
env:
RUST_VERSION: ${{ steps.configure.outputs.rust-version }}
run: |
set -euo pipefail
rustup toolchain install "$RUST_VERSION" --profile minimal --component rustfmt
rustup default "$RUST_VERSION"
- name: Build shared generator
shell: bash
run: go -C tooling/extension-bindings/orchestrator build -trimpath -buildvcs=false -o "$RUNNER_TEMP/binding-tools/rc" ./cmd/rc
14 changes: 11 additions & 3 deletions .github/workflows/binding-generate.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Generate and commit Go, Python and TypeScript bindings
name: Generate and commit Go, Python, TypeScript and Rust bindings

on:
push:
Expand All @@ -11,19 +11,21 @@ on:
- 'bindings/**/go/**'
- 'bindings/**/python/**'
- 'bindings/**/typescript/**'
- 'bindings/**/rust/**'
- 'tooling/extension-bindings/packages.yaml'
- 'tooling/extension-bindings/toolchain.lock.yaml'
- 'tooling/extension-bindings/model/**'
- 'tooling/extension-bindings/normalizer/**'
- 'tooling/extension-bindings/emitters/go/**'
- 'tooling/extension-bindings/emitters/python/**'
- 'tooling/extension-bindings/emitters/typescript/**'
- 'tooling/extension-bindings/emitters/rust/**'
- 'tooling/extension-bindings/orchestrator/**'
- 'tooling/extension-bindings/ci/**'
workflow_dispatch:
inputs:
targets:
description: 'Comma-separated package-key:go, package-key:python, or package-key:typescript targets, or all; empty uses automatic targets'
description: 'Comma-separated package-key:go, package-key:python, package-key:typescript, or package-key:rust targets, or all; empty uses automatic targets'
type: string
default: ''

Expand All @@ -42,7 +44,7 @@ jobs:
permissions:
contents: write
env:
INPUT_TARGETS: ${{ inputs.targets || vars.BINDING_GENERATION_TARGETS || 'common-integrations:go,env-configuration:go,common-integrations:python,env-configuration:python,common-integrations:typescript,env-configuration:typescript' }}
INPUT_TARGETS: ${{ inputs.targets || vars.BINDING_GENERATION_TARGETS || 'common-integrations:go,env-configuration:go,common-integrations:python,env-configuration:python,common-integrations:typescript,env-configuration:typescript,common-integrations:rust,env-configuration:rust' }}
GOTOOLCHAIN: local
steps:
- name: Check out current main
Expand All @@ -62,6 +64,7 @@ jobs:
env:
HAS_PYTHON: ${{ steps.tools.outputs.has-python }}
HAS_TYPESCRIPT: ${{ steps.tools.outputs.has-typescript }}
HAS_RUST: ${{ steps.tools.outputs.has-rust }}
run: |
set -euo pipefail
python -m unittest discover -s tooling/extension-bindings/ci -p 'test_*.py'
Expand All @@ -77,6 +80,9 @@ jobs:
if [[ "$HAS_TYPESCRIPT" == true ]]; then
npm --prefix tooling/extension-bindings/emitters/typescript test
fi
if [[ "$HAS_RUST" == true ]]; then
cargo test --locked --manifest-path tooling/extension-bindings/emitters/rust/Cargo.toml
fi

- name: Generate and verify bindings
env:
Expand All @@ -89,6 +95,8 @@ jobs:
--rc "$RC" --core-schema "$CORE_SCHEMA"
python tooling/extension-bindings/ci/generate-typescript.py update --targets "$INPUT_TARGETS" \
--rc "$RC" --core-schema "$CORE_SCHEMA"
python tooling/extension-bindings/ci/generate-rust.py update --targets "$INPUT_TARGETS" \
--rc "$RC" --core-schema "$CORE_SCHEMA"

- name: Commit verified generated source to main
env:
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/binding-publish-cargo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Publish Rust bindings (placeholder, publishes nothing)

on:
workflow_dispatch:
inputs:
targets:
description: 'Comma-separated package-key:rust targets, or all'
type: string
default: ''
dry_run:
description: 'Must stay true until crates.io publication is implemented'
type: boolean
default: true

permissions:
contents: read

concurrency:
group: binding-publish-cargo
cancel-in-progress: false

jobs:
publish:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 15

steps:
- name: Report placeholder status
env:
TARGETS: ${{ inputs.targets }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
if [ "${DRY_RUN}" != "true" ]; then
echo "::error::crates.io publication is not implemented; rerun with dry_run enabled."
exit 1
fi
{
echo "### Placeholder: nothing was verified or published"
echo "crates.io publication is not implemented. No package was verified, built, or pushed."
echo "targets: \`${TARGETS:-none}\`"
} >> "$GITHUB_STEP_SUMMARY"
30 changes: 30 additions & 0 deletions .github/workflows/extension-binding-normalizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- "tooling/extension-bindings/emitters/go/**"
- "tooling/extension-bindings/emitters/java/**"
- "tooling/extension-bindings/emitters/typescript/**"
- "tooling/extension-bindings/emitters/rust/**"
- "tooling/extension-bindings/model/**"
- "tooling/extension-bindings/normalizer/**"
push:
Expand All @@ -22,6 +23,7 @@ on:
- "tooling/extension-bindings/emitters/go/**"
- "tooling/extension-bindings/emitters/java/**"
- "tooling/extension-bindings/emitters/typescript/**"
- "tooling/extension-bindings/emitters/rust/**"
- "tooling/extension-bindings/model/**"
- "tooling/extension-bindings/normalizer/**"
workflow_dispatch:
Expand Down Expand Up @@ -142,3 +144,31 @@ jobs:

- name: Test the Java emitter and generated conformance packages
run: gradle test --console=plain

verify-rust-emitter:
name: Verify Rust emitter on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]

defaults:
run:
working-directory: tooling/extension-bindings/emitters/rust

steps:
- name: Check out repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false

- name: Set up Rust
run: rustup toolchain install 1.92.0 --profile minimal --component clippy --component rustfmt && rustup default 1.92.0

- name: Test the Rust emitter and generated conformance packages
run: cargo test --locked

- name: Lint Rust emitter
run: cargo clippy --locked -- -D warnings
33 changes: 31 additions & 2 deletions tooling/extension-bindings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

`IMPLEMENTATION.md` is historical design guidance; the repository owner's
standing overrides govern generation and publication.
The shared Go resolver and normalizer feed native Go and Python emitters. Native TypeScript
and Java emitters live under `emitters/typescript/` and `emitters/java/`.
The shared Go resolver and normalizer feed native Go and Python emitters. Native TypeScript,
Java, and Rust emitters live under `emitters/typescript/`, `emitters/java/`, and
`emitters/rust/`; the Rust contract is in `CONTRACT.md`.
The Go orchestrator exposes eight commands through Go's standard `flag` package.
GitHub Actions generates and commits Go and Python bindings to `main`, then a
separate manual workflow publishes verified Go module tags and release assets.
Expand Down Expand Up @@ -285,6 +286,34 @@ development toolchain lock remains a development input. Go publication uses
verified workspace tools and records their actual identities. A separately
released tooling lock is optional future work.

## Rust quickstart

`emitters/rust/rust-toolchain.toml` pins Rust 1.92.0 with rustfmt and clippy, matching
`toolchain.lock.yaml`. From `emitters/rust/`, emit and verify a package from a committed
normalized model and package target:

```sh
M=../../model/conformance/expected/01-owned-kind-interface/runtimeconditions.binding-model.yaml
cargo run --locked -- emit --model $M --package-config testdata/package-target.yaml --output /tmp/rust-pkg
cargo run --locked -- verify --model $M --package-config testdata/package-target.yaml --input /tmp/rust-pkg
```

Add `--json` to get structured errors. A consumer adds the generated crate as a path
dependency, for example `runtimeconditions-conformance-owned-kind-interface = { path = "/tmp/rust-pkg" }`.

Real models come from `rc bindings normalize`, and the package target is generated
from `packages.yaml`. The orchestrator computes `emitterSha256` from the emitter
sources (`Cargo.toml`, `Cargo.lock`, `package-target.schema.yaml`, `src/*.rs`); the standalone
emitter only requires a 64-character hex digest. To generate, verify, and package every Rust
target from the repository root:

```sh
/tmp/rc bindings update --target common-integrations:rust --target env-configuration:rust
/tmp/rc bindings check --target common-integrations:rust --target env-configuration:rust
```

`cargo test --locked` runs the conformance suite, which compiles and runs the generated crates.

## Phase 5 verification scope

On 2026-10-06, the repository owner explicitly overruled the per-target
Expand Down
81 changes: 81 additions & 0 deletions tooling/extension-bindings/ci/generate-rust.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Configure and update verified Rust bindings for the Actions workflow."""

from __future__ import annotations

import argparse
import shutil
import subprocess
from pathlib import Path

from generation import (
Target,
exact_version,
of_language,
select_targets,
summarize,
write_outputs,
)

LANGUAGE = "rust"
TOOLS = ("cargo", "rustc", "rustfmt")


def require_tools() -> None:
missing = [name for name in TOOLS if shutil.which(name) is None]
if missing:
raise ValueError(
"Rust targets require the provisioned toolchain on PATH: missing "
+ ", ".join(missing)
)


def update(root: Path, targets: list[Target], rc: Path, core: Path) -> None:
selected = of_language(targets, LANGUAGE)
if not selected:
summarize("No Rust targets selected; nothing to update.")
return
require_tools()
tooling = root / "tooling" / "extension-bindings"
args = [
str(rc.resolve()),
"bindings",
"update",
"--packages",
str(tooling / "packages.yaml"),
"--toolchain-lock",
str(tooling / "toolchain.lock.yaml"),
"--core-schema",
str(core.resolve()),
]
for key, _, _ in selected:
args.extend(["--target", key])
subprocess.run(args, cwd=root, check=True)
summarize(f"Verified and synchronized {len(selected)} Rust binding targets.")


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("command", choices=["configure", "update"])
parser.add_argument("--root", type=Path, default=Path.cwd())
parser.add_argument("--targets", default="")
parser.add_argument("--rc", type=Path)
parser.add_argument("--core-schema", type=Path)
args = parser.parse_args()
root = args.root.resolve()
try:
targets = select_targets(
root / "tooling/extension-bindings/packages.yaml", args.targets
)
if args.command == "configure":
version = exact_version(targets, LANGUAGE)
write_outputs(rust_version=version, has_rust="true" if version else "false")
else:
if args.rc is None or args.core_schema is None:
raise ValueError("update requires --rc and --core-schema")
update(root, targets, args.rc, args.core_schema)
except (ValueError, KeyError, OSError, subprocess.CalledProcessError) as error:
parser.exit(1, f"binding generation failed: {error}\n")


if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion tooling/extension-bindings/ci/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import yaml

LANGUAGES = {"go": "Go", "python": "Python", "typescript": "TypeScript"}
LANGUAGES = {"go": "Go", "python": "Python", "typescript": "TypeScript", "rust": "Rust"}

Target = tuple[str, str, str]

Expand Down
Loading
Loading