Zig Build System: Guia Prático do build.zig

O Zig Build System é uma das features mais poderosas da linguagem Zig. Diferente de C e C++ que dependem de ferramentas externas como Make, CMake, Autotools ou Meson, Zig vem com um sistema de build integrado, escrito na própria linguagem. Isso significa que você usa Zig para configurar como Zig compila seu projeto — sem DSLs, sem dependências externas, sem “mágica”.

Neste tutorial completo, você vai aprender tudo sobre o build.zig: desde a estrutura básica até cenários avançados como cross-compilation, integração com C e build steps customizados.

Pré-requisito: Ter o Zig instalado. Se ainda não instalou, siga nosso Guia de Instalação do Zig.

O que é o Zig Build System? Por que Não Usar Make/CMake?

O Problema dos Build Systems Tradicionais

Se você vem de C ou C++, provavelmente já sofreu com ferramentas de build:

FerramentaProblema
MakeSintaxe arcaica, tabs obrigatórios, difícil de portar entre plataformas
CMakeLinguagem própria (CMakeLists.txt) complexa e verbosa
AutotoolsConfigure/Makefile.am — extremamente complexo
MesonDependência de Python, outra DSL para aprender
BazelPesado, complexo, curva de aprendizado íngreme

Todos esses sistemas compartilham um problema fundamental: são ferramentas separadas da linguagem, com suas próprias linguagens de configuração, bugs e limitações.

A Solução do Zig

O Zig Build System resolve isso de forma elegante:

  • Escrito em Zig — é código Zig normal, não uma DSL
  • Integrado ao compilador — nenhuma ferramenta externa necessária
  • Cross-compilation nativa — compila para qualquer plataforma sem configuração extra
  • Cache inteligente — recompila apenas o que mudou
  • Execução concorrente — build steps rodam em paralelo automaticamente
  • Compila C/C++ também — substitui gcc/clang como compilador C/C++
# Sem Zig: você precisa de TUDO isso
sudo apt install build-essential cmake autotools-dev python3-meson ninja-build

# Com Zig: uma única ferramenta
zig build

Anatomia do build.zig — Estrutura Básica

O build.zig é um arquivo Zig normal com uma função pública especial chamada build. O Zig Build System chama essa função passando um ponteiro *std.Build que permite configurar o processo de compilação.

Estrutura Mínima

const std = @import("std");

pub fn build(b: *std.Build) void {
    // Aqui você configura como o projeto é compilado
    const exe = b.addExecutable(.{
        .name = "meu-programa",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = b.graph.host,
        }),
    });

    b.installArtifact(exe);
}

Vamos decompor cada parte:

ElementoDescrição
pub fn build(b: *std.Build)Ponto de entrada do build system
b.addExecutable(...)Cria um executável
.name = "meu-programa"Nome do binário resultante
b.createModule(...)Cria um módulo de compilação
.root_source_file = b.path("src/main.zig")Arquivo raiz do código
.target = b.graph.hostCompilar para a máquina atual
b.installArtifact(exe)Registra o executável para instalação

Como o Build System Funciona

O Zig Build System é baseado em um DAG (Directed Acyclic Graph) — um grafo direcionado acíclico de “steps” (etapas):

install (step padrão)
└─ install meu-programa
   └─ compile exe meu-programa

Quando você executa zig build, o build runner:

  1. Executa a função build() para construir o grafo
  2. Resolve dependências entre steps
  3. Executa steps concorrentemente respeitando as dependências
  4. Usa cache para evitar recompilação desnecessária

Diretórios Gerados

Após zig build, dois diretórios são criados:

meu-projeto/
├── build.zig          # Configuração do build
├── src/
│   └── main.zig       # Seu código
├── .zig-cache/        # Cache de compilação (NÃO commitar)
└── zig-out/           # Artefatos de saída
    └── bin/
        └── meu-programa
DiretórioPropósitoGit?
.zig-cache/Cache de compilação (intermediários)❌ Adicionar ao .gitignore
zig-out/Binários e artefatos finais❌ Adicionar ao .gitignore

Criando um Projeto do Zero com zig init

A forma mais rápida de criar um projeto Zig é usar zig init:

mkdir meu-projeto && cd meu-projeto
zig init

Saída:

info: created build.zig
info: created build.zig.zon
info: created src/main.zig
info: created src/root.zig
info: see `zig build --help` for a menu of options

Estrutura Gerada

meu-projeto/
├── build.zig          # Configuração do build system
├── build.zig.zon      # Metadados do pacote (nome, versão, deps)
└── src/
    ├── main.zig       # Ponto de entrada do executável
    └── root.zig       # Ponto de entrada da biblioteca

O build.zig Gerado

O zig init cria um build.zig completo com as práticas recomendadas:

const std = @import("std");

pub fn build(b: *std.Build) void {
    // Opções padrão de target e otimização
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Biblioteca
    const lib = b.addLibrary(.{
        .name = "meu-projeto",
        .linkage = .static,
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/root.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    b.installArtifact(lib);

    // Executável
    const exe = b.addExecutable(.{
        .name = "meu-projeto",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    b.installArtifact(exe);

    // Step "run"
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }
    const run_step = b.step("run", "Run the application");
    run_step.dependOn(&run_cmd.step);

    // Step "test"
    const lib_unit_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/root.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);

    const exe_unit_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);

    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_lib_unit_tests.step);
    test_step.dependOn(&run_exe_unit_tests.step);
}

Comandos Básicos

Com o projeto criado, você pode usar:

# Compilar
zig build

# Compilar e executar
zig build run

# Rodar testes (veja nosso guia de [testes em Zig](/tutoriais/testes-zig/))
zig build test

# Ver sumário detalhado
zig build --summary all

# Ver opções disponíveis
zig build --help

# Passar argumentos para o programa
zig build run -- arg1 arg2

# Limpar cache
rm -rf .zig-cache zig-out

O build.zig.zon

O arquivo build.zig.zon (Zig Object Notation) contém metadados do projeto:

.{
    .name = .@"meu-projeto",
    .version = "0.1.0",
    .fingerprint = 0x...,  // hash automático
    .minimum_zig_version = "0.15.0",
    .dependencies = .{},
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}
CampoDescrição
.nameNome do pacote
.versionVersão semântica
.fingerprintHash único do pacote (gerado automaticamente)
.minimum_zig_versionVersão mínima do Zig necessária
.dependenciesDependências externas
.pathsArquivos incluídos no pacote

Configurando Targets e Modos de Build

Standard Options

As funções standardTargetOptions e standardOptimizeOption expõem opções na linha de comando:

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    b.installArtifact(exe);
}

Agora o usuário pode configurar target e otimização via -D:

# Build padrão (debug, máquina local)
zig build

# Release otimizado para velocidade
zig build -Doptimize=ReleaseFast

# Release otimizado para tamanho
zig build -Doptimize=ReleaseSmall

# Release seguro (checks em runtime)
zig build -Doptimize=ReleaseSafe

# Cross-compile para Windows
zig build -Dtarget=x86_64-windows

# Cross-compile para ARM Linux
zig build -Dtarget=aarch64-linux-gnu

Modos de Otimização

ModoDescriçãoChecks de SegurançaVelocidadeTamanho
DebugPadrão para desenvolvimento✅ Todos ativosNormalGrande
ReleaseSafeRelease com segurança✅ Todos ativosAltaMédio
ReleaseFastMáxima velocidade❌ DesativadosMáximaMédio
ReleaseSmallBinário mínimo❌ DesativadosAltaMínimo

Opções Customizadas

Você pode criar opções personalizadas com b.option:

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Opção booleana customizada
    const enable_logging = b.option(
        bool,
        "logging",
        "Habilitar logging detalhado",
    ) orelse false;

    // Opção de string customizada
    const versao = b.option(
        []const u8,
        "version",
        "Versão do aplicativo",
    ) orelse "0.1.0";

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    // Passar opções para o código via @import("config")
    const options = b.addOptions();
    options.addOption(bool, "logging_enabled", enable_logging);
    options.addOption([]const u8, "version", versao);
    exe.root_module.addOptions("config", options);

    b.installArtifact(exe);
}

No código Zig, acesse as opções:

const config = @import("config");

pub fn main() !void {
    if (config.logging_enabled) {
        std.debug.print("Versão: {s}\n", .{config.version});
    }
}

Uso na linha de comando:

zig build -Dlogging=true -Dversion=1.0.0

Adicionando Dependências (Packages)

O sistema de pacotes do Zig usa o build.zig.zon para declarar dependências.

Adicionando uma Dependência com zig fetch

O comando zig fetch --save é a forma mais fácil de adicionar uma dependência:

# Sintaxe: zig fetch --save <url-do-tarball>
zig fetch --save https://github.com/zigtools/zls/archive/refs/tags/0.13.0.tar.gz

Isso atualiza automaticamente o build.zig.zon:

.{
    .name = .@"meu-projeto",
    .version = "0.1.0",
    .dependencies = .{
        .zls = .{
            .url = "https://github.com/zigtools/zls/archive/refs/tags/0.13.0.tar.gz",
            .hash = "1220abc123...",  // hash de integridade
        },
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

Usando a Dependência no build.zig

Após declarar a dependência no build.zig.zon, use-a no build.zig:

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Obter a dependência
    const dep = b.dependency("nome-do-pacote", .{
        .target = target,
        .optimize = optimize,
    });

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    // Adicionar o módulo da dependência
    exe.root_module.addImport("nome-do-modulo", dep.module("nome-do-modulo"));

    b.installArtifact(exe);
}

Exemplo Prático: Adicionando uma Biblioteca de Logging

Vamos adicionar a biblioteca logz como exemplo:

1. Buscar a dependência:

zig fetch --save git+https://github.com/chung-leong/logz

2. Configurar no build.zig:

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    // Importar a dependência logz
    const logz = b.dependency("logz", .{
        .target = target,
        .optimize = optimize,
    });
    exe.root_module.addImport("logz", logz.module("logz"));

    b.installArtifact(exe);
}

3. Usar no código:

const logz = @import("logz");

pub fn main() !void {
    var logger = logz.Logger.init(.{});
    logger.info("Aplicação iniciada", .{});
}

Dependências Locais (Path)

Para dependências em disco (monorepos, desenvolvimento local):

// build.zig.zon
.{
    .name = .@"meu-projeto",
    .version = "0.1.0",
    .dependencies = .{
        .minha_lib = .{
            .path = "../minha-lib",
        },
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}

Cross-Compilation — Compilar para Outras Plataformas

Uma das maiores forças do Zig é a cross-compilation trivial. Enquanto em C/C++ configurar cross-compilation pode levar horas (ou dias), com Zig é um flag. Para um guia aprofundado, veja nosso tutorial dedicado de cross-compilation com Zig.

Compilar para Outra Plataforma

# Compilar para Windows (a partir de Linux/macOS)
zig build -Dtarget=x86_64-windows

# Compilar para Linux ARM (Raspberry Pi)
zig build -Dtarget=aarch64-linux-gnu

# Compilar para macOS Intel (a partir de ARM)
zig build -Dtarget=x86_64-macos

# Compilar para macOS ARM (Apple Silicon)
zig build -Dtarget=aarch64-macos

# Compilar para WebAssembly
zig build -Dtarget=wasm32-wasi

# Compilar para FreeBSD
zig build -Dtarget=x86_64-freebsd

Targets Disponíveis

Para ver todos os targets suportados:

# Listar arquiteturas suportadas
zig targets | head -50

# Ou usar zig build-exe diretamente
zig build-exe --help | grep "target"

Alguns targets populares:

TargetDescrição
x86_64-linux-gnuLinux 64-bit (glibc)
x86_64-linux-muslLinux 64-bit (musl libc, estático)
aarch64-linux-gnuLinux ARM 64-bit (Raspberry Pi 4+)
x86_64-windowsWindows 64-bit
aarch64-macosmacOS Apple Silicon
x86_64-macosmacOS Intel
wasm32-wasiWebAssembly (WASI)
riscv64-linux-gnuLinux RISC-V 64-bit

Build para Múltiplos Targets (Release)

Para projetos que precisam distribuir binários para várias plataformas, crie um step customizado:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});

    // Targets para os quais queremos compilar
    const targets: []const std.Target.Query = &.{
        .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .gnu },
        .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
        .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .gnu },
        .{ .cpu_arch = .x86_64, .os_tag = .windows },
        .{ .cpu_arch = .aarch64, .os_tag = .macos },
        .{ .cpu_arch = .x86_64, .os_tag = .macos },
    };

    // Step "release" que compila para todos os targets
    const release_step = b.step("release", "Build para todas as plataformas");

    for (targets) |t| {
        const exe = b.addExecutable(.{
            .name = "meu-app",
            .root_module = b.createModule(.{
                .root_source_file = b.path("src/main.zig"),
                .target = b.resolveTargetQuery(t),
                .optimize = optimize,
            }),
        });

        const install = b.addInstallArtifact(exe, .{});
        release_step.dependOn(&install.step);
    }
}

Uso:

# Build release para todas as plataformas de uma vez
zig build release -Doptimize=ReleaseFast --summary all

Saída:

zig-out/
└── bin/
    ├── meu-app              # Linux x86_64 (glibc)
    ├── meu-app              # Linux x86_64 (musl)
    ├── meu-app              # Linux ARM64
    ├── meu-app.exe          # Windows x86_64
    └── meu-app              # macOS (2 variantes)

Zig como Compilador C/C++ Cross-Platform

O zig cc funciona como um drop-in replacement para gcc/clang, com cross-compilation embutida:

# Compilar C para a máquina local
zig cc -o programa main.c

# Compilar C para ARM Linux (a partir de qualquer plataforma!)
zig cc -target aarch64-linux-gnu -o programa main.c

# Compilar C++ para Windows
zig c++ -target x86_64-windows -o programa.exe main.cpp

Isso é tão poderoso que empresas como Uber usam zig cc apenas como compilador C/C++ cross-platform, sem nem escrever código Zig.

Integrando Bibliotecas C no Build

Uma das maiores vantagens do Zig é a integração transparente com código C. O build.zig permite compilar e linkar código C diretamente.

Exemplo 1: Compilar Código C com Zig

Suponha que temos um arquivo C simples:

src/mathutils.c:

#include "mathutils.h"

int soma(int a, int b) {
    return a + b;
}

double raiz_quadrada(double x) {
    return __builtin_sqrt(x);
}

src/mathutils.h:

#ifndef MATHUTILS_H
#define MATHUTILS_H

int soma(int a, int b);
double raiz_quadrada(double x);

#endif

build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .link_libc = true,
        }),
    });

    // Adicionar código C
    exe.addCSourceFile(.{
        .file = b.path("src/mathutils.c"),
        .flags = &.{ "-std=c11", "-Wall" },
    });

    // Adicionar diretório de headers
    exe.addIncludePath(b.path("src"));

    b.installArtifact(exe);
}

src/main.zig — chamando o código C:

const c = @cImport({
    @cInclude("mathutils.h");
});
const std = @import("std");

pub fn main() void {
    const resultado = c.soma(10, 20);
    std.debug.print("10 + 20 = {d}\n", .{resultado});

    const raiz = c.raiz_quadrada(144.0);
    std.debug.print("sqrt(144) = {d}\n", .{raiz});
}

Exemplo 2: Linkar com Biblioteca do Sistema

Para usar bibliotecas instaladas no sistema (como zlib, OpenSSL, SQLite):

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "compressor",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    // Linkar com zlib do sistema
    exe.linkSystemLibrary("z");

    // Linkar com a libc (necessário para libs do sistema)
    exe.linkLibC();

    b.installArtifact(exe);
}

No código Zig:

const c = @cImport({
    @cInclude("zlib.h");
});
const std = @import("std");

pub fn main() void {
    // Usa funções da zlib diretamente
    const version = c.zlibVersion();
    std.debug.print("zlib versão: {s}\n", .{
        std.mem.span(version),
    });
}

Exemplo 3: Múltiplos Arquivos C

Para projetos com vários arquivos C:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "projeto-misto",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .link_libc = true,
        }),
    });

    // Adicionar múltiplos arquivos C de uma vez
    exe.addCSourceFiles(.{
        .files = &.{
            "src/parser.c",
            "src/lexer.c",
            "src/utils.c",
        },
        .flags = &.{
            "-std=c11",
            "-Wall",
            "-Wextra",
            "-O2",
        },
    });

    exe.addIncludePath(b.path("include"));

    b.installArtifact(exe);
}

Exemplo 4: Biblioteca Estática C

Criar uma biblioteca estática que mistura C e Zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Criar biblioteca estática
    const lib = b.addLibrary(.{
        .name = "minha-lib",
        .linkage = .static,
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/lib.zig"),
            .target = target,
            .optimize = optimize,
            .link_libc = true,
        }),
    });

    // Adicionar código C à biblioteca
    lib.addCSourceFile(.{
        .file = b.path("src/backend.c"),
        .flags = &.{"-std=c11"},
    });

    lib.addIncludePath(b.path("include"));

    // Instalar a biblioteca e headers
    b.installArtifact(lib);
    lib.installHeader(b.path("include/minha-lib.h"), "minha-lib.h");
}

Para um tutorial completo sobre interoperabilidade Zig-C, leia Zig para Programadores C: Guia de Migração.

Build Steps Customizados

O poder real do Zig Build System aparece quando você cria steps customizados para tarefas como geração de código, processamento de assets ou execução de ferramentas.

Adicionando um Step “run”

O step mais comum é adicionar um comando run para executar o programa compilado:

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    b.installArtifact(exe);

    // Step "run" — executa o programa compilado
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());

    // Permitir passar argumentos via linha de comando
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Executar a aplicação");
    run_step.dependOn(&run_cmd.step);
}
# Executa o programa
zig build run

# Passa argumentos
zig build run -- --porta 8080 --verbose

Executando Ferramentas do Sistema

Você pode executar qualquer ferramenta do sistema como um build step:

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    b.installArtifact(exe);

    // Step para gerar documentação
    const docs = b.addSystemCommand(&.{
        "echo", "Gerando documentação...",
    });
    const doc_step = b.step("docs", "Gerar documentação");
    doc_step.dependOn(&docs.step);

    // Step para formatar código
    const fmt = b.addSystemCommand(&.{
        "zig", "fmt", "src/",
    });
    const fmt_step = b.step("fmt", "Formatar código Zig");
    fmt_step.dependOn(&fmt.step);
}

Executando Ferramentas Escritas em Zig

Para ferramentas mais complexas, você pode compilar e executar uma ferramenta Zig como parte do build:

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Ferramenta de geração de código (compilada em Zig)
    const codegen = b.addExecutable(.{
        .name = "codegen",
        .root_module = b.createModule(.{
            .root_source_file = b.path("tools/codegen.zig"),
            .target = b.graph.host,  // roda na máquina de build
        }),
    });

    // Executar a ferramenta para gerar um arquivo
    const codegen_step = b.addRunArtifact(codegen);
    codegen_step.addArg("--input-file");
    codegen_step.addFileArg(b.path("schema/api.json"));
    codegen_step.addArg("--output-file");
    const generated = codegen_step.addOutputFileArg("generated_api.zig");

    // Usar o arquivo gerado no executável principal
    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    // O código gerado fica disponível via @import("api")
    exe.root_module.addAnonymousImport("api", .{
        .root_source_file = generated,
    });

    b.installArtifact(exe);
}

Gerando Arquivos com @embedFile

Embutir arquivos gerados no binário em tempo de compilação:

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Gerar um arquivo de versão
    const wf = b.addWriteFiles();
    _ = wf.add("version.txt", b.option(
        []const u8,
        "version",
        "Versão do aplicativo",
    ) orelse "dev");

    const exe = b.addExecutable(.{
        .name = "app",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });

    // Disponibilizar para @embedFile
    exe.root_module.addAnonymousImport("version", .{
        .root_source_file = wf.files.items[0].getPath(),
    });

    b.installArtifact(exe);
}

No código:

const version = @embedFile("version");

pub fn main() void {
    std.debug.print("Versão: {s}\n", .{version});
}

Exemplos Práticos

Exemplo 1: Projeto com Testes Completos

Um projeto real com testes de unidade para múltiplos módulos:

Estrutura:

meu-projeto/
├── build.zig
├── build.zig.zon
├── src/
│   ├── main.zig
│   ├── parser.zig
│   ├── lexer.zig
│   └── utils.zig
└── tests/
    ├── parser_test.zig
    └── lexer_test.zig

build.zig:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Módulo compartilhado
    const meu_modulo = b.createModule(.{
        .root_source_file = b.path("src/parser.zig"),
        .target = target,
        .optimize = optimize,
    });

    // === Executável ===
    const exe = b.addExecutable(.{
        .name = "meu-parser",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    exe.root_module.addImport("parser", meu_modulo);
    b.installArtifact(exe);

    // === Step "run" ===
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| run_cmd.addArgs(args);
    const run_step = b.step("run", "Executar o parser");
    run_step.dependOn(&run_cmd.step);

    // === Testes ===
    const test_step = b.step("test", "Rodar todos os testes");

    // Testes do parser
    const parser_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("tests/parser_test.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    parser_tests.root_module.addImport("parser", meu_modulo);
    const run_parser_tests = b.addRunArtifact(parser_tests);
    test_step.dependOn(&run_parser_tests.step);

    // Testes do lexer
    const lexer_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("tests/lexer_test.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    const run_lexer_tests = b.addRunArtifact(lexer_tests);
    test_step.dependOn(&run_lexer_tests.step);

    // Testes inline no código-fonte
    const src_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/parser.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    const run_src_tests = b.addRunArtifact(src_tests);
    test_step.dependOn(&run_src_tests.step);
}

Uso:

# Rodar todos os testes
zig build test --summary all

# Saída esperada:
# Build Summary: 7/7 steps succeeded
# test success
# ├─ run test (parser_test) success
# ├─ run test (lexer_test) success
# └─ run test (parser inline) success

Exemplo 2: Servidor HTTP com Múltiplos Targets

Um projeto mais realista: servidor HTTP com build para várias plataformas + Docker:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // === Executável principal ===
    const exe = b.addExecutable(.{
        .name = "servidor",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    b.installArtifact(exe);

    // === Step "run" ===
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| run_cmd.addArgs(args);
    const run_step = b.step("run", "Iniciar o servidor");
    run_step.dependOn(&run_cmd.step);

    // === Step "test" ===
    const unit_tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    const run_tests = b.addRunArtifact(unit_tests);
    const test_step = b.step("test", "Rodar testes unitários");
    test_step.dependOn(&run_tests.step);

    // === Step "release" — build para múltiplas plataformas ===
    const release_targets: []const std.Target.Query = &.{
        .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
        .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl },
        .{ .cpu_arch = .x86_64, .os_tag = .macos },
        .{ .cpu_arch = .aarch64, .os_tag = .macos },
        .{ .cpu_arch = .x86_64, .os_tag = .windows },
    };

    const release_step = b.step("release", "Build para todas as plataformas");

    for (release_targets) |t| {
        const rel_exe = b.addExecutable(.{
            .name = "servidor",
            .root_module = b.createModule(.{
                .root_source_file = b.path("src/main.zig"),
                .target = b.resolveTargetQuery(t),
                .optimize = .ReleaseSafe,
            }),
        });
        const install = b.addInstallArtifact(rel_exe, .{});
        release_step.dependOn(&install.step);
    }

    // === Step "docker" — compilar para Linux musl (container Alpine) ===
    const docker_exe = b.addExecutable(.{
        .name = "servidor",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = b.resolveTargetQuery(.{
                .cpu_arch = .x86_64,
                .os_tag = .linux,
                .abi = .musl,
            }),
            .optimize = .ReleaseSafe,
        }),
    });
    const docker_install = b.addInstallArtifact(docker_exe, .{});
    const docker_step = b.step("docker", "Build para container Docker (Linux musl)");
    docker_step.dependOn(&docker_install.step);
}

Uso:

# Desenvolvimento local
zig build run

# Testes
zig build test

# Build para Docker (binário estático Linux)
zig build docker --summary all

# Release para todas as plataformas
zig build release --summary all

# Ver todas as opções disponíveis
zig build --help

Exemplo 3: Projeto Misto Zig + C com Dependência Externa

Um projeto que mistura Zig e C, com uma dependência externa:

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    // Dependência externa (declarada no build.zig.zon)
    const clap = b.dependency("clap", .{
        .target = target,
        .optimize = optimize,
    });

    // Executável principal
    const exe = b.addExecutable(.{
        .name = "ferramenta",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
            .link_libc = true,
        }),
    });

    // Adicionar dependência Zig
    exe.root_module.addImport("clap", clap.module("clap"));

    // Adicionar código C
    exe.addCSourceFiles(.{
        .files = &.{"src/legacy/processador.c"},
        .flags = &.{"-std=c11"},
    });
    exe.addIncludePath(b.path("src/legacy"));

    // Linkar com SQLite do sistema
    exe.linkSystemLibrary("sqlite3");

    b.installArtifact(exe);

    // Steps run e test...
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| run_cmd.addArgs(args);

    const run_step = b.step("run", "Executar a ferramenta");
    run_step.dependOn(&run_cmd.step);
}

Dicas e Boas Práticas

1. Sempre Use standardTargetOptions e standardOptimizeOption

Isso dá flexibilidade ao usuário do seu projeto:

// ✅ Bom — flexível
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

// ❌ Ruim — hardcoded
.target = b.graph.host,
// sem opção de otimização

2. Adicione --summary all ao Desenvolvimento

# Ver o que o build system fez
zig build --summary all

# Saída típica:
# Build Summary: 5/5 steps succeeded
# install success
# └─ install app success
#    └─ compile exe app Debug native success 1s MaxRSS:136M

3. Use --watch para Recompilação Automática

# Recompila automaticamente quando arquivos mudam
zig build --watch

4. Configure o .gitignore

# Zig build artifacts
.zig-cache/
zig-out/

5. Separe Build Steps por Responsabilidade

pub fn build(b: *std.Build) void {
    // Steps claramente separados:
    // - "run" — executar em desenvolvimento
    // - "test" — rodar testes
    // - "release" — build de produção
    // - "docker" — build para containers
    // - "docs" — gerar documentação
    // - "fmt" — formatar código
    // - "bench" — rodar benchmarks
}

6. Documente Suas Opções

# Qualquer opção definida com b.option() aparece no --help
zig build --help

# Project-Specific Options:
#   -Dlogging=[bool]     Habilitar logging detalhado
#   -Dversion=[string]   Versão do aplicativo
#   -Dport=[u16]         Porta do servidor (default: 8080)

7. Referência Rápida de Comandos

ComandoDescrição
zig initCriar projeto novo
zig buildCompilar
zig build runCompilar e executar
zig build testRodar testes
zig build --summary allBuild com sumário detalhado
zig build --watchRecompilação automática
zig build --helpVer opções disponíveis
zig build -Dtarget=...Cross-compilation
zig build -Doptimize=...Selecionar modo de otimização
zig fetch --save <url>Adicionar dependência
zig build --fetchBaixar dependências

8. Erros Comuns e Soluções

ErroCausaSolução
FileNotFound: src/main.zigArquivo fonte não existeVerificar caminho em b.path()
error: DependencyNotFoundDependência não baixadaExecutar zig build --fetch
error: LibCNotFoundlibc não encontradaAdicionar .link_libc = true ao módulo ou exe.linkLibC()
Compilação lentaCache corrompidoRemover .zig-cache/ e recompilar
Cross-compile falha com libcBiblioteca do sistema não disponível para targetUsar .abi = .musl para builds estáticos

Resumo

O Zig Build System é uma das razões pelas quais Zig se destaca como linguagem de sistemas moderna:

CaracterísticaZig Build SystemMake/CMake
LinguagemZig (a mesma do projeto)DSL própria
Cross-compilationUm flag (-Dtarget=...)Dias de configuração
Dependências externasZero (integrado ao compilador)Make, Python, CMake, Ninja…
CacheAutomático e inteligenteManual ou básico
ConcorrênciaAutomáticaManual com -j
Compila C/C++Sim, nativamenteApenas com configuração adicional
Gerenciamento de pacotesbuild.zig.zon + zig fetchNenhum (ou conan, vcpkg…)

O build.zig é código Zig — você tem toda a expressividade da linguagem para configurar seu build. Sem templates mágicos, sem DSLs para aprender, sem dependências para instalar.

Próximos Passos

Agora que você domina o Zig Build System:

  1. 📦 Como Instalar o Zig — se ainda não instalou.
  2. 🔄 Zig para Programadores C: Guia de Migração — para integrar código C existente.
  3. Comptime em Zig: O Poder da Execução em Tempo de Compilação — para metaprogramação avançada.
  4. 📖 Documentação oficial do Build System — referência completa com mais exemplos.

Tem dúvidas ou sugestões sobre o Zig Build System? Compartilhe com a comunidade Zig Brasil!

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.