Migrar de Makefile para build.zig

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

Makefilebuild.zig
CC = gccAutomático (Zig gerencia)
CFLAGS = -Wall.flags = &.{"-Wall"}
LDFLAGS = -lmexe.linkSystemLibrary("m")
SRCS = ...exe.addCSourceFiles(...)
Target allb.installArtifact(exe)
Target cleanzig build --clean (automático)
Target installb.installArtifact(exe) + zig build install
ifdef DEBUGconst optimize = b.standardOptimizeOption(.{})
.PHONYNã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

  1. Linguagem real: build.zig é código Zig — tem tipos, funções, loops, condicionais
  2. Cross-compilation trivial: Flag de linha de comando, sem toolchain externo
  3. Cache automático: Zig gerencia cache de compilação
  4. Sem dependências: Não precisa de make, autotools, pkg-config
  5. Reproduzível: Sem variações entre sistemas operacionais
  6. 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.

Continue aprendendo Zig

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