build_rust_binaries 0.1.0
build_rust_binaries: ^0.1.0 copied to clipboard
Dart build hooks and CLI for building Rust locally or automatically in CI/CD and retrieving Rust binaries from a CDN like those built in Github Actions for a Github Releases.
build_rust_binaries #
Build hooks and CLI for cross-compiling Rust native libraries and distributing them via CDN — designed for Dart & Flutter packages that embed Rust FFI binaries.
Features #
- Three build modes — fetch pre-built binaries from a CDN, build from a local Rust checkout, or use an existing binary file
- Cross-compilation — maps Dart target triples (OS + architecture) to Rust target triples for Android, iOS, macOS, Linux, Windows, and Fuchsia
- Android NDK integration — auto-generates
.cargo/config.tomlwith the correct linkers and environment for Android cross-compilation - SHA-256 verification — validates downloaded and built binaries against known hashes
- CI/CD ready — YAML config file, CSV/JSON output, fail-fast or continue-on-failure strategies
- Dart build hook — integrates with the Dart
hookspackage to fetch or build Rust binaries duringdart pub getorflutter pub get - Dependency injection — every external interaction (HTTP, process execution, logging) is injectable, enabling full testability
Installation #
# pubspec.yaml
dependencies:
build_rust_binaries: ^0.1.0
Or via CLI:
dart pub add build_rust_binaries
Prerequisites #
- Dart SDK >= 3.10.0
- Rust toolchain with
rustup(forcheckoutand CLI build modes) - Android NDK (for Android cross-compilation targets)
- Platform-specific toolchains (see Cross-compilation setup below)
Concepts #
The package provides two entry points that work together:
| Entry point | What it does | When to use |
|---|---|---|
BuildRustBinariesCLI |
Builds Rust binaries for multiple targets on a single host machine | In CI or local dev — produces binary artifacts that you upload to a CDN |
sourceRustBinariesBuildHook |
Fetches or locates a pre-built Rust binary for the current target | In a hook/build.dart script — runs automatically when consumers run dart pub get |
A typical workflow:
- Build — Run
BuildRustBinariesCLIin CI (GitHub Actions, etc.) on Linux, macOS, and Windows runners to cross-compile.so/.dylib/.dllfiles for every target - Upload — Upload the built binaries to a release CDN (GitHub Releases, S3, etc.)
- Fetch — In your package's
hook/build.dart, usesourceRustBinariesBuildHookinfetchmode to download the correct binary for the end user's platform - Verify — The hook validates the downloaded binary against a known SHA-256 hash
Usage #
CLI — Building binaries locally or in CI #
Create a build_binaries.config.yaml file in your Rust project:
# build_binaries.config.yaml
assetName: my_library-$libraryType-$target
outputDirectory: platform-build
hostSupportedTargets:
windows:
- "x86_64-pc-windows-msvc"
- "aarch64-pc-windows-msvc"
linux:
- "x86_64-unknown-linux-gnu"
- "aarch64-unknown-linux-gnu"
darwin:
- "aarch64-apple-darwin"
- "x86_64-apple-darwin"
- "aarch64-apple-ios"
- "aarch64-apple-ios-sim"
outputs:
my_library-default-$target:
targets:
- "aarch64-apple-darwin"
- "x86_64-apple-darwin"
- "aarch64-pc-windows-msvc"
- "x86_64-pc-windows-msvc"
- "aarch64-unknown-linux-gnu"
- "x86_64-unknown-linux-gnu"
my_library-wasmi-$target:
features: wasmi,wasi
noDefaultFeatures: true
targets:
- "aarch64-apple-ios"
- "aarch64-apple-ios-sim"
Run the CLI:
# Using the standalone CLI
dart run build_rust_binaries \
--config build_binaries.config.yaml \
--manifest-path ./rust/ \
--output-directory platform-build \
--build-dynamic \
--create-cargo-config
# Or from your own package's bin/ entry point
dart run bin/build_binaries.dart --create-cargo-config --output-directory platform-build
Pass --create-cargo-config on first run to generate .cargo/config.toml with the correct Android NDK linkers (only needed once).
The CLI outputs binaries named according to the assetName template, e.g.:
platform-build/
my_library-dynamic-x86_64-unknown-linux-gnu
my_library-dynamic-aarch64-apple-darwin
my_library-dynamic-x86_64-pc-windows-msvc
sha256-hashes.csv
Using the CLI from your own code
import 'package:build_rust_binaries/build_rust_binaries.dart';
void main(List<String> args) async {
await BuildRustBinariesCLI(
manifestPathDefault: './native/', // default path to Cargo.toml
assetNameDefault: r'my_lib-$libraryType-$target',
log: print,
runProcess: myCustomProcessRunner, // optional: wrap cargo commands
).mainCli(args);
}
Using the CLI locally with the build hook (local mode)
Instead of uploading to a CDN, you can build locally with the CLI and use the build hook's local mode to copy the binary from the output directory:
# 1. Build the binary locally
dart run bin/build_binaries.dart \
--output-directory platform-build \
--targets x86_64-unknown-linux-gnu \
--build-dynamic
In your root pubspec.yaml, configure the build hook to use local mode:
hooks:
user_defines:
my_package:
buildMode: local
localPath: platform-build/my_library-dynamic-x86_64-unknown-linux-gnu
Build Hook — Fetching binaries from a CDN #
Create a hook/build.dart in your package:
import 'package:build_rust_binaries/build_rust_binaries.dart';
void main(List<String> args) async {
await sourceRustBinariesBuildHook(
args,
SourceBinariesParams(
fetchAssetUrl: (input) {
// Dynamically construct the CDN URL for the current target
final rustTarget = /* derive from input.config.code */ '';
return Uri.parse(
'https://github.com/your-org/your-repo/releases/download/v1.0.0/'
'my_library-dynamic-$rustTarget',
);
},
sha256ForAsset: (input) {
// Return the expected hash for the current target
return myKnownHashes[rustTarget];
},
defaultBuildOptions: BuildOptions(
buildMode: BuildModeEnum.fetch,
libraryName: 'my_library',
),
),
);
}
Or use a fetch URI base with per-asset SHA-256 hashes:
void main(List<String> args) async {
await sourceRustBinariesBuildHook(
args,
SourceBinariesParams(
defaultBuildOptions: BuildOptions(
buildMode: BuildModeEnum.fetch,
fetchUriBase: 'https://github.com/your-org/your-repo/releases/download/v1.0.0/',
libraryName: 'my_library',
assetsSha256: {
'my_library-dynamic-aarch64-apple-darwin':
'1654553b0782eca999cd5ce3ae9610d5f338e27fcb82a5856e0cdf67acf1b2d9',
'my_library-dynamic-x86_64-unknown-linux-gnu':
'5a0df6b8992c112a66dca94efeacc87a05ac3c695e2ddf38fea8e32dc47ee05f',
// ... all targets
},
),
),
);
}
The full path is constructed as $fetchUriBase$assetName where assetName follows the template $libraryName-$libraryType-$target by default.
Build Hook — Building from a local Rust checkout #
# pubspec.yaml
hooks:
user_defines:
my_package:
buildMode: checkout
checkoutPath: packages/my_package/native/
The checkout mode runs rustup target add, configures the toolchain, and invokes cargo rustc with the appropriate flags for the current Dart target.
Configuring the build mode via pubspec.yaml #
Users control the build mode through their pubspec.yaml:
hooks:
user_defines:
my_package:
# Option 1: Fetch from CDN
buildMode: fetch
# Option 2: Use a local binary
buildMode: local
localPath: path/to/library.so
# Option 3: Build from a Rust checkout
buildMode: checkout
checkoutPath: packages/my_package/native/
Cross-compilation Setup #
Android targets #
Android cross-compilation requires the Android NDK. Set one of these environment variables before running:
ANDROID_NDK_ROOTANDROID_NDK_HOMEANDROID_NDK_LATEST_HOME
Then run the CLI with --create-cargo-config (only once). This generates a .cargo/config.toml file with the correct NDK linker paths:
[target]
aarch64-linux-android.linker="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/aarch64-linux-android31-clang"
armv7-linux-androideabi.linker="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/armv7a-linux-androideabi31-clang"
i686-linux-android.linker="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/i686-linux-android31-clang"
x86_64-linux-android.linker="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/x86_64-linux-android31-clang"
riscv64-linux-android.linker="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/riscv64-linux-android35-clang"
[env]
ANDROID_NDK_HOME="<ndk>"
AR="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/llvm-ar"
CC_aarch64-linux-android="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/aarch64-linux-android31-clang"
CC_armv7-linux-androideabi="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/armv7a-linux-androideabi31-clang"
CC_i686-linux-android="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/i686-linux-android31-clang"
CC_x86_64-linux-android="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/x86_64-linux-android31-clang"
CC_riscv64-linux-android="<ndk>/toolchains/llvm/prebuilt/<os>-x86_64/bin/riscv64-linux-android35-clang"
Per-target Android API levels
Pass a comma-separated list of <target>=<api-level> pairs to --android-version. Entries without a = set the default:
--android-version 'aarch64-linux-android=33,riscv64-linux-android=35,31'
This sets API level 33 for aarch64-linux-android, 35 for riscv64-linux-android, and 31 for all other Android targets.
Linux cross-compilation from any host #
Install the cross-compilation toolchains for your distribution:
# Ubuntu/Debian
sudo apt-get install \
gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf \
gcc-riscv64-linux-gnu gcc-i686-linux-gnu
The generated .cargo/config.toml includes linker entries for these.
Windows: x64 to ARM64 cross-compilation #
To cross-compile for aarch64-pc-windows-msvc (Windows on ARM) from an x64 host:
-
Install the ARM64 build tools via Visual Studio Installer:
- Open Visual Studio Installer → Modify → Individual components
- Select MSVC v143 - VS 2022 C++ ARM64 build tools (or the version matching your VS installation)
- Select Windows 11 SDK (or Windows 10 SDK)
-
Open a Developer Command Prompt for VS and run the ARM64 environment script:
"C:\Program Files\Microsoft Visual Studio\18\Community\VC\Auxiliary\Build\vcvarsamd64_arm64.bat"
- Run the build CLI from that same prompt:
dart run bin/build_binaries.dart --output-directory platform-build
The linker script sets up the environment variables (LIB, INCLUDE, PATH) needed for the MSVC ARM64 cross-compiler toolchain.
macOS: iOS cross-compilation #
Set SDKROOT to the Xcode SDK path before building:
export SDKROOT=$(xcrun --show-sdk-path)
Configuration Reference #
build_binaries.config.yaml #
| Key | Type | Description |
|---|---|---|
outputDirectory |
String |
Directory for built binaries |
assetName |
String |
Output filename template. Supports $libraryType, $target, $output, $features, $libraryName |
manifestPath |
String |
Path to Cargo.toml (default: ./rust/Cargo.toml) |
buildStatic |
bool |
Build static libraries (default: false) |
buildDynamic |
bool |
Build dynamic libraries (default: true) |
createCargoConfig |
bool |
Generate .cargo/config.toml for Android NDK |
androidVersion |
String |
Android API level or per-target versions |
failFast |
bool |
Stop on first build failure (default: true) |
computeSha256 |
bool |
Generate sha256-hashes.csv (default: true) |
hostSupportedTargets |
Map<String, List<String>> |
Targets allowed per host OS (windows, linux, darwin). Filters out targets the current host cannot compile. |
outputs |
Map<String, BuildBinariesOutput> |
Named output groups, each with optional features, noDefaultFeatures, and a list of targets |
pubspec.yaml build hook defines #
Set under hooks: user_defines: <package_name>::
| Key | Values | Description |
|---|---|---|
buildMode |
fetch, local, checkout |
How to obtain the Rust binary |
localPath |
filesystem path | Required for local mode. Path to the pre-built binary. |
checkoutPath |
filesystem path | Required for checkout mode. Path to the Rust crate root (containing Cargo.toml). |
fetchUri |
URL string | Full URL to the binary (for fetch mode) |
fetchUriBase |
URL string | Base URL; binary name is appended from assetName template |
features |
comma-separated string | Cargo features to enable |
noDefaultFeatures |
true / false |
Disable default Cargo features |
libraryName |
string | Library name (default: package name) |
assetName |
string | Template for the asset filename |
assetsSha256 |
map of string → string | Asset name to expected SHA-256 hash |
CLI Reference #
--help, -h Show usage help
--output-directory, -o Directory to place built libraries
--config, -c Path to YAML config file (default: build_binaries.config.yaml)
--manifest-path, -m Path to Cargo.toml (default: ./rust/Cargo.toml)
--targets, -t Comma-separated list of Rust targets to build
--features, -f Comma-separated Cargo features to enable
--no-default-features Disable default Cargo features
--build-static Build static libraries
--build-dynamic Build dynamic libraries (default)
--asset-name Output filename template (e.g. mylib-$libraryType-$target)
--create-cargo-config Generate .cargo/config.toml with Android NDK linkers
--no-create-cargo-config Skip cargo config generation
--android-version Android API level (default: 31,riscv64-linux-android=35)
--fail-fast Stop on first failure (default: true)
--no-fail-fast Continue building remaining targets on failure
--compute-sha256 Generate sha256-hashes.csv (default: true)
--no-compute-sha256 Skip hash computation
--cargo-project Path to the Cargo project directory
Complete Example: CI/CD Pipeline #
This example shows a GitHub Actions workflow that builds Rust binaries across Linux, macOS, and Windows runners, producing artifacts for all supported targets.
# .github/workflows/build_binaries.yaml
name: Build Rust Binaries
on:
push:
tags: ['v*']
workflow_dispatch:
env:
RUST_TOOLCHAIN: nightly-2025-09-27
jobs:
build_binaries:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: stable
- run: dart pub global activate melos && melos bs
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
# Linux: install cross-compilation linkers
- name: Setup Linux cross-compilation linkers
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install \
gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf \
gcc-riscv64-linux-gnu gcc-i686-linux-gnu
# macOS: set SDKROOT for iOS targets
- name: Set SDKROOT for iOS cross-compilation
if: matrix.os == 'macos-latest'
run: |
echo "SDKROOT=$(xcrun --show-sdk-path)" >> $GITHUB_ENV
# Build
- run: dart run bin/build_binaries.dart \
--create-cargo-config \
--output-directory ../../platform-build \
--no-fail-fast
working-directory: packages/my_package
# Upload artifacts
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.os }}
path: platform-build/
After the workflow runs, upload the platform-build/ contents to your CDN (GitHub Releases, S3, etc.). The build hook in your package's hook/build.dart will download the appropriate binary at dart pub get time.
Real-world example #
The wasm_run package uses this package to build and distribute native Rust binaries for 16+ targets across 3 host OSes. See:
packages/wasm_run/build_binaries.config.yaml— multi-output config with wasmi/wasmtime feature variantspackages/wasm_run/hook/build.dart— fetch-mode build hook with per-target SHA-256 hashes.github/workflows/build.yaml— thebuild_binariesjob in CI
Template Variables #
The assetName template supports these variables:
| Variable | Description | Example |
|---|---|---|
$libraryType |
static or dynamic |
dynamic |
$target |
Rust target triple | x86_64-unknown-linux-gnu |
$output |
Output group name from config | my_library-wasmi |
$features |
Comma-separated Cargo features (underscores replace commas) | wasmi_wasi |
$libraryName |
Library name (from config or Cargo.toml) | my_library |
Example template: $libraryName-$libraryType-$target produces my_library-dynamic-x86_64-unknown-linux-gnu.
Additional Information #
- Homepage: github.com/juancastillo0/wasm_run
- Issue tracker: github.com/juancastillo0/wasm_run/issues
- License: See repository root