Languages: English · Русский · 简体中文
When you find a bug, before fixing it, write a test that reproduces
it — the test must fail on main and pass after the fix. Include
both in the same PR.
Tests live under lib/src/test/java/lang/ktav/:
| File | Scope |
|---|---|
SmokeTest.java |
Loads / Dumps happy paths, BigInteger, error surface. |
ConformanceTest.java |
Cross-language conformance against ktav-lang/spec. |
This Java library is deliberately a thin wrapper. Parser and format
behaviour belong in the Rust crate
(ktav-lang/rust) — changing it
there updates every language binding at once. Only Java-specific
ergonomics (Value type tree, JNA loader, cache / download logic)
belong in this repo.
If your change requires a format change, start a discussion in
ktav-lang/spec first.
If you touch anything exported from lang.ktav, say in the PR
description whether it is:
- semver-compatible (additions, looser signatures, doc changes); or
- semver-breaking (renamed / removed items, changed signatures, tightened types) — in which case the version bump lands in the next MINOR while we are pre-1.0.
Update CHANGELOG.md and the two translations in the same PR.
Commits should be atomic: a bug fix and its test together, a feature
and its tests together, a rename on its own, a refactor on its own.
git log --oneline should read like a changelog. Don't prefix commit
messages with feat: / fix: — no conventional commits here.
The LIB_VERSION constant in
lib/src/main/java/lang/ktav/internal/NativeLoader.java must match
the git tag used to cut the release. If you bump the library version,
update LIB_VERSION in the same commit. Mismatched values cause
consumers to download a native library that doesn't match their code.
You need:
- JDK 17+.
- A Rust toolchain via
rustup. MSRV: 1.70. git.
Gradle ships via the included wrapper (./gradlew); no separate
install needed.
Layout during development — the Java library loads the Rust-built
ktav_cabi cdylib via JNA. Clone the sibling spec repo (used by
conformance tests) next to this one or initialise the submodule:
ktav-lang/
├── java/ ← this repo
├── rust/ ← sibling Rust crate (path dep for local dev)
└── spec/ ← conformance fixtures (git submodule at java/spec/)
The Rust C ABI crate (crates/cabi/) depends on the published ktav
crate on crates.io by default. For local cross-repo edits, switch the
workspace.dependencies.ktav entry in Cargo.toml to
{ path = "../rust" }.
# 1. Build the native library for your host platform.
cargo build --release -p ktav-cabi
# 2. Point Java at it.
export KTAV_LIB_PATH="$PWD/target/release/libktav_cabi.so" # Linux
# ="$PWD/target/release/libktav_cabi.dylib" # macOS
# ="$PWD/target/release/ktav_cabi.dll" # Windows
# 3. For conformance tests, point at the spec submodule.
git submodule update --init
export KTAV_SPEC_ROOT="$PWD/spec/versions/0.1/tests"./gradlew :lib:test # full suite
./gradlew :lib:test --tests '*SmokeTest*' # filter by class
./gradlew :lib:test --tests '*ConformanceTest*' # spec fixtures onlyWhen either KTAV_LIB_PATH or KTAV_SPEC_ROOT is unset, the relevant
tests skip / no-op rather than fail — so ./gradlew test in a bare
checkout stays green.
./gradlew :lib:compileJava # javac warnings as-is
cargo fmt --all --check
cargo clippy --release -p ktav-cabi -- -D warningsCI runs the same commands; run them locally before pushing.
- Wire format. Rust and Java exchange JSON over the FFI boundary,
with
{"$i":"..."}/{"$f":"..."}wrappers for typed integers / floats. This preserves arbitrary precision and the:ivs:fdistinction through encoding / decoding. - Memory ownership. Rust allocates the output buffer; Java copies
it into a
byte[]and immediately callsktav_freeon the Rust side. No buffer is long-lived across the FFI boundary. - Loader.
lang.ktav.internal.NativeLibdlopens the shared library once per process via JNA'sNative.load. The path is resolved byNativeLoader.resolve()— env / cache / download.
Tag v<X.Y.Z> on main. The release workflow cross-compiles six
platform binaries (linux amd64/arm64, darwin amd64/arm64, windows
amd64/arm64) plus builds the library JAR, and attaches all of them as
GitHub Release assets. The LIB_VERSION constant in
NativeLoader.java must match the tag — change it in the same commit
as the tag message.
Ktav's motto: "be the config's friend, not its examiner." Before proposing a new Java-specific feature, ask:
- Does this add a new rule the reader must hold in their head?
- Could this live in user code instead of the library?
- Does this erode the "no magic types" principle?
New rules are costly. Reject everything that doesn't clearly belong.
This repo participates in the org-wide three-language policy (EN / RU /
ZH). Every prose file lives in three parallel versions — see
ktav-lang/.github/AGENTS.md
for the naming convention and the "update all three in one commit"
rule.