Migrar de CMake para build.zig

Introdução

CMake é o sistema de build mais usado em projetos C/C++. Embora poderoso, sua linguagem de script é complexa, com sintaxe pouco intuitiva e comportamento que varia entre versões. O build.zig de Zig oferece a mesma funcionalidade em uma linguagem de programação real, com tipagem e tooling adequados.

Este guia converte padrões CMake comuns para build.zig. Para Makefile, veja Migrar de Makefile 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 CMakeLists.txt
  • Familiaridade básica com Zig

Projeto Básico

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(MeuProjeto C)

set(CMAKE_C_STANDARD 11)

add_executable(meu-programa
    src/main.c
    src/utils.c
    src/parser.c
)

target_include_directories(meu-programa PRIVATE include)
target_compile_options(meu-programa PRIVATE -Wall -Wextra)
target_link_libraries(meu-programa m pthread)

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 = "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);
}

Mapeamento de Comandos

CMakebuild.zig
project(nome).name = "nome" em addExecutable
add_executableb.addExecutable(...)
add_library(STATIC)b.addStaticLibrary(...)
add_library(SHARED)b.addSharedLibrary(...)
target_include_directoriesexe.addIncludePath(...)
target_link_librariesexe.linkSystemLibrary(...)
target_compile_definitionsexe.defineCMacro(...)
target_compile_optionsVia .flags em addCSourceFiles
find_packageexe.linkSystemLibrary(...)
option(...)b.option(...)
install(TARGETS)b.installArtifact(...)
enable_testing()b.addTest(...)
set(CMAKE_BUILD_TYPE)b.standardOptimizeOption(.{})

find_package e Dependências

CMake

find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)
find_package(Threads REQUIRED)

target_link_libraries(meu-programa
    OpenSSL::SSL
    OpenSSL::Crypto
    ZLIB::ZLIB
    Threads::Threads
)

build.zig

exe.linkSystemLibrary("ssl");
exe.linkSystemLibrary("crypto");
exe.linkSystemLibrary("z");
exe.linkLibC(); // inclui pthreads via libc

Zig não precisa de módulos Find separados — linkSystemLibrary busca bibliotecas do sistema diretamente via pkg-config ou caminhos padrão.

Opções de Build

CMake

option(ENABLE_LOGGING "Ativar logging" ON)
option(USE_OPENSSL "Usar OpenSSL" OFF)

if(ENABLE_LOGGING)
    target_compile_definitions(meu-programa PRIVATE ENABLE_LOGGING)
endif()

if(USE_OPENSSL)
    find_package(OpenSSL REQUIRED)
    target_link_libraries(meu-programa OpenSSL::SSL)
endif()

build.zig

const enable_logging = b.option(bool, "logging", "Ativar logging") orelse true;
const use_openssl = b.option(bool, "openssl", "Usar OpenSSL") orelse false;

if (enable_logging) {
    exe.defineCMacro("ENABLE_LOGGING", null);
}

if (use_openssl) {
    exe.linkSystemLibrary("ssl");
    exe.linkSystemLibrary("crypto");
}
zig build -Dlogging=true -Dopenssl=true

Subdirectories e Targets Múltiplos

CMake

# CMakeLists.txt raiz
add_subdirectory(libs/utils)
add_subdirectory(libs/parser)
add_subdirectory(src)

# libs/utils/CMakeLists.txt
add_library(utils STATIC utils.c)
target_include_directories(utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# src/CMakeLists.txt
add_executable(programa main.c)
target_link_libraries(programa utils parser)

build.zig

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

    // Biblioteca utils
    const utils = b.addStaticLibrary(.{
        .name = "utils",
        .target = target,
        .optimize = optimize,
    });
    utils.addCSourceFile(.{
        .file = b.path("libs/utils/utils.c"),
        .flags = &.{"-std=c11"},
    });
    utils.addIncludePath(b.path("libs/utils"));

    // Biblioteca parser
    const parser = b.addStaticLibrary(.{
        .name = "parser",
        .target = target,
        .optimize = optimize,
    });
    parser.addCSourceFile(.{
        .file = b.path("libs/parser/parser.c"),
        .flags = &.{"-std=c11"},
    });
    parser.addIncludePath(b.path("libs/parser"));

    // Executável principal
    const exe = b.addExecutable(.{
        .name = "programa",
        .target = target,
        .optimize = optimize,
    });
    exe.addCSourceFile(.{
        .file = b.path("src/main.c"),
        .flags = &.{"-std=c11"},
    });
    exe.linkLibrary(utils);
    exe.linkLibrary(parser);
    exe.linkLibC();

    b.installArtifact(exe);
}

Testes

CMake

enable_testing()
add_executable(test_utils tests/test_utils.c)
target_link_libraries(test_utils utils cmocka)
add_test(NAME TestUtils COMMAND test_utils)

build.zig

// Para testes Zig nativos
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);

// Para testes C existentes
const test_c = b.addExecutable(.{
    .name = "test_utils",
    .target = target,
    .optimize = optimize,
});
test_c.addCSourceFile(.{
    .file = b.path("tests/test_utils.c"),
    .flags = &.{"-std=c11"},
});
test_c.linkLibrary(utils);
test_c.linkSystemLibrary("cmocka");
test_c.linkLibC();

const run_test_c = b.addRunArtifact(test_c);
test_step.dependOn(&run_test_c.step);

Veja Testes Unitários.

Instalação

CMake

install(TARGETS programa DESTINATION bin)
install(FILES include/minha_lib.h DESTINATION include)
install(TARGETS utils DESTINATION lib)

build.zig

b.installArtifact(exe);
b.installArtifact(utils);
b.installFile("include/minha_lib.h", "include/minha_lib.h");

Cross-compilation

CMake

# Requer toolchain file externo
# cmake -DCMAKE_TOOLCHAIN_FILE=toolchain-arm.cmake ..

# toolchain-arm.cmake:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)

build.zig

# Uma flag, sem arquivo de toolchain:
zig build -Dtarget=arm-linux-gnueabihf
zig build -Dtarget=aarch64-linux-gnu
zig build -Dtarget=x86_64-windows-gnu

Veja Cross-compilation em Zig.

Vantagens sobre CMake

  1. Linguagem real: build.zig é Zig, com tipagem, autocompletion e debugging
  2. Sem versões incompatíveis: CMake 3.x tem comportamento que varia entre versões
  3. Cross-compilation trivial: Uma flag vs arquivos de toolchain
  4. Sem dependências: Não precisa instalar CMake, ninja, ou geradores
  5. Cache transparente: Sem diretório build/ opaco
  6. Reproduzível: Mesmo resultado em qualquer máquina com Zig instalado

Conclusão

A migração de CMake para build.zig simplifica significativamente o processo de build. A maior vantagem é usar uma linguagem de programação real em vez de scripts CMake, com cross-compilation integrada e sem dependências externas.

Para projetos mistos C/Zig, veja Interoperabilidade C-Zig. Para migração de código, consulte Guia de Migração: C para Zig.

Continue aprendendo Zig

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