¡Hola, entusiastas de Zig! 👋 En nuestra publicación anterior, dimos nuestros primeros pasos en el mundo de Zig. Hoy, vamos a adentrarnos en el corazón de la gestión de proyectos de Zig: el sistema de construcción de Zig. ¡Empecemos! 🎉
¿Qué es el sistema de construcción de Zig? 🤔
El sistema de construcción de Zig es una herramienta poderosa e incorporada que te ayuda a gestionar, compilar, probar y enlazar tus proyectos. Simplifica procesos de construcción complejos y proporciona una compilación cruzada sin interrupciones, permitiéndote apuntar a diferentes plataformas con facilidad.
Empezando con build.zig
🚀
Para aprovechar el sistema de construcción de Zig, necesitas crear un archivo build.zig
en el directorio raíz de tu proyecto.
El conjunto de herramientas de Zig (toolchain) llama a la función build(b: *std.Build) void
de build.zig
. El parámetro b
se utiliza para configurar y definir el proceso de construcción mediante pasos o instrucciones que no formen ciclos o bucles.
Por ejemplo, podemos crear nuestro propio paso my-step
:
⚠️ ¡Atención, programador! Este post utiliza la versión 0.11.0-dev.3971 de Zig
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
const my_step = b.step("my-step", "Este paso es mi paso");
_ = my_step;
}
Este paso aparecerá en la ayuda si ejecutamos zig build --help
, y aunque no hace nada, hace mucha ilusión.
$ zig build --help
Usage: zig build [steps] [options]
Steps:
install (default) Copy build artifacts to prefix path
uninstall Remove build artifacts from prefix path
my-step Este paso es mi paso
[...]
Project-Specific Options:
(none)
[...]
También podemos observar en la ayuda una sección Project-Specific Options
, que por ahora aparece vacía.
Añadiendo el primer paso 👣
Zig viene equipado con algunos pasos listos para usar. Uno de los más interesantes es addExecutable
, que permite compilar un ejecutable o librería.
Si añadimos este main.zig
al directorio:
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Hola otra vez\n", .{});
}
Y definimos build.zig
como:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "my-executable",
.root_source_file = .{ .path = "main.zig" },
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
}
Cuando ejecutemos el comando zig build
se compilará nuestro ejecutable. Estas son las líneas principales:
-
const target = b.standardTargetOptions(.{})
: Define las opciones de destino que estarán disponibles y cual será la de defecto, lo que permite al usuario seleccionar para qué plataforma se va a construir el código. -
const optimize = b.standardOptimizeOption(.{})
: Define las opciones de optimización estándar, lo que permite al usuario seleccionar el nivel de optimización para la compilación. -
const exe = b.addExecutable(.)
: Añade un ejecutable al proceso de construcción. Los parámetros definen cómo se construirá el ejecutable, incluyendo el archivo fuente principal, las opciones de destino y las opciones de optimización. -
b.installArtifact(exe)
: Esta línea declara que el ejecutable creado en el paso anterior debe ser instalado en la ubicación estándar cuando se realiza el paso de instalación.
Si volvemos a ejecutar zig build --help
observaremos como ahora la sección Project-Specific Options
muestra opciones para establecer la plataforma, características de la cpu y la optimización desde línea de comandos.
Project-Specific Options:
-Dtarget=[string] The CPU architecture, OS, and ABI to build for
-Dcpu=[string] Target CPU features to add or subtract
-Doptimize=[enum] Prioritize performance, safety, or binary size (-O flag)
Supported Values:
Debug
ReleaseSafe
ReleaseFast
ReleaseSmall
Al lanzar zig build
se generarán dos directorios importantes:
- zig-cache: Este directorio contiene artefactos de construcción intermedios, como archivos de objeto, y es utilizado por el sistema de construcción de Zig para almacenar en caché los resultados de la construcción y acelerar las compilaciones posteriores.
- zig-out: Este directorio almacena la salida final de tu proceso de construcción, incluyendo ejecutables, bibliotecas y otros binarios.
Compilación cruzada hecha fácil 🌉
Una de las características más poderosas del sistema de construcción de Zig es su capacidad para compilar de forma cruzada tus proyectos. Para ello simplemente proporciona la opción --target
cuando ejecutes zig build
:
zig build -Dtarget=aarch64-linux-gnu
Este comando compila tu proyecto para la plataforma objetivo especificada, manejando todas las complejidades por ti. 🎉
$ file zig-out/bin/executable
zig-out/bin/executable: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped
Añadiendo más pasos 👣👣
Ahora que sabemos como funciona podemos añadir otros dos pasos, uno para ejecutar el programa y otro para lanzar los tests:
const std = @import("std");
pub fn build(b: *std.Build) void {
// compilación
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "executable",
.root_source_file = .{ .path = "main.zig" },
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
// ejecución
const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| {
run_cmd.addArgs(args);
}
run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
// tests
const unit_tests = b.addTest(.{
.root_source_file = .{ .path = "main_test.zig" },
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
}
En este código podemos ver
-
El bloque
if (b.args) |args| {...}
: Añade cualquier argumento pasado al comando de ejecución. -
const run_step = b.step("run", "Run the app")
: Crea un paso de construcción que ejecuta el comando de ejecución. -
const unit_tests = b.addTest(.)
yconst run_unit_tests = b.addRunArtifact(unit_tests)
: Crea un paso para las pruebas unitarias. Este paso construye el ejecutable de prueba pero no lo ejecuta. -
const test_step = b.step("test", "Run unit tests")
: Este paso ejecuta las pruebas unitarias.
Para añadir los test añadimos el archivo main_test.zig
:
const std = @import("std");
test "simple test" {
try std.testing.expect(addOne(41) == 42);
}
fn addOne(number: i32) i32 {
return number + 1;
}
Para crear un caso de prueba en Zig, usa la palabra clave test
, seguida de un nombre de prueba y un bloque de código.
Para lanzar los test usamos el comando zig build test
. Si todo va bien no deberíamos ver ningún error, el comando es realmente muy silencioso.
¿Se puede más fácil? 😅
Crear un archivo build.zig
puede ser un poco tedioso, más aun cuando todavía no dominamos el lenguaje. Pero no tienes por qué empezar desde cero. El comando zig init-exe
te generará las carpetas y archivos necesarios para compilar, ejecutar y pasar los test de un proyecto de básico de ejemplo en el directorio donde te encuentres.
¿Qué sigue? 🌟
¡Eso es todo! Ahora tienes un sólido entendimiento del sistema de construcción de Zig y puedes usarlo para gestionar, construir y compilar de forma cruzada tus proyectos con facilidad. 💡
¡Feliz codificación y nos vemos en la próxima publicación! 👩💻👨💻🚀