-
Notifications
You must be signed in to change notification settings - Fork 118
Description
Building bare-metal/embedded Rust projects with Crane requires several
workarounds due to the unique constraints of #![no_std] and #![no_main]
targets.
1. Custom Linker Override
Problem: Crane's mkCrossToolchainEnv automatically sets
CARGO_TARGET_*_LINKER environment variable:
CARGO_TARGET_${cargoEnv}_LINKER = "${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc";This environment variable takes precedence over .cargo/config.toml settings,
causing cargo to use the system linker (clang/gcc) instead of
embedded-specific linkers like flip-link.
Solution: Override the CARGO_TARGET_*_LINKER environment variable
directly in commonArgs:
CARGO_TARGET_THUMBV6M_NONE_EABI_LINKER = "flip-link";2. Disable Tests
Problem: Embedded targets cannot run tests (no test harness, no std
library).
Solution: Set doCheck = false in commonArgs:
doCheck = false;3. Custom buildDepsOnly setup
Problem: Embedded projects may require custom linker scripts (e.g., memory.x)
that must be included in both the dummy source and final build.
Solution: Use extraDummyScript to copy linker scripts and build.rs, and
filter them in source cleaning:
Problem: Crane's default dummy binary doesn't compile for some embedded projects. Embassy (https://embassy.dev) projects require additional setup like #[entry] and bind_interrupts! macro.
Solution: Provide custom dummyrs and dummyBuildrs in buildDepsOnly
cleanSrc = lib.cleanSourceWith {
src = src;
filter = path: type:
(craneLib.filterCargoSources path type) ||
(builtins.match ".*/(memory\\.x)$" path != null);
};
cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
dummyBuildrs = "build.rs";
dummyrs = pkgs.writeText "dummy.rs" ''
#![no_main]
#![no_std]
use panic_reset as _;
use cortex_m_rt::entry;
use embassy_rp::bind_interrupts;
bind_interrupts!(struct Irqs {});
#[entry]
fn main() -> ! {
loop { }
}
'';
extraDummyScript = ''
cp ${src}/memory.x $out/memory.x
cp ${src}/build.rs $out/build.rs
'';
});4. Complete Example
{
cleanSrc = lib.cleanSourceWith {
src = src;
filter = path: type:
(craneLib.filterCargoSources path type) ||
(builtins.match ".*/(memory\\.x)$" path != null);
};
commonArgs = {
src = cleanSrc;
strictDeps = true;
doCheck = false; # Embedded targets can't run tests
CARGO_BUILD_TARGET = "thumbv6m-none-eabi";
# Override crane's automatic linker configuration
CARGO_TARGET_THUMBV6M_NONE_EABI_LINKER = "flip-link";
nativeBuildInputs = [ pkgs.flip-link ];
};
cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
# Custom dummy binary for no_std/no_main projects
dummyBuildrs = "build.rs";
dummyrs = pkgs.writeText "dummy.rs" ''
#![no_main]
#![no_std]
use panic_reset as _;
use cortex_m_rt::entry;
use embassy_rp::bind_interrupts;
bind_interrupts!(struct Irqs {});
#[entry]
fn main() -> ! {
loop { }
}
'';
# Include linker scripts and build.rs in dummy source
extraDummyScript = ''
cp ${src}/memory.x $out/memory.x
cp ${src}/build.rs $out/build.rs
'';
});
firmware = craneLib.buildPackage (commonArgs // {
inherit cargoArtifacts;
});
}