Project Configuration
A project’s settings live in one weaveffi.toml at its root. The API
definition (a Rust producer crate or an IDL) describes only the API; this file says
where it is, what the library is called, how it’s built and packaged, and how
each target is generated.
Every table is optional, and every unknown table or key is an error, so a typo
fails the run instead of being ignored.
[project]
input = "." # this crate, or an IDL such as "kvstore.yml"
out = "bindings"
targets = ["c", "swift", "kotlin", "python"]
[package]
name = "kvstore"
version = "1.2.0"
description = "An embedded key-value store"
license = "MIT"
authors = ["Example <hello@example.dev>"]
repository = "https://github.com/example/kvstore"
[build]
platforms = ["darwin-arm64", "ios-arm64", "ios-sim-arm64", "android-arm64"]
[generators.kotlin]
name = "com.example.kvstore"
weaveffi init writes a starter file: in a Rust crate it points input at
the crate ("."); elsewhere it also writes a starter IDL.
Discovery
With an explicit input (weaveffi generate api.yml, weaveffi generate path/to/crate), the CLI uses the nearest weaveffi.toml at or above the
input (for a crate, at or above its directory). With no input, it looks for
weaveffi.toml in the current directory and its parents and reads
[project] input, so weaveffi generate works from anywhere inside the
project, like cargo build. --config <path> names a file explicitly.
Without any file, every setting takes its default.
[project]
| Key | Default | Meaning |
|---|---|---|
input | none | The API definition: a Rust producer crate (its directory, usually ".", or its Cargo.toml), whose API is read from its built library (see Library Mode), or a .yml, .yaml, .json, or .toml IDL |
out | bindings | Output directory for generate and diff |
targets | all eleven | Targets to generate when --target isn’t given |
Paths are relative to the directory holding weaveffi.toml. Command-line
arguments (input, -o, --target) override the table.
[package]
[package] resolves into the library’s identity:
the package name every ecosystem publishes under, the C symbol prefix,
and the native library base name. Generators derive every other name from
these three.
| Key | Meaning |
|---|---|
name | Distribution name (may contain - or .) |
version | Version stamped into every manifest (default 0.1.0) |
description, license, authors, homepage, repository | Manifest metadata |
c_prefix | C symbol prefix; IDL inputs only |
library | Native library base name (lib{library}.so); IDL inputs only |
dist | Where weaveffi package writes its artifacts (default dist); not part of the identity |
The rules depend on the input:
| Rust producer (a crate) | IDL | |
|---|---|---|
name | [package] name, else the crate’s Cargo package name | [package] name, else the input file stem |
prefix | the crate’s library name ([lib] name, else the package name with - mapped to _) | c_prefix, else snake-case name |
library | same as prefix (the cdylib Cargo builds) | library, else snake-case name |
| metadata | [package], falling back to cargo metadata (workspace-inherited version.workspace = true included) | [package] |
A Rust producer’s prefix is what the macro compiled into its symbols, so it
can’t be configured: setting c_prefix or library for a Rust producer is
an error. Renaming the published package with name is fine.
[build]
How weaveffi build and weaveffi package compile a Rust producer (see
Packaging):
| Key | Default | Meaning |
|---|---|---|
platforms | the host | Platform ids to build when --platforms isn’t given |
release | true | Build with the release profile (--debug overrides it) |
macos_deployment_target | "11.0" | MACOSX_DEPLOYMENT_TARGET, and the macOS version wheel tags carry |
ios_deployment_target | "13.0" | IPHONEOS_DEPLOYMENT_TARGET |
android_api | 21 | The minimum Android API level, which picks the NDK compiler |
manifest | the project’s crate | The producer’s Cargo.toml, for an IDL project built from a Rust crate |
[generators.<target>]
One table per target (c, cpp, swift, kotlin, node, wasm,
python, dotnet, dart, go, ruby) holds that generator’s options.
Every target that names something after the package spells the override
name:
| Table | name sets | Default |
|---|---|---|
[generators.cpp] | the C++ namespace | {prefix} |
[generators.swift] | the SwiftPM package, product, and module (the C module is C{name}) | PascalCase(name) |
[generators.kotlin] | the Kotlin package | {prefix} |
[generators.node] | the npm package | {name} |
[generators.wasm] | the npm package | {name} |
[generators.python] | the PyPI distribution | {name} |
[generators.dotnet] | the namespace, assembly, and NuGet id | PascalCase(name) |
[generators.dart] | the pub package | {prefix} |
[generators.go] | the module path | {name} |
[generators.ruby] | the gem | {name} |
The C target has no package name. A few targets name a second thing:
| Table | Key | Default |
|---|---|---|
[generators.cpp] | header_name | {library}.hpp |
[generators.python] | import_name (the import package) | {prefix} |
[generators.ruby] | module_name (the top-level Ruby module) | PascalCase(name) |
The constants that go into package manifests are options too:
| Table | Key | Default |
|---|---|---|
[generators.swift] | min_macos, min_ios (the platforms: of Package.swift) | "11.0", "13.0" |
[generators.swift] | xcframework_url (where the packaged binary target downloads from; {version} and {file} are substituted) | a placeholder |
[generators.kotlin] | min_sdk, compile_sdk | 21, 35 |
[generators.python] | requires_python | ">=3.9" |
[generators.node] | node_engine (engines.node) | ">=18" |
[generators.dart] | sdk (the pubspec SDK constraint) | ">=3.10.0 <4.0.0" |
Keep min_macos, min_ios, and min_sdk at or above the [build]
deployment targets and API level, or the toolchains warn that the library is
newer than the package claims. The remaining options (C’s
buffer_helpers, C++’s standard, Kotlin’s flavor, and Wasm’s
emscripten) are documented on the target’s page
under Generators. No target has its own C prefix:
the prefix belongs to the library.
Generation records
Rendering is pure: each target renders its files in memory, and the orchestrator does every write. Every run renders every selected target, then:
- Only files whose contents changed are rewritten, so file timestamps (and incremental builds) stay stable. When nothing changed, nothing is written.
- The orchestrator records the files each target produced in
{out}/.weaveffi-cache/{target}.json. Files the previous run wrote that the current run no longer produces (a renamed type, a removed module) are deleted. Files you added under the output directory (anode_modules/, a build directory) are never touched.
--dry-run validates and prints the files that would be written.
CI
weaveffi diff --check regenerates in memory and compares with the output
directory without writing anything:
weaveffi diff --check # uses [project]
weaveffi diff path/to/crate -o bindings --target c,swift --check
It exits 0 when the directory is up to date, 2 when files would change,
and 3 when files would be added or removed (stale files count as removed).
weaveffi validate --format json prints one JSON object with ok, counts,
and any errors (each with a code matching the
error catalog); --warn adds advisory
warnings, which never change the exit status.
Pitfalls
- Config travels with the definition. Two checkouts with different
weaveffi.tomlfiles generate different packages from the same source. Commit the file next to the definition and let discovery find it. - Name overrides don’t rename the library. A target’s package or module
override changes what consumers import, not which native library it loads;
that’s always
{library}. The targets that load the library at run time (Python, Ruby, .NET, Dart, Kotlin on the JVM, and WebAssembly on Node.js) also accept a full path in the{PREFIX}_LIBRARYenvironment variable; C, C++, Swift, Go, and the Node.js addon link it when they’re built (see each target’s page).