馃 Haciendo uso de Builds Scripts.
Algunos paquetes necesitan compilar c贸digo de terceros que no sea de Rust, por ejemplo, bibliotecas C. Otros necesitan funciones como la generaci贸n de c贸digo antes de la construcci贸n 贸 cualquier otra cosa antes de construir.
Cargo no pretende sustituir a otras herramientas que est谩n bien optimizadas para estas tareas, pero se integra con ellas con scripts de construcci贸n personalizados. Colocar un archivo llamado build.rs en la ra铆z de un paquete har谩 que Cargo compile ese script y lo ejecute justo antes de construir el paquete.
Texto tomado de The Cargo Book.
En nuestro Cargo.toml agregagamos semver como dependencia de construcci贸n, es importante aclarar que no debe confundirse con el crate "cargo-semver".
Por defecto Build Scripts y build.rs no tienen acceso a ninguna libreria/crate, para que se pueda utilizar semver, hay que definirla como una dependencia de construcci贸n y definir el archivo inicial de construcci贸n (build.rs).
# Cargo.toml
...
[package]
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
semver = "1.0.12"
Iniciando
En este ejemplo, vamos a generar un archivo (build_version.conf, en nuestro caso) para almacenar la versi贸n incrementada, cada vez que nuestro proyecto sea compilado.
Creamos un archivo en la ra铆z de nuestro proyecto llamado build.rs
Agregamos algunos crates para manejar la creaci贸n de archivos y el versionamiento sem谩ntico de nuestra aplicaci贸n.
// build.rs
use semver::{Prerelease, Version};
use std::fs;
use std::io::Write;
use std::path::Path;
Definimos una constante que contiene la ruta de nuestro archivo.
// build.rs
const BUILD_FILE_NAME: &str = "target/build_version.conf";
Funciones para leer y crear el archivo.
// build.rs
fn read_file() -> String {
match fs::read_to_string(BUILD_FILE_NAME) {
Ok(c) => c,
Err(_) => String::from("Error reading file"),
}
}
fn save_file(data: String) {
let mut conf_file = fs::File::create(BUILD_FILE_NAME).unwrap();
conf_file
.write_all(data.as_bytes())
.expect("Error while writing to file");
}
Incrementando el n煤mero de compilaci贸n en cada construcci贸n 馃殮
La funci贸n "generate_version", toma la versi贸n actual (inicialmente del archivo Cargo.toml) e incrementa el n煤mero de compilaci贸n, generando un Prerelease (valiendose del crate semver) utilizado para generar el build.
Veamos un poco m谩s de cerca esto. En nuestro archivo Cargo.toml tenemos algo como esto:
La versi贸n indica "1.0.0"
# Cargo.toml
...
[package]
name = "awesome-app"
version = "1.0.0"
edition = "2018"
build = "build.rs"
Utilizando el crate semver parseamos el numero de versi贸n("1.0.0"), generamos un prerelase, un identificador opcional de pre-lanzamiento en la cadena de versi贸n, Ejemplos:
- 0.7.0-alpha.1
- 1.0.0-beta.8
- 1.0.0-rc.0
- 0.11.0-shitshow
- 0.0.0-reserved
etc, etc,.. el nombre lo defines t煤, para nuestro caso usaremos el prefijo "build."
Para finalizar esta parte, una vez se genere una construcci贸n, la versi贸n quedar谩 de la siguiente manera:
versi贸n inicial: 1.0.0
despues de una construcci贸n: 1.0.0-build.1, incrementando el "build", en cada construcci贸n. 馃槃
// build.rs
fn generate_version(pkg_version: &str) -> String {
let mut current_version = Version::parse(pkg_version.trim()).unwrap();
let mut new_build: i32 = 1;
if !current_version.pre.is_empty() {
let current_build = current_version.pre.as_str();
let (_, build_number) = current_build.split_at(6); // extract "build."
println!("cargo:warning=current build_number: {}", build_number);
new_build = build_number.parse::<i32>().unwrap_or(0);
new_build += 1;
}
let build_text = format!("build.{}", new_build);
current_version.pre = Prerelease::new(build_text.as_str()).unwrap();
println!("cargo:warning=generating build version: {}", build_text);
current_version.to_string()
}
Generando el archivo o actualiz谩ndolo seg煤n sea el caso
La funcion "update_build_version", revisa si nuestro archivo de construcci贸n existe, en ese caso leer谩 el archivo y actualizar谩 la versi贸n, en caso contrario generar谩 un nuevo archivo, con la informaci贸n de la variable CARGO_PKG_VERSION que 茅sta a su vez, extrae del archivo Cargo.toml
// build.rs
fn update_build_version() {
let content_file: String;
if Path::new(BUILD_FILE_NAME).exists() {
content_file = read_file();
} else {
let pkg_version = env!("CARGO_PKG_VERSION");
content_file = String::from(pkg_version);
}
let result = generate_version(content_file.as_str());
save_file(result);
}
Finalizando
En nuestra funci贸n main, empezamos el proceso de generar el build en cada compilaci贸n, llamando a la funci贸n update_build_version.
La instrucci贸n rerun-if-changed=build.rs en el println! le indica a Cargo cu谩ndo debe volver a ejecutar el script, en este caso si el archivo build.rs es modificado. M谩s info
// build.rs
fn main() {
println!("cargo:rerun-if-changed=build.rs");
update_build_version();
}
Toque final
Nuestro archivo build.rs debe lucir as铆:
// build.rs
use semver::{Prerelease, Version};
use std::fs;
use std::io::Write;
use std::path::Path;
const BUILD_FILE_NAME: &str = "target/build_version.conf";
fn read_file() -> String {
match fs::read_to_string(BUILD_FILE_NAME) {
Ok(c) => c,
Err(_) => String::from("Error reading file"),
}
}
fn save_file(data: String) {
let mut conf_file = fs::File::create(BUILD_FILE_NAME).unwrap();
conf_file
.write_all(data.as_bytes())
.expect("Error while writing to file");
}
fn generate_version(pkg_version: &str) -> String {
let mut current_version = Version::parse(pkg_version.trim()).unwrap();
let mut new_build: i32 = 1;
if !current_version.pre.is_empty() {
let current_build = current_version.pre.as_str();
let (_, build_number) = current_build.split_at(6); // extract "build."
println!("cargo:warning=current build_number: {}", build_number);
new_build = build_number.parse::<i32>().unwrap_or(0);
new_build += 1;
}
let build_text = format!("build.{}", new_build);
current_version.pre = Prerelease::new(build_text.as_str()).unwrap();
println!("cargo:warning=generating build version: {}", build_text);
current_version.to_string()
}
fn update_build_version() {
let content_file: String;
if Path::new(BUILD_FILE_NAME).exists() {
content_file = read_file();
} else {
let pkg_version = env!("CARGO_PKG_VERSION");
content_file = String::from(pkg_version);
}
let result = generate_version(content_file.as_str());
save_file(result);
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
update_build_version();
}
A tener en cuenta
-
No olvides definir la llave "build-dependencies" en el archivo Cargo.toml y en la llave "package" el campo build.
-
El archivo de construcci贸n se encuentra localizado en: "target/build_version.conf", aqu铆 no se modifica el archivo Cargo.toml, por lo que si haces una limpieza (ej:
cargo clean
), el n煤mero de compilaci贸n se perder谩. -
No, no recomendamos usar el archivo build.rs para modificar el archivo Cargo.toml, leyendo el archivo y extrayendo la informaci贸n de la versi贸n, ya que causa problemas al lazar una construcci贸n, modificar el archivo Cargo.toml, actualizarlo y compilar tu aplicaci贸n.
-
S铆 lo que quieres, es modificar el archivo Cargo.toml y actualizar la versi贸n, utilizando versionamiento sem谩ntico, lo mejor ser铆a utilizar el crate cargo-semver
-
S铆 se invoca directamente a println! en el archivo build.rs, esta no imprimir谩 nada por la consola, a menos que se especifique "cargo:warning", ejemplo:
println!("cargo:warning=Mensaje aqui...");
- Revisa el apartado de "Environment Variables" de The Cargo Book, para m谩s informaci贸n de las variables de entorno.