Introdução
O sistema de build de Zig é uma das suas features mais poderosas. Em vez de aprender uma linguagem de build separada (Make), você escreve a lógica de build em Zig — a mesma linguagem do seu projeto. Isso elimina bugs de Makefile, simplifica cross-compilation, e torna o build reproduzível.
Este guia mostra como converter um Makefile típico para build.zig. Para CMake, veja Migrar de CMake para build.zig. Para o sistema de build em geral, consulte Zig Build System.
Pré-requisitos
- Zig instalado (versão 0.13+). Veja Como Instalar Zig
- Projeto C/C++ existente com Makefile
- Familiaridade básica com Zig
Exemplo Básico: Projeto C Simples
Makefile Original
CC = gcc
CFLAGS = -Wall -Wextra -O2 -std=c11
LDFLAGS = -lm -lpthread
SRCS = src/main.c src/utils.c src/parser.c
OBJS = $(SRCS:.c=.o)
TARGET = meu-programa
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
install: $(TARGET)
cp $(TARGET) /usr/local/bin/
.PHONY: all clean install
build.zig Equivalente
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "meu-programa",
.target = target,
.optimize = optimize,
});
exe.addCSourceFiles(.{
.files = &.{
"src/main.c",
"src/utils.c",
"src/parser.c",
},
.flags = &.{
"-Wall",
"-Wextra",
"-std=c11",
},
});
exe.addIncludePath(b.path("include"));
exe.linkLibC();
exe.linkSystemLibrary("m");
exe.linkSystemLibrary("pthread");
b.installArtifact(exe);
// Equivalente a "make run"
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "Executar o programa");
run_step.dependOn(&run_cmd.step);
}
Mapeamento de Conceitos
| Makefile | build.zig |
|---|---|
CC = gcc | Automático (Zig gerencia) |
CFLAGS = -Wall | .flags = &.{"-Wall"} |
LDFLAGS = -lm | exe.linkSystemLibrary("m") |
SRCS = ... | exe.addCSourceFiles(...) |
Target all | b.installArtifact(exe) |
Target clean | zig build --clean (automático) |
Target install | b.installArtifact(exe) + zig build install |
ifdef DEBUG | const optimize = b.standardOptimizeOption(.{}) |
.PHONY | Não necessário |
Compilação Condicional
Makefile
ifdef DEBUG
CFLAGS += -g -DDEBUG
else
CFLAGS += -O2 -DNDEBUG
endif
build.zig
const optimize = b.standardOptimizeOption(.{});
// O modo é controlado via linha de comando:
// zig build -Doptimize=Debug
// zig build -Doptimize=ReleaseFast
// Para defines customizados:
const debug = b.option(bool, "debug", "Ativar modo debug") orelse false;
if (debug) {
exe.defineCMacro("DEBUG", null);
}
Múltiplos Targets
Makefile
all: programa lib-estatica lib-dinamica
programa: $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
lib-estatica: $(LIB_OBJS)
ar rcs libminha.a $^
lib-dinamica: $(LIB_OBJS)
$(CC) -shared -o libminha.so $^
build.zig
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Biblioteca
const lib = b.addStaticLibrary(.{
.name = "minha",
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
// Biblioteca compartilhada
const shared = b.addSharedLibrary(.{
.name = "minha",
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(shared);
// Executável que usa a biblioteca
const exe = b.addExecutable(.{
.name = "programa",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.linkLibrary(lib);
b.installArtifact(exe);
}
Dependências Externas
Makefile
CFLAGS += $(shell pkg-config --cflags openssl)
LDFLAGS += $(shell pkg-config --libs openssl)
build.zig
exe.linkSystemLibrary("ssl");
exe.linkSystemLibrary("crypto");
Cross-compilation
Makefile
# Requer toolchain externo configurado manualmente
CC_ARM = arm-linux-gnueabihf-gcc
arm: CC = $(CC_ARM)
arm: all
build.zig
# Integrado, sem configuração extra:
zig build -Dtarget=arm-linux-gnueabihf
zig build -Dtarget=aarch64-linux-gnu
zig build -Dtarget=x86_64-windows-gnu
zig build -Dtarget=x86_64-macos
Veja Cross-compilation em Zig para detalhes.
Testes
Makefile
test: $(TEST_OBJS)
$(CC) -o test_runner $^ -lcmocka
./test_runner
build.zig
const testes = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_testes = b.addRunArtifact(testes);
const test_step = b.step("test", "Executar testes");
test_step.dependOn(&run_testes.step);
zig build test
Veja Testes Unitários e Testes com Allocator.
Projeto Misto C + Zig
O cenário mais comum na migração é ter código C e Zig juntos:
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "projeto-misto",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
// Código C existente
exe.addCSourceFiles(.{
.files = &.{
"src/legado/parser.c",
"src/legado/utils.c",
},
.flags = &.{ "-std=c11", "-Wall" },
});
exe.addIncludePath(b.path("include"));
exe.linkLibC();
b.installArtifact(exe);
}
Veja Interoperabilidade C-Zig e Chamar Funções C de Zig.
Vantagens do build.zig sobre Makefile
- Linguagem real: build.zig é código Zig — tem tipos, funções, loops, condicionais
- Cross-compilation trivial: Flag de linha de comando, sem toolchain externo
- Cache automático: Zig gerencia cache de compilação
- Sem dependências: Não precisa de
make,autotools,pkg-config - Reproduzível: Sem variações entre sistemas operacionais
- Debugável: Build.zig pode ser debugado como qualquer código Zig
Conclusão
Migrar de Makefile para build.zig simplifica significativamente o processo de build, especialmente para projetos que precisam de cross-compilation ou que misturam código C e Zig. A curva de aprendizado é mínima se você já conhece Zig.
Para migrar de CMake, veja Migrar de CMake para build.zig. Para a estratégia completa de migração de projeto C, consulte Como Migrar um Projeto C para Zig.