Введение: Вечный кризис управления зависимостями в C и C++
Знакомо ли вам чувство, когда вы пытаетесь подключить проверенную десятилетиями библиотеку на C к новому проекту, а в ответ получаете трехэтажные ошибки CMake, несовпадение версий компиляторов и сломанные пути к заголовочным файлам? (А потом смотришь на свой рабочий стол и думаешь: а ведь когда-то просто хотел написать «Hello, World»). Прямо сейчас, когда микросервисы собираются одной кнопкой, мир C и C++ для многих остается минным полем. В мире динамических языков вроде Python, Node.js или Rust экосистемы (npm, pip, Cargo) давно эту боль решили, позволив добавлять зависимости в одну строку. Долгое время универсального и чистого инструмента для C/C++ просто не существовало.
Разработчики вынуждены были полагаться на зоопарк систем сборки: Make, CMake, Autotools, Meson, Bazel, MSBuild и другие. Ручное портирование библиотек, написание оберток, решение проблем с кросс-компиляцией и управление версиями превращались в отдельный инженерный ад. Появление систем вроде Conan и vcpkg частично сгладило проблему, но они привнесли собственную сложность, тяжеловесные инфраструктурные требования и зависимость от внешнего окружения.
И тут на арену выходит Zig. Будучи современным системным языком программирования, созданным Андреем Келли (Andrew Kelley), Zig с самого начала позиционировался не просто как замена C, а как революционный инструмент для сборки и кросс-компиляции существующих C и C++ проектов. Встроенная система сборки (Zig Build System) и нативный пакетный менеджер предлагают элегантное, быстрое и концептуально чистое решение проблемы упаковки и сборки легаси-кода. В этой статье мы подробно разберем, как интегрировать и собирать C/C++ проекты с помощью Zig без боли.
Анатомия системы сборки Zig (Zig Build System) для не-Zig кода
Когда вы в очередной раз часами медитируете над синтаксисом CMakeLists.txt (и искренне верите, что еще пара правок заставят это работать на вашей машине), особенно остро ощущается нехватка логичных инструментов. К счастью, концепция сборки в Zig устроена совсем иначе: вместо запутанных макросов здесь используется чистый, статически типизированный код на самом Zig.
Главный компонент системы сборки — файл build.zig. Когда дело доходит до интеграции кода на C или C++, Zig предлагает первоклассные примитивы. Вместо того чтобы запускать внешний Makefile или вызывать CMake из терминала, Zig может напрямую компилировать исходный код на C/C++ с помощью встроенного форка Clang и связывать его с вашим проектом.
Рассмотрим базовый пример создания объекта сборки для сторонней C-библиотеки:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lib = b.addStaticLibrary(.{
.name = "mylib",
.target = target,
.optimize = optimize,
});
lib.addIncludePath(b.path("vendor/include"));
lib.addCSourceFiles(.{
.files = &[_][]const u8{
"vendor/src/foo.c",
"vendor/src/bar.c",
},
.flags = &[_][]const u8{"-std=c99", "-Wall"},
});
lib.linkLibC();
b.installArtifact(lib);
}
Пакетный менеджер Zig: Отказ от тяжеловесных зависимостей
Описанный подход к сборке избавляет от внешних утилит, но как быть с загрузкой самих исходников и контролем их целостности? Здесь на помощь приходит нативный пакетный менеджер Zig, который делает процесс управления сторонними библиотеками предсказуемым и молниеносным. (И главное — никаких случайно скачанных малварей из заброшенных репозиториев без хешей).
Конфигурация build.zig.zon
В основе пакетного менеджера лежит файл build.zig.zon (Zig Object Notation). В нем описываются метаданные проекта и его зависимости. Пример такого файла выглядит максимально лаконично:
.{
.name = "my_project",
.version = "0.1.0",
.dependencies = .{
.raylib = .{
.url = "https://github.com/raysan5/raylib/archive/refs/tags/5.0.tar.gz",
.hash = "1220... хеш для верификации ...",
},