How elf-magic works under the hood
elf-magic uses standard Rust build patterns to generate ELF constants at compile time. This document explains the complete build process and how your code is generated.
elf-magic follows the same pattern used by tools like bindgen, prost, and other code generators:
- Build script runs during
cargo build - Programs are discovered and built via
cargo build-sbf - Code is generated to
$OUT_DIR/generated.rs - Environment variable is set pointing to generated file
- Your
src/lib.rsincludes the generated code
This keeps your source tree clean and avoids race conditions with tools like Rust Analyzer.
When you run cargo build, Rust executes your build.rs:
// build.rs
fn main() {
elf_magic::build().unwrap();
}elf-magic uses cargo metadata to discover all Solana programs in your workspace(s):
# Internally runs:
cargo metadata --format-version 1This finds all crates with crate-type = ["cdylib"] - the marker for Solana programs.
For each discovered program, elf-magic builds the .so file:
# For each program, runs:
cargo build-sbf --package program-nameBuilt programs are cached and only rebuilt when source files change (incremental builds).
Generated code is written to $OUT_DIR/generated.rs:
// $OUT_DIR/generated.rs (generated at build time)
pub const TOKEN_MANAGER_ELF: &[u8] = include_bytes!(env!("TOKEN_MANAGER_ELF_PATH"));
pub const GOVERNANCE_ELF: &[u8] = include_bytes!(env!("GOVERNANCE_ELF_PATH"));
pub fn elves() -> Vec<(&'static str, &'static [u8])> {
vec![
("token_manager", TOKEN_MANAGER_ELF),
("governance", GOVERNANCE_ELF),
]
}elf-magic sets these environment variables for the main compilation:
ELF_MAGIC_GENERATED_PATH- Path to generated code fileTOKEN_MANAGER_ELF_PATH- Path to each program's.sofileGOVERNANCE_ELF_PATH- etc.
Your hand-written src/lib.rs includes the generated code:
// src/lib.rs (hand-written)
//! ELF binaries for my Solana programs.
include!(env!("ELF_MAGIC_GENERATED_PATH"));your-workspace/
├── target/
│ └── deploy/ # Built .so files
│ ├── token_manager.so # Built by cargo build-sbf
│ └── governance.so
├── my-elves/ # Your ELF crate
│ ├── build.rs # elf_magic::build()
│ ├── Cargo.toml
│ ├── src/lib.rs # Hand-written
│ └── target/
│ └── debug/
│ └── build/
│ └── my-elves-*/
│ └── out/
│ └── generated.rs # Generated code
└── programs/
├── token-manager/
└── governance/
This follows the same pattern as major Rust codegen tools:
- bindgen generates bindings to
$OUT_DIR - prost generates protobuf code to
$OUT_DIR - sqlx generates query metadata to
$OUT_DIR
- No generated files in
src/ - No
.gitignoreentries needed - No conflicts with Rust Analyzer
elf-magic tracks:
- Source file changes in program directories
- Cargo.toml changes
- Build script changes
Only rebuilds what actually changed.
Previous versions that wrote to src/lib.rs had race conditions:
- Rust Analyzer would scan half-written files
- Multiple builds could conflict
- Required
--allow-dirtyfor publishing
The $OUT_DIR pattern eliminates these issues.
elf-magic reads your package metadata to determine the mode:
[package.metadata.elf-magic]
mode = "magic" # or "permissive" or "laser-eyes"No metadata = Magic Mode (default).
Depending on mode:
- Magic Mode - Single workspace, auto-discovery
- Permissive Mode - Multi-workspace with exclusions
- Laser Eyes Mode - Explicit target lists
For modes with exclusions/inclusions, elf-magic applies glob patterns:
deny = ["target:test*", "package:*-demo"]Patterns are matched against target names, package names, and paths.
If a program fails to build, it's excluded from generated code:
// Program 'broken-program' failed to build - excluded from generated code
pub const WORKING_PROGRAM_ELF: &[u8] = include_bytes!(env!("WORKING_PROGRAM_ELF_PATH"));
pub fn elves() -> Vec<(&'static str, &'static [u8])> {
vec![
("working_program", WORKING_PROGRAM_ELF),
// 'broken_program' not included
]
}Build errors are shown in console output but don't fail the overall build.
If cargo build-sbf is not available, elf-magic provides helpful error messages pointing to Solana CLI installation instructions.
You can control elf-magic behavior with environment variables:
ELF_MAGIC_VERBOSE=1- Enable verbose loggingELF_MAGIC_CACHE_DIR- Override cache directoryELF_MAGIC_NO_CACHE=1- Disable incremental builds
View the generated code:
# Find the generated file
find target -name "generated.rs" -path "*/out/*"
# View contents
cat target/debug/build/my-elves-*/out/generated.rsEnable verbose output:
ELF_MAGIC_VERBOSE=1 cargo buildThis shows:
- Program discovery details
- Build commands executed
- Cache hit/miss information
- Generated file paths
Clear cache to force full rebuild:
cargo clean
cargo buildFirst build:
- Discovers programs: ~100ms
- Builds N programs: ~30s per program
- Generates code: ~10ms
Subsequent builds (no changes):
- Cache validation: ~50ms
- Code generation: ~10ms
elf-magic caches:
- Program metadata
- Built
.sofiles - File modification times
Cache keys include source file hashes, so changes are automatically detected.
Manual approach:
cargo build-sbf --package token-manager
cargo build-sbf --package governance
# Manually copy .so files and write includes...elf-magic approach:
cargo build # Handles everything automaticallyCustom build script:
// Lots of manual workspace discovery
// Manual cargo build-sbf invocation
// Manual file tracking for incremental builds
// Manual code generationelf-magic:
fn main() { elf_magic::build().unwrap(); }Planned improvements:
- Parallel builds - Build multiple programs simultaneously
- Cross-compilation - Support different target platforms
- Custom builders - Plugin system for non-Solana programs
- Binary optimization - Automatic UPX compression, etc.