Packaging Rust Projects#
Load when packaging a Rust project with Cargo for dakota/Bluefin BuildStream.
When NOT to Use#
- Go project →
packaging-go.md - Zig project →
packaging-zig.md - Pre-built binary →
packaging-binaries.md
Overview#
Rust elements use kind: make with a cargo2 source block that vendors all crate dependencies offline. BST builds are network-isolated — cargo fetch is not available at build time.
Scaffolding (Fastest Path)#
There is no scaffold command. Copy an existing Rust element as a starting point:
cp elements/bluefin/sudo-rs.bst elements/bluefin/<name>.bst
# Edit name, URL, version, binary name, and regenerate cargo2 sources
Then regenerate cargo2 sources (see below).
cargo2 Sources Are Generated — Never Hand-Written#
# Generate cargo2 source block from Cargo.lock
python3 files/scripts/generate_cargo_sources.py path/to/Cargo.lock
After bumping a git ref with just bst source track bluefin/<name>.bst:
- Enter the build sandbox to get the new Cargo.lock:
just bst shell --build bluefin/<name>.bst - Run
generate_cargo_sources.pyon the new Cargo.lock - Replace the generated block in the element
The cargo2 source block (starting at the first - kind: cargo2 line) is generated output. Do not edit it manually.
Element Structure#
kind: make
build-depends:
- freedesktop-sdk.bst:components/rust.bst
- freedesktop-sdk.bst:bootstrap-import.bst
depends:
- freedesktop-sdk.bst:public-stacks/runtime-minimal.bst
variables:
version: '1.2.3'
cargo-home: '%{build-root}/.cargo'
cargo-opts: '--release --locked'
config:
build-commands:
- |
export CARGO_HOME="%{cargo-home}"
cargo build %{cargo-opts} --bin project
install-commands:
- install -Dm755 "target/release/project" "%{install-root}%{bindir}/project"
- '%{install-extra}'
sources:
# ── hand-authored section above; generated cargo2 block below ──
- kind: git_repo
url: github:owner/project.git
track: main
ref: abc123...
# ── GENERATED: do not edit below this line ──
- kind: cargo2
url: "https://static.crates.io/crates/some-crate/some-crate-1.0.0.crate"
ref: sha256hex...
# ... (many more cargo2 entries)
Cargo Flags#
| Flag | Purpose |
|---|---|
--release | Optimized build |
--locked | Use exact Cargo.lock versions — required for reproducibility |
--offline | Prevent any network access during build |
Use --locked --offline together:
variables:
cargo-opts: '--release --locked --offline'
Tracking Group#
Rust elements require human review when bumping refs because cargo2 regeneration touches many lines. Add the element to the manual-merge matrix in .github/workflows/track-bst-sources.yml (not auto-merge).
systemd Service (if needed)#
install-commands:
# ... install binary ...
- |
install -Dm644 /dev/stdin "%{install-root}%{indep-libdir}/systemd/system/project.service" <<'SERVICE'
[Unit]
Description=Project
After=network.target
[Service]
ExecStart=/usr/bin/project
Restart=on-failure
[Install]
WantedBy=multi-user.target
SERVICE
- |
install -Dm644 /dev/stdin "%{install-root}%{indep-libdir}/systemd/system-preset/80-project.preset" <<'PRESET'
enable project.service
PRESET
Checklist#
-
cargo2sources generated by script (not hand-written) -
--locked --offlinein cargo-opts -
strip-binaries: ""NOT needed (Rust produces ELF) - Tracking group set to
manual-mergein tracking workflow - Element added to
elements/bluefin/deps.bst -
just validatepasses -
just bst build bluefin/<name>.bstpasses
Lessons Learned#
Wrong template: copy tailscale.bst (pre-built binary), not for Rust-from-source (2026-06-07)#
The "Scaffolding" section above said to copy tailscale.bst as a starting point for Rust
elements. That's wrong — tailscale.bst is a pre-built binary element (kind: manual without
cargo). The correct Rust-from-source template is sudo-rs.bst:
cp elements/bluefin/sudo-rs.bst elements/bluefin/<name>.bst
sudo-rs.bst uses kind: make + cargo build --release and has the correct cargo2 source
structure.
overlap-whitelist required when binary conflicts with another element (2026-06-07)#
sudo-rs replaces the system sudo, so it needs an explicit whitelist for the paths it shares
with the upstream sudo element from fdsdk. Without it, the OCI image build fails with an
overlap error:
public:
bst:
overlap-whitelist:
- /usr/bin/sudo
- /usr/bin/sudoedit
- /usr/lib/debug/usr/bin/sudo.debug
Any Rust element replacing a binary that already exists in fdsdk or gnome-build-meta will need
this. Use just bst show --format '%{name}: %{overlap-whitelist}' oci/bluefin.bst to audit.
cargo build --release alone (no --locked --offline) works because cargo2 provides deps (2026-06-07)#
The cargo2 source block vendors all crates offline. BST's hermetic sandbox means no network
access is possible, so --offline is redundant (it will succeed either way). --locked is
still recommended as best practice but sudo-rs.bst omits it without issue. The build will
still fail with a clear error if cargo2 sources are missing or mismatched — treat that as a
signal to regenerate cargo2 sources, not a reason to add --offline.
Fat LTO + codegen-units=1 causes SIGABRT in BST btrfs overlay on ghost (2026-06-07)#
Upstream Rust crates that set lto = true + codegen-units = 1 in their [profile.release]
emit -C linker-plugin-lto -C codegen-units=1 to each rustc invocation. Inside BST's
btrfs overlay sandbox on ghost (Podman container + btrfs), LLVM's fat LTO codegen pass
corrupts its own allocator:
realloc(): invalid next size
rustc ... -C linker-plugin-lto -C codegen-units=1 (signal: 6, SIGABRT)
This is a ghost-environment issue — CI's remote execution server handles fat LTO fine.
Do NOT fix in the element. Put the override in ghost's userconfig to avoid
invalidating the remote CAS artifact (see ci.md → Ghost-specific build fixes):
# ~/.config/buildstream/userconfig.yaml on ghost
projects:
dakota:
elements:
bluefin/uutils-coreutils.bst:
environment:
CARGO_PROFILE_RELEASE_LTO: "thin"
Thin LTO provides most cross-crate optimization benefits with far less memory per
codegen unit and does not trigger the allocator corruption.