Getting started
Adding the crate, and building this repository
Using the crate is one line in a Cargo.toml, because the build downloads the
renderer it needs. Working on compose-rust itself is the longer path further down.
0.0.1 contains the boundary, the protocol, the schema, the Runtime trait and the
renderer download. It does not contain the authoring API yet, so there is no
launch(app) to call and no screen to write. That arrives with 1.0.0 on
2026-10-20 (#23, #64).
Until then the crate serves a layer that builds the tree by implementing
Runtime.
Adding it to a project
The renderer is far too large for crates.io, so each release carries it as a checksummed artifact. Fetching it is the build script's job, not yours.
cargo add compose-rust@0.0.1
[dependencies]
compose-rust = "0.0.1"
cargo build works out which renderer the target needs. It downloads the release
artifact for the crate's exact version, checks it against the published .sha256,
unpacks it into a cache outside target/, and links it. There is no environment
variable to set and no script to run. The cache is keyed by version and target, so it
survives cargo clean and is shared between projects.
| Variable | Effect |
|---|---|
COMPOSE_RUST_RENDERER_DIR | Use the renderer in this directory. It is checked first, and nothing is downloaded when it is set. A renderer you built yourself, a vendored copy and an offline build all work through it. |
COMPOSE_RUST_CACHE_DIR | Moves the cache off $HOME/.cache/compose-rust (%LOCALAPPDATA%\compose-rust on Windows). |
A build with no network says which files to put where. Putting them there is all it takes.
default-features = false builds with no renderer at all, for a headless or
documentation build. A binary built that way says what is missing and exits non-zero. It
does not open no window and return 0, because a program that drew nothing has
to be distinguishable from one that finished its work. The mock-renderer
feature drives the boundary with no renderer on purpose, for tests and for embedding the
Host in something that draws for itself.
What you can build on today
The crate's public surface in 0.0.1 is the part every authoring layer shares. A layer
implements Runtime: it writes the tree's records into a Batch on
rebuild, writes what changed on render, and runs a handler in
handle_event, returning the value the boundary hands back. It then registers
itself with launch_runtime or LaunchBuilder. Everything else, the
entry points, the batch, the theme, assets, messages and frame requests, belongs to the
Host and behaves the same whatever built the tree.
The Theme, DesignSystem and Palette values on
Design systems compile today. The full API reference is on
docs.rs.
Working on this repository
Everything below is for working on compose-rust itself, which needs the renderer toolchain as well. Start with the setup check. It checks every tool the build needs, names what is missing, and prints the command that fixes it.
./scripts/setup-check.sh # every tool the build needs, with the fix for anything missing
./scripts/check.sh # fmt, clippy, tests, quick benchmarks, Kotlin build and tests
The workspace targets Rust 1.85+ (edition 2024), and rustfmt
and clippy are required. Kotlin needs nothing installed:
renderer/kotlin (kotlin.bat on Windows) downloads the pinned
toolchain on first use. ./scripts/check.sh --no-kotlin skips the Kotlin gate,
and --full runs the full benchmark sample.
Build the renderer
The renderer uses a pinned commit of
thisisthepy/compose-multiplatform-core-extended.
It is a Compose fork that adds what the published build lacks: the Linux targets, the text
selection menu and the copy keys. Build the fork once, then the renderer.
# macOS
./renderer/scripts/build-compose.sh
cd renderer && ./desktop/scripts/build-macos.sh --release # build/macos/<target>/
# Linux
./renderer/scripts/build-compose.sh --target linuxX64
cd renderer && ./desktop/scripts/build-linux.sh --release # build/linux/<target>/
Each produces one static library, libcompose_rust_renderer.a, with Compose,
Skia and the interpreter inside it. The Rust build links it when
DXC_MACOS_NATIVE_LIB or DXC_LINUX_NATIVE_LIB names its directory.
Without one of those, a checkout links the GraalVM native image from
renderer/build/native-image/dist/lib, which is what 0.0.1 ships.
Windows still builds the GraalVM native image, with
renderer/desktop/scripts/build-native-windows.ps1. The native image on macOS
needs Liberica NIK 25 Full, because upstream GraalVM skips AWT on Darwin
(oracle/graal#13272).
./scripts/install-nik.sh installs it. See
Troubleshooting for how a wrong JDK shows up.
The first build is slow, because Compose and Skia are compiled ahead of time. You repeat it only when the Kotlin side changes; ordinary Rust work reuses the library.
Smoke-test the library
Before involving Rust, check that the native-image library alone can open a window. The smoke
test links a small C host against it and calls compose_rust_renderer_run.
./renderer/desktop/scripts/build-native.sh
./renderer/desktop/scripts/smoke-test.sh
A window should appear. Closing it makes run return 0, and the
process exits normally.
The JVM development shell
The development shell is the fastest loop when you work on the renderer itself, with hot
reload and @Preview. The JVM is allowed only here. Shipped artifacts never
contain one.
cd renderer && ./kotlin run -m desktop
Samples
The samples/ directory holds 12 samples, clones of real applications such as a
calculator, a notepad, a task list and a chat client. They do not build today, because they
are being rewritten on the authoring API Planned (#85). The
rewritten set becomes the public samples (#2).
The generated code check
One test compares the checked-in Kotlin protocol file against what the Rust schema generates.
If you change the schema and forget to regenerate, that test fails. That is the intended
alarm: the schema is the single source, and a Kotlin side that was not updated should fail
the build rather than misbehave at runtime. Regenerate with the codegen binary.
cargo run -p compose-rust --bin codegen