Aprende Rust · 5 min
build.rs: los scripts de compilación de Cargo
Qué es un build script, para qué sirve y cómo inyectar metadatos de versión en tu binario.
Cuando Cargo encuentra un archivo build.rs en la raíz de tu paquete, lo trata de forma especial: lo compila a un ejecutable, lo corre y recién después compila el resto del crate. Ese script no forma parte de tu programa; es un ayudante que corre en tu máquina (o en la de integración continua) durante el build. Comunica sus resultados a Cargo imprimiendo líneas con un formato acordado en la salida estándar.
Para qué sirve
Un build script resuelve tareas que no se pueden expresar en el código fuente normal:
- Generar código antes de compilar (por ejemplo, a partir de un esquema o un archivo de definición).
- Compilar y enlazar bibliotecas nativas en C o C++ que tu crate necesita.
- Detectar capacidades del sistema donde se compila (una librería presente, una versión, una característica).
- Inyectar metadatos de versión en el binario: el hash del commit de git, la fecha de compilación, la arquitectura de destino.
Este último caso es el más común en servicios y herramientas de línea de comandos, porque permite que un --version diga exactamente de qué commit salió el binario.
Las directivas que imprime
El script se comunica con Cargo escribiendo líneas en stdout. Estas son las más usadas:
| Directiva | Qué hace |
|---|---|
cargo:rustc-env=CLAVE=valor | Define una variable de entorno visible en compilación, legible con env!("CLAVE"). |
cargo:rustc-link-lib=NOMBRE | Enlaza una biblioteca nativa (pasa -l a rustc). |
cargo:rustc-link-search=RUTA | Agrega una carpeta donde buscar bibliotecas (pasa -L). |
cargo:rerun-if-changed=RUTA | Vuelve a ejecutar el script solo si cambia ese archivo o carpeta. |
cargo:rerun-if-env-changed=NOMBRE | Vuelve a ejecutar el script solo si cambia esa variable de entorno. |
cargo:warning=MENSAJE | Muestra una advertencia al terminar el script. |
La directiva rerun-if-changed es clave para el rendimiento. Si no declaras ninguna condición, Cargo vuelve a correr el script casi en cada build. Al declarar de qué depende, evitas recompilaciones innecesarias.
Inyectar versión: ejemplo completo
Este build.rs inyecta el hash de git, la fecha de compilación y la arquitectura. Respeta SOURCE_DATE_EPOCH para builds reproducibles: si esa variable está definida, usa esa fecha en lugar de la hora actual.
// build.rs
fn main() {
// Hash corto del commit actual.
let git_hash = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_else(|| "desconocido".to_string());
println!("cargo:rustc-env=GIT_HASH={git_hash}");
// Fecha de compilación. Respeta SOURCE_DATE_EPOCH (builds reproducibles).
let build_date = match std::env::var("SOURCE_DATE_EPOCH") {
Ok(epoch) => {
let secs: i64 = epoch.parse().unwrap_or(0);
chrono::DateTime::from_timestamp(secs, 0)
.unwrap_or_default()
.format("%Y-%m-%d")
.to_string()
}
Err(_) => chrono::Utc::now().format("%Y-%m-%d").to_string(),
};
println!("cargo:rustc-env=BUILD_DATE={build_date}");
// Arquitectura de destino (Cargo la expone al script).
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
println!("cargo:rustc-env=ARCH={arch}");
// Recompila solo cuando cambia el commit o la fecha fijada.
println!("cargo:rerun-if-changed=.git/HEAD");
println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH");
}
La fecha usa la crate chrono, declarada como dependencia solo de build:
[build-dependencies]
chrono = "0.4"
En tu programa consumes esos valores con el macro env!, que los lee en tiempo de compilación:
const GIT_HASH: &str = env!("GIT_HASH");
const BUILD_DATE: &str = env!("BUILD_DATE");
const ARCH: &str = env!("ARCH");
fn main() {
println!("compilado {GIT_HASH} ({ARCH}) el {BUILD_DATE}");
}
build.rs, el script imprime la versión, rustc la lee con env!() y queda grabada en el binario.Buenas prácticas y advertencias
- Es código Rust normal. Puede tener dependencias, leer archivos y ejecutar programas, pero corre en la máquina que compila, no donde se ejecuta tu binario.
- Mantenlo rápido. Se ejecuta durante el build; un script lento lo vuelve lento para todo el equipo.
- Declara tus dependencias con
rerun-if-changed. Sin eso, Cargo lo corre de más. - Cuidado con la red. Un script que descarga algo vuelve el build frágil y no reproducible. Evítalo salvo que sea imprescindible.
- Recuerda el orden. El script corre antes del crate, así que no puede depender de código de tu propio crate.
Cuándo usarlo
Usa build.rs cuando necesites preparar algo que el código fuente no puede expresar por sí solo: generar código, enlazar bibliotecas nativas o grabar metadatos de versión. Para la mayoría de proyectos, inyectar el hash de git y la fecha es el caso que más vale la pena, y es barato. Si tu necesidad es solo compilar código C, considera antes la crate cc, que encapsula ese trabajo.
Para ubicar build.rs entre el resto de tu flujo, revisa la caja de herramientas.
Fuentes
- Build Scripts - The Cargo BookThe Cargo Book
- Build Script Examples - The Cargo BookThe Cargo Book