zig-build-system

zig build: sistema de construcción y automatización en Zig

  • 4 min

zig build es el sistema de construcción integrado que permite definir proyectos usando código Zig. Si has trabajado con C o C++, seguramente ya conoces Make y CMake.

Escribes tu código, pero luego tienes que escribir un Makefile con una sintaxis críptica de los años 70. O peor, tienes que escribir un CMakeLists.txt con un lenguaje de scripting propio que parece diseñado para confundir. Y si quieres que funcione en Windows y Linux a la vez… buena suerte.

Zig propone describir el grafo de construcción con su propia API.

En Zig, no existe un lenguaje de configuración separado. El “script” que define cómo se compila tu proyecto es un programa escrito en Zig.

Esto significa que tienes:

  1. Autocompletado y tipado estático en tu build script.
  2. Acceso a toda la librería estándar (manipular archivos, ejecutar procesos, etc.).
  3. Una base portable; las bibliotecas y herramientas externas siguen dependiendo del entorno que requiera el proyecto.

El archivo build.zig

Cuando creas un proyecto nuevo con zig init, verás que se genera un archivo build.zig en la raíz. Este archivo es el punto de entrada del sistema de construcción.

Su estructura básica es una función pública build que recibe un objeto std.Build.

const std = @import("std");

pub fn build(b: *std.Build) void {
    // Aquí definimos CÓMO se construye nuestro proyecto
}
Copied!

Este objeto b es nuestro director de orquesta. A través de él, crearemos un grafo de pasos (compilar, instalar, ejecutar tests, generar docs).

Anatomía de un build estándar

Vamos a descomponer un build.zig típico línea por línea.

const std = @import("std");

pub fn build(b: *std.Build) void {
    // 1. Opciones estándar (Target y Optimize)
    // Permite al usuario pasar -Dtarget=... y -Doptimize=... por línea de comandos
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // 2. Definir el ejecutable
    // Decimos: "Quiero compilar un exe llamado 'mi-app' desde 'src/main.zig'"
    const exe = b.addExecutable(.{
        .name = "mi-app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    // 3. Paso de Instalación
    // Esto dice: "Copia el resultado a la carpeta zig-out/bin"
    b.installArtifact(exe);
}
Copied!

Opciones del usuario (-D)

Las líneas standardTargetOptions y standardOptimizeOption nos ahorran bastante trabajo. Con solo ponerlas, nuestro script acepta argumentos potentes desde la terminal:

  • Compilar para Release: zig build -Doptimize=ReleaseFast
  • Compilar para Windows desde Linux (Cross-compile): zig build -Dtarget=x86_64-windows

¡Sin configurar toolchains ni variables de entorno extrañas! Zig se encarga de todo.

Pasos y dependencias

El sistema de build de Zig se basa en Steps. Un paso puede depender de otro.

Por ejemplo, el comando zig build run (que compila y ejecuta) no aparece de la nada. Es un paso que nosotros definimos en el build.zig.

    // ... (continuación del código anterior)

    // 4. Crear el comando de ejecución
    const run_cmd = b.addRunArtifact(exe);
    
    // Propagamos al programa los argumentos escritos después de --
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    // 5. Exponer el paso al usuario
    const run_step = b.step("run", "Ejecutar la aplicación");
    run_step.dependOn(&run_cmd.step);
Copied!

Gracias a esto, cuando escribimos zig build --help, veremos nuestro paso run documentado.

Compilar C y C++ con zig build

zig build no solo organiza código Zig; también puede compilar fuentes C y C++ mediante el toolchain incluido.

Si tienes un proyecto mixto (o puramente C), puedes usar build.zig para gestionarlo.

    const exe_c = b.addExecutable(.{
        .name = "app-en-c",
        .root_module = b.createModule(.{
            .target = target,
            .optimize = optimize,
            .link_libc = true,
        }),
    });
    
    // Añadimos archivos fuente de C
    exe_c.root_module.addCSourceFile(.{
        .file = b.path("src/main.c"),
        .flags = &[_][]const u8{ "-Wall", "-Wextra" }, // Flags de GCC/Clang
    });
    
    b.installArtifact(exe_c);
Copied!

Esto compilará tu código C usando el compilador zig cc integrado, con todas las ventajas de cacheo y compilación cruzada de Zig.

Gestión de dependencias

Desde la versión 0.11, Zig incluye un gestor de paquetes oficial. zig build se integra con un archivo llamado build.zig.zon (Zig Object Notation).

Para usar una librería externa:

  1. La declaramos en build.zig.zon.
  2. En build.zig, la “invocamos”:
    // Obtenemos la dependencia 'zap' (un servidor web, por ejemplo)
    const zap_dep = b.dependency("zap", .{
        .target = target,
        .optimize = optimize,
    });
    
    // Añadimos el módulo de zap a nuestro ejecutable
    exe.root_module.addImport("zap", zap_dep.module("zap"));
Copied!

Zig obtiene la dependencia declarada, verifica su hash y la incorpora al grafo de construcción. Las dependencias ya disponibles quedan en la caché local.