Add third party crates
We manage third-party Rust dependencies with GNRT.
All crates are transformed into GN’s BUILD.gn files via gnrt,
which was originally developed by the Chromium project.
Declare Rust dependencies in external/rust/BUILD.gn or another file listed in
external/gnrt_config.toml. GNRT uses Cargo and external/rust/Cargo.lock to
resolve crate versions, download source code, apply patches, and generate
BUILD.gn files and GN target mappings in external/vendor/. Git ignores this
directory. Run gnrt sync --all before generating kernel or application build
files with GN.
Install GNRT and sync dependencies
Run these commands from the BlueKernel repository root, with Cargo, GN, Python,
the BlueOS toolchain, and Cargo’s binary directory on PATH:
cargo install --path external/gnrt --locked --bin gnrt
gnrt sync --all
gnrt sync --all --check
Cargo installs GNRT to ~/.cargo/bin.
After updating GNRT’s source code, rerun the installation command and
gnrt sync --all before building BlueKernel.
Set GNRT_CARGO_BIN to select the Cargo executable GNRT uses to resolve
dependencies. Cargo must support the dependencies’ manifests, including Rust
edition 2024. Use the same tools for sync and builds. GN checks their paths and
checksums.
Finish gnrt sync before building the kernel or applications. Do not run it
while a build is using the same checkout. gnrt sync --all --check verifies the
saved sync state offline; it does not compile crates.
Add dependency in //external/rust/BUILD.gn
To add a new third party crate, for example unwinding, add following code in //external/rust/BUILD.gn
cargo_spec("unwinding") {
package = "unwinding"
version = "=0.2.4"
default_features = false
features = [ "unwinder" ]
}
Run gnrt sync --all to generate the GN rules under //external/vendor/.
Targets can depend on those rules, for example by adding //external/rust:unwinding to the deps field in BUILD.gn.
Procedural macros
Declare a procedural macro crate in the same way as a regular dependency. For
example, external/rust/BUILD.gn declares paste:
cargo_spec("paste") {
package = "paste"
version = "=1.0.15"
default_features = false
}
In the consuming target, put regular libraries in deps and procedural macros
in proc_macro_deps:
import("//build/templates/build_template.gni")
build_rust("example") {
crate_type = "rlib"
sources = [ "src/lib.rs" ]
deps = [ "//external/rust:unwinding" ]
proc_macro_deps = [ "//external/rust:paste" ]
}
GNRT selects a host build for paste and a target build for unwinding.
If a crate has a build.rs, GNRT also generates its host build and arranges
for the script to run before compiling the crate for the target.
Board-specific features
Keep the declaration outside board-specific if blocks. Put the board selection
inside features so GNRT can collect the declaration for every board. For
example, external/rust/BUILD.gn declares both chip features for esp-hal:
cargo_spec("esp-hal") {
package = "esp-hal"
version = "=1.1.1"
default_features = false
features = [
{
condition = "board == \"esp32c6_devkitc_1\""
values = [ "esp32c6" ]
},
{
condition = "board == \"seeed_xiao_esp32c3\""
values = [ "esp32c3" ]
},
]
}
A consuming target depends on //external/rust:esp-hal. Its generated rules
enable esp32c6 when building esp32c6_devkitc_1, or esp32c3 when building
seeed_xiao_esp32c3.
When adding another declaration file, add its path to the existing
collect.declaration_files list in external/gnrt_config.toml. The current
declaration files are registered as follows:
[collect]
declaration_files = ["external/rust/BUILD.gn"]
Source patches
Pin the crate version or Git revision before adding a patch. Use paths starting
with //, relative to the repository root, in patches. Configure patches for
transitive dependencies and custom license file paths in
external/gnrt_config.toml. Keep existing local changes and license notices,
and run the project’s license compliance checks.
GNRT applies configured patches during sync and skips patches that are already
applied. When adding or updating patches, test them against unmodified upstream
source code and compare the patched files with external/vendor/.
gnrt sync --all --check verifies the saved sync state; it does not test patch
application on a clean source tree.