Cuando trabajas con Zig, tu proyecto necesita una manera de compilar y enlazar el código fuente. En Zig esto se gestiona a través del sistema de construcción incorporado, que se configura con un archivo llamado build.zig. Uno de los conceptos fundamentales dentro de este sistema es el de target, que representa un conjunto de parámetros que definen para qué arquitectura, sistema operativo y modo de ejecución se compilará tu programa.
1. ¿Qué es un target en Zig?
Un target en Zig es una abstracción que describe las características del entorno donde se ejecutará el binario final. Estas características incluyen:
- Arquitectura del procesador (x86, x86_64, arm, aarch64, riscv64, etc.)
- Sistema operativo (linux, windows, macos, freestanding)
- Modo de enlace (static o dynamic)
- Modo de ejecución (debug o release-fast / release-small)
- Opciones adicionales como la presencia de libc o el uso de
-fno-builtin
El target se construye a partir de la información de la máquina donde se ejecuta el compilador, pero también puedes especificar uno explícitamente para compilar para otra plataforma (cross-compilation).
2. Construyendo un target en el archivo build.zig
El archivo build.zig contiene una función build que el sistema de construcción llama para preparar el proceso de compilación. Dentro de esta función, se crean Builder y Project y se definen los targets. A continuación se muestra un ejemplo completo con comentarios explicativos:
const std = @import("std");
// Esta función es la entrada del sistema de construcción. No debe
// renombrarse; Zig busca automáticamente una función llamada `build`.
pub fn build(b: *std.Build) void {
// 1. Crear una instancia de Builder que gestiona todo el proceso de build.
// `b` ya es un pointer a la estructura proporcionada por Zig.
// 2. Definir un proyecto llamado "hello". El nombre es arbitrario y
// se usa para identificar la salida y los archivos temporales.
const hello = b.addExecutable("hello", "src/main.zig");
// 3. Establecer la configuración del target. Aquí usamos el target
// por defecto (la plataforma en la que se ejecuta el build) y
// especificamos que el modo de enlace será estático.
hello.setTarget(b.standardTargetOptions(.{}));
hello.setBuildMode(b.standardReleaseOptions());
// 4. Opcional: si quisiéramos cross-compile para, por ejemplo,
// Linux x86_64, podríamos hacer algo así:
// const customTarget = std.Target{
// .cpu_arch = .x86_64,
// .os_tag = .linux,
// .abi = .gnu,
// };
// hello.setTarget(.{ .value = customTarget });
// 5. Añadir dependencias (si las hubiera). En este caso no hay.
// 6. Definir la salida. Esto indica que el binario se escribirá
// en el directorio de salida del build.
hello.install();
// 7. Si quieres crear un target de prueba, podrías usar:
// const test_step = b.step("test", "Run unit tests");
// test_step.dependOn(&hello.step);
}
Desglose de cada línea:
const std = @import("std");importa el estándar de Zig.pub fn build(b: *std.Build) void {es la firma obligatoria.const hello = b.addExecutable("hello", "src/main.zig");crea un objetivo de compilación de tipo ejecutable llamado “hello” a partir del archivosrc/main.zig.hello.setTarget(b.standardTargetOptions(.{}));asigna el target predeterminado (plataforma local) a la compilación.hello.setBuildMode(b.standardReleaseOptions());selecciona la configuración de construcción (debug, release-fast, release-small, etc.).- El bloque comentado muestra cómo cambiar manualmente el target para cross-compilación.
hello.install();indica que el binario debe instalarse en la ruta de salida del build.
3. Variaciones de target: Release vs Debug
La elección del modo de build afecta el tamaño, el rendimiento y la depuración. Zig ofrece tres modos estándar:
- Debug: No optimiza y añade información de depuración.
- Release-fast: Optimiza para velocidad de ejecución.
- Release-small: Optimiza para menor tamaño de binario.
Para elegir uno, simplemente cambia la llamada a setBuildMode:
// Modo de depuración
hello.setBuildMode(.debug);
// Modo de producción rápido
hello.setBuildMode(.release_fast);
// Modo de producción compacto
hello.setBuildMode(.release_small);
4. Cross‑compilación: Compilar para otra plataforma
Imagina que desarrollas en Windows pero quieres compilar un ejecutable para Linux. Con Zig, no necesitas herramientas externas; basta con definir un std.Target con la arquitectura y el OS deseado:
// Definir el target Linux x86_64 con ABI gnu
const linuxTarget = std.Target{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .gnu,
};
// Asignar el target al ejecutable
hello.setTarget(.{ .value = linuxTarget });
// Cambiar el modo de enlace a estático (opcional)
hello.setLinkMode(.static);
Con estos ajustes, Zig descargará automáticamente el binario cruzado apropiado y compilará el código para Linux.
5. Ejemplo práctico completo
A continuación, un proyecto mínimo que demuestra todas las piezas:
Archivo build.zig:
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable("hello", "src/main.zig");
// Target por defecto (plataforma local)
exe.setTarget(b.standardTargetOptions(.{}));
// Modo Release-fast
exe.setBuildMode(b.standardReleaseOptions(.{
.optimize = .ReleaseFast,
}));
exe.install();
}
Archivo src/main.zig:
const std = @import("std");
pub fn main() !void {
// 1. Obtener el objeto de salida estándar (stdout)
const stdout = std.io.getStdOut().writer();
// 2. Escribir una línea de texto. La llamada a `print` devuelve
// un `Error!void`, por eso usamos `!` y `?` para propagar errores.
try stdout.print("¡Hola, Mundo!\\n", .{});
// 3. Retornar con éxito. En Zig, `main` debe devolver `!void`,
// lo que significa "puede fallar con un Error".
}
Pasos para compilar y ejecutar:
zig build— compila el proyecto según las opciones de build.- El binario se coloca en
