Build System do Zig: Guia Completo do build.zig
O build system de Zig é único — em vez de arquivos declarativos como Makefiles ou CMakeLists.txt, você escreve a lógica de build em Zig puro. Isso significa que tem acesso a toda a expressividade da linguagem: condicionais, loops, comptime e até chamadas de rede, tudo dentro do build script.
Neste guia, vamos explorar o build.zig em profundidade: desde a estrutura básica até dependências externas com build.zig.zon, steps customizados e integração com projetos C/C++.
Se é novo em Zig, comece com Por que aprender Zig e o cheatsheet do build system.
Estrutura Básica do build.zig
Todo projeto Zig começa com um build.zig na raiz:
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(.{});
// Definir executável principal
const exe = b.addExecutable(.{
.name = "meu-app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Instalar o artefato
b.installArtifact(exe);
// Step de "run" para executar o programa
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "Executar o aplicativo");
run_step.dependOn(&run_cmd.step);
}
Execute com:
zig build # Compila
zig build run # Compila e executa
zig build -Doptimize=ReleaseFast # Build otimizado
O
standardTargetOptionsé o que habilita a poderosa cross-compilation do Zig — basta passar-Dtarget=aarch64-linux.
Steps: O Grafo de Build
O build system funciona como um grafo direcionado acíclico (DAG) de steps. Cada step pode depender de outros, e o Zig executa apenas o que é necessário:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Step 1: Compilar a lib
const lib = b.addStaticLibrary(.{
.name = "minha-lib",
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
// Step 2: Compilar o executável (depende da lib)
const exe = b.addExecutable(.{
.name = "meu-app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.linkLibrary(lib);
// Step 3: Testes unitários
const unit_tests = b.addTest(.{
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
const run_tests = b.addRunArtifact(unit_tests);
// Steps acessíveis pela CLI
b.installArtifact(exe);
const test_step = b.step("test", "Rodar testes unitários");
test_step.dependOn(&run_tests.step);
const run_step = b.step("run", "Executar o app");
const run_exe = b.addRunArtifact(exe);
run_exe.step.dependOn(b.getInstallStep());
run_step.dependOn(&run_exe.step);
}
Agora zig build test roda testes e zig build run executa. Cada step só compila o necessário — muito mais rápido que Makefiles tradicionais.
Para mais sobre testes, veja o guia completo de testes em Zig.
Build Options: Configuração Flexível
Defina opções customizáveis via CLI para compilação condicional:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Opção booleana
const enable_logging = b.option(
bool,
"enable-logging",
"Habilitar logs detalhados",
) orelse false;
// Opção de enum
const Backend = enum { sqlite, postgres, mysql };
const backend = b.option(
Backend,
"backend",
"Selecionar backend de banco de dados",
) orelse .sqlite;
const exe = b.addExecutable(.{
.name = "meu-app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Passar opções para o código via build options
const options = b.addOptions();
options.addOption(bool, "enable_logging", enable_logging);
options.addOption(Backend, "backend", backend);
exe.root_module.addOptions("config", options);
b.installArtifact(exe);
}
No código Zig:
// src/main.zig
const config = @import("config");
pub fn main() void {
if (config.enable_logging) {
// Logs detalhados habilitados
std.debug.print("Logging ativado\n", .{});
}
switch (config.backend) {
.sqlite => initSqlite(),
.postgres => initPostgres(),
.mysql => initMysql(),
}
}
Use na CLI:
zig build -Denable-logging=true -Dbackend=postgres
Isso substitui o #ifdef do C e os feature flags do Cargo (Rust) — tudo resolvido em comptime com type safety.
Dependências com build.zig.zon
O build.zig.zon é o gerenciador de pacotes do Zig. Diferente do go.mod ou Cargo.toml, usa a sintaxe ZON (Zig Object Notation):
// build.zig.zon
.{
.name = "meu-projeto",
.version = "0.1.0",
.dependencies = .{
.zap = .{
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.7.0.tar.gz",
.hash = "1220aabff7e77a3a21e46738bd8e4016e8d0347ae0b5a8f6e3e5e68b1f42a1e35c3f",
},
.clap = .{
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.8.0.tar.gz",
.hash = "122062d301569d9b5288ac2ea13c24ca7b3f41398cba2f41d73947f7a5e624e78b49",
},
},
.paths = .{"."},
}
Para detalhes sobre ZON, veja o glossário de zon e o package manager do Zig.
Depois, use as dependências no build.zig:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Buscar dependência
const zap = b.dependency("zap", .{
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "meu-servidor",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Adicionar módulo da dependência
exe.root_module.addImport("zap", zap.module("zap"));
b.installArtifact(exe);
}
Para adicionar uma dependência:
zig fetch --save https://github.com/zigzap/zap/archive/refs/tags/v0.7.0.tar.gz
Saiba mais sobre zig fetch no glossário.
Integração com Projetos C/C++
O build system brilha na interoperabilidade com C. Você pode compilar código C/C++ diretamente:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "app-hibrido",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Compilar arquivos C junto com Zig
exe.addCSourceFiles(.{
.files = &.{
"vendor/utils.c",
"vendor/parser.c",
},
.flags = &.{
"-std=c11",
"-Wall",
"-Wextra",
"-O2",
},
});
// Adicionar include paths
exe.addIncludePath(b.path("vendor/include"));
// Linkar com bibliotecas do sistema
exe.linkSystemLibrary("pthread");
exe.linkLibC();
b.installArtifact(exe);
}
Isso torna o Zig uma alternativa ao GCC/Clang como compilador C — muitos projetos usam zig cc como drop-in replacement. Veja como isso se aplica na migração de projetos C para Zig.
Steps Customizados
Crie steps que executam scripts, geram código ou fazem validações:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "meu-app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
// Step customizado: gerar documentação
const docs_step = b.step("docs", "Gerar documentação");
const docs_cmd = b.addSystemCommand(&.{
"zig", "doc", "src/lib.zig",
});
docs_step.dependOn(&docs_cmd.step);
// Step customizado: limpar artefatos
const clean_step = b.step("clean", "Limpar build artifacts");
const clean_cmd = b.addRemoveDirTree(b.path("zig-out"));
clean_step.dependOn(&clean_cmd.step);
// Step de benchmark
const bench = b.addExecutable(.{
.name = "bench",
.root_source_file = b.path("src/bench.zig"),
.target = target,
.optimize = .ReleaseFast,
});
const bench_run = b.addRunArtifact(bench);
const bench_step = b.step("bench", "Rodar benchmarks");
bench_step.dependOn(&bench_run.step);
}
Agora:
zig build docs # Gera documentação
zig build clean # Limpa artefatos
zig build bench # Roda benchmarks
Compilação Condicional por Plataforma
O build system tem acesso completo às informações do target:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "meu-app",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Linking condicional por OS
if (target.result.os.tag == .linux) {
exe.linkSystemLibrary("io_uring");
} else if (target.result.os.tag == .macos) {
exe.linkFramework("CoreFoundation");
} else if (target.result.os.tag == .windows) {
exe.linkSystemLibrary("ws2_32");
}
// Arquivo fonte condicional
if (target.result.cpu.arch == .aarch64) {
exe.addCSourceFile(.{
.file = b.path("src/neon_simd.c"),
.flags = &.{},
});
}
b.installArtifact(exe);
}
Isso é muito mais legível que os #ifdef _WIN32 do C ou condicionais complexas em CMake. Para SIMD especificamente, veja nosso guia de SIMD em Zig.
Para comparação, o sistema de build do Go (
go build) é mais simples mas menos flexível. Saiba mais em golang.com.br. Já o Cargo do Rust oferece features semelhantes — explore em rustlang.com.br.
Múltiplos Artefatos
Projetos maiores produzem vários binários e bibliotecas:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Biblioteca compartilhada
const lib = b.addSharedLibrary(.{
.name = "minha-lib",
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
// Biblioteca estática
const static_lib = b.addStaticLibrary(.{
.name = "minha-lib",
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(static_lib);
// Executável CLI
const cli = b.addExecutable(.{
.name = "minha-cli",
.root_source_file = b.path("src/cli.zig"),
.target = target,
.optimize = optimize,
});
cli.linkLibrary(static_lib);
b.installArtifact(cli);
// Servidor
const server = b.addExecutable(.{
.name = "meu-servidor",
.root_source_file = b.path("src/server.zig"),
.target = target,
.optimize = optimize,
});
server.linkLibrary(static_lib);
b.installArtifact(server);
}
Dicas e Boas Práticas
- Use
standardTargetOptionsestandardOptimizeOption— habilitam flags padrão de CLI - Separe lib e exe — facilita testes e reutilização
- Documente seus steps — a descrição aparece em
zig build --help - Use
build.zig.zonpara dependências — não faça git submodules - Teste o build para múltiplos targets — aproveite a cross-compilation
- Use release modes adequados —
ReleaseFastpara performance,ReleaseSafepara produção com safety checks
Para ferramentas de debug e profiling, veja o ecossistema de debug tools e profiling.
Conclusão
O build system do Zig é poderoso porque é código Zig real — não uma DSL limitada. Você define targets, options, steps e dependências com a mesma linguagem que escreve seu software. Combinado com a interoperabilidade C nativa e o gerenciamento de pacotes via build.zig.zon, é uma das experiências de build mais coesas do ecossistema de linguagens de sistemas.
Para se aprofundar, explore o overview do build system, o glossário de build.zig e o cheatsheet do build system. E se está planejando sua carreira com Zig, confira o roadmap de desenvolvedor e as ferramentas de produtividade.