Система Zig Build
Коли починати використовувати систему Zig Build?
Основних команд zig build-exe
, zig build-lib
, zig build-obj
і zig test
часто достатньо. Однак інколи проект потребує ще одного рівня абстракції, щоб керувати складністю збірки з вихідного коду.
Наприклад, можливо, виникла одна з таких ситуацій:
- Командний рядок стає надто довгим і громіздким, і вам потрібно знайти місце, щоб його записати.
- Ви хочете створити багато речей, або процес створення складається з багатьох кроків.
- Ви хочете скористатися перевагами паралелізму та кешування, щоб скоротити час створення.
- Ви хочете відкрити параметри конфігурації для проекту.
- Процес збирання відрізняється залежно від цільової системи та інших параметрів.
- Ви маєте залежність від інших проектів.
- Ви хочете уникнути непотрібної залежності від cmake, make, shell, msvc, python тощо, зробивши проект доступним для більшої кількості учасників.
- Ви хочете надати пакет для використання третіми особами.
- Ви хочете надати стандартизований спосіб для таких інструментів, як IDE, щоб семантично зрозуміти, як створити проект.
Якщо будь-який із цих варіантів застосовний, проект отримає користь від використання системи Zig Build.
Почнемо
Простий Виконуваний Файл
Цей сценарій збірки створює виконуваний файл із Zig-файлу, який містить загальнодоступне визначення основної функції.
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("hello.zig"),
.target = b.host,
});
b.installArtifact(exe);
}
$ zig build--summary all
Build Summary: 3/3 steps succeeded
install cached
└─ install hello cached
└─ zig build-exe hello Debug native cached 66ms MaxRSS:36M
Встановлення Артефактів Збірки
Система збірки Zig, як і більшість систем збірки, базується на моделюванні проекту як спрямованого ациклічного графа (DAG) кроків, які виконуються незалежно та одночасно.
За замовчуванням основним кроком на графіку є крок Встановлення, метою якого є копіювання артефактів збірки в місце їх останнього спочинку. Крок інсталяції починається без залежностей, тому нічого не станеться під час запуску zig build
. Сценарій збірки проекту має додати до набору речей для встановлення, що й робить виклик функції installArtifact
, наведений вище.
Вивід
├── build.zig
├── hello.zig
├── .zig-cache
└── zig-out
└── bin
└── hello
У цьому виводі є два згенерованих каталоги: .zig-cache
і zig-out
. Перший містить файли, які зроблять наступні збірки швидшими, але ці файли не призначені для перевірки в системі керування джерелами, і цей каталог можна повністю видалити в будь-який час без жодних наслідків.
Другий, zig-out
, є "префіксом встановлення". Це відповідає стандартній концепції ієрархії файлової системи. Цей каталог вибирає не проект, а користувач zig build
з прапорцем --prefix
(скорочено -p
).
Ви, як супроводжувач проекту, вибираєте, що буде розміщено в цьому каталозі, але користувач вибирає, де це встановити у своїй системі. Сценарій збірки не може жорстко закодувати вихідні шляхи, оскільки це порушить кешування, паралелізм і компонування, а також дратує кінцевого користувача.
Додавання Зручного Кроку для Запуску Програми
Звичайним є додавання кроку run
, щоб надати можливість безпосереднього запуску головної програми з команди побудови.
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("hello.zig"),
.target = b.host,
});
b.installArtifact(exe);
const run_exe = b.addRunArtifact(exe);
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_exe.step);
}
$ zig buildrun --summary all
Hello World!
Build Summary: 3/3 steps succeeded
run success
└─ run hello success 14ms MaxRSS:1M
└─ zig build-exe hello Debug native cached 44ms MaxRSS:36M
Основи
Параметри, Надані Користувачем
Використовуйте b.option
, щоб зробити сценарій збирання доступним для кінцевих користувачів, а також інших проектів, які залежать від проекту як пакета.
const std = @import("std");
pub fn build(b: *std.Build) void {
const windows = b.option(bool, "windows", "Target Microsoft Windows") orelse false;
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("example.zig"),
.target = b.resolveTargetQuery(.{
.os_tag = if (windows) .windows else null,
}),
});
b.installArtifact(exe);
}
$ zig build--help
Usage: /home/ci/deps/zig-linux-x86_64-0.13.0/zig build [steps] [options]
Steps:
install (default) Copy build artifacts to prefix path
uninstall Remove build artifacts from prefix path
General Options:
-p, --prefix [path] Where to install files (default: zig-out)
--prefix-lib-dir [path] Where to install libraries
--prefix-exe-dir [path] Where to install executables
--prefix-include-dir [path] Where to install C header files
--release[=mode] Request release mode, optionally specifying a
preferred optimization mode: fast, safe, small
-fdarling, -fno-darling Integration with system-installed Darling to
execute macOS programs on Linux hosts
(default: no)
-fqemu, -fno-qemu Integration with system-installed QEMU to execute
foreign-architecture programs on Linux hosts
(default: no)
--glibc-runtimes [path] Enhances QEMU integration by providing glibc built
for multiple foreign architectures, allowing
execution of non-native programs that link with glibc.
-frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
ARM64 macOS hosts. (default: no)
-fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
execute WASI binaries. (default: no)
-fwine, -fno-wine Integration with system-installed Wine to execute
Windows programs on Linux hosts. (default: no)
-h, --help Print this help and exit
-l, --list-steps Print available steps
--verbose Print commands before executing them
--color [auto|off|on] Enable or disable colored error messages
--prominent-compile-errors Buffer compile errors and display at end
--summary [mode] Control the printing of the build summary
all Print the build summary in its entirety
new Omit cached steps
failures (Default) Only print failed steps
none Do not print the build summary
-j<N> Limit concurrent jobs (default is to use all CPU cores)
--maxrss <bytes> Limit memory usage (default is to use available memory)
--skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
--fetch Exit after fetching dependency tree
Project-Specific Options:
-Dwindows=[bool] Target Microsoft Windows
System Integration Options:
--search-prefix [path] Add a path to look for binaries, libraries, headers
--sysroot [path] Set the system root directory (usually /)
--libc [file] Provide a file which specifies libc paths
--system [pkgdir] Disable package fetching; enable all integrations
-fsys=[name] Enable a system integration
-fno-sys=[name] Disable a system integration
Available System Integrations: Enabled:
(none) -
Advanced Options:
-freference-trace[=num] How many lines of reference trace should be shown per compile error
-fno-reference-trace Disable reference trace
--build-file [file] Override path to build.zig
--cache-dir [path] Override path to local Zig cache directory
--global-cache-dir [path] Override path to global Zig cache directory
--zig-lib-dir [arg] Override path to Zig lib directory
--build-runner [file] Override path to build runner
--seed [integer] For shuffling dependency traversal order (default: random)
--debug-log [scope] Enable debugging the compiler
--debug-pkg-config Fail if unknown pkg-config flags encountered
--verbose-link Enable compiler debug output for linking
--verbose-air Enable compiler debug output for Zig AIR
--verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
--verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC
--verbose-cimport Enable compiler debug output for C imports
--verbose-cc Enable compiler debug output for C compilation
--verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
Будь ласка, зверніть вашу увагу на ці рядки:
Project-Specific Options:
-Dwindows=[bool] Target Microsoft Windows
Ця частина меню довідки створюється автоматично на основі запуску логіки build.zig
. Таким чином користувачі можуть відкрити параметри конфігурації сценарію збірки.
Стандартні Параметри Конфігурації
Раніше ми використовували прапорець для позначення створення для Windows. Однак ми можемо зробити краще.
Більшість проектів хочуть надати можливість змінювати цільові параметри та налаштування оптимізації. Щоб заохочувати стандартні угоди про іменування цих параметрів, Zig надає допоміжні функції standardTargetOptions
і standardOptimizeOption
.
Стандартні параметри цілі дозволяють користувачу, який запускає zig build
, вибрати, для якої цілі будувати. За замовчуванням дозволено будь-яку ціль, і відсутність вибору означає націлювання на хост-систему. Доступні інші варіанти обмеження підтримуваного цільового набору.
Стандартні параметри оптимізації дозволяють користувачу, який запускає zig build
, вибирати між Debug
, ReleaseSafe
, ReleaseFast
і ReleaseSmall
. За замовчуванням жоден із варіантів випуску не вважається кращим вибором сценарієм збірки, і користувач повинен прийняти рішення, щоб створити збірку випуску.
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("hello.zig"),
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
}
$ zig build-Dtarget=x86_64-windows -Doptimize=ReleaseSmall --summary all
Build Summary: 3/3 steps succeeded
install cached
└─ install hello cached
└─ zig build-exe hello ReleaseSmall x86_64-windows cached 44ms MaxRSS:36M
Тепер наше меню --help
містить більше елементів:
Project-Specific Options:
-Dtarget=[string] The CPU architecture, OS, and ABI to build for
-Dcpu=[string] Target CPU features to add or subtract
-Doptimize=[enum] Prioritize performance, safety, or binary size (-O flag)
Supported Values:
Debug
ReleaseSafe
ReleaseFast
ReleaseSmall
Цілком можливо створити ці параметри безпосередньо за допомогою b.option
, але цей API забезпечує загальноприйнятий спосіб іменування цих часто використовуваних параметрів.
Зверніть увагу, що у вихідних даних нашого терміналу ми передали -Dtarget=x86_64-windows -Doptimize=ReleaseSmall
. Порівняно з першим прикладом, тепер ми бачимо інші файли в префіксі встановлення:
zig-out/
└── bin
└── hello.exe
Параметри Умовної Компіляції
Щоб передати параметри зі сценарію збірки в Zig-код проекту, скористайтеся кроком Options
.
const std = @import("std");
const config = @import("config");
const semver = std.SemanticVersion.parse(config.version) catch unreachable;
extern fn foo_bar() void;
pub fn main() !void {
if (semver.major < 1) {
@compileError("too old");
}
std.debug.print("version: {s}\n", .{config.version});
if (config.have_libfoo) {
foo_bar();
}
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "app",
.root_source_file = b.path("app.zig"),
.target = b.host,
});
const version = b.option([]const u8, "version", "application version string") orelse "0.0.0";
const enable_foo = detectWhetherToEnableLibFoo();
const options = b.addOptions();
options.addOption([]const u8, "version", version);
options.addOption(bool, "have_libfoo", enable_foo);
exe.root_module.addOptions("config", options);
b.installArtifact(exe);
}
fn detectWhetherToEnableLibFoo() bool {
return false;
}
$ zig build-Dversion=1.2.3 --summary all
Build Summary: 4/4 steps succeeded
install cached
└─ install app cached
└─ zig build-exe app Debug native cached 33ms MaxRSS:36M
└─ options cached
У цьому прикладі дані, надані @import("config")
, відомі комп’ютеру, запобігаючи запуску @compileError
. Якби ми передали -Dversion=0.2.3
або опустили цей параметр, ми побачили б помилку компіляції app.zig
із помилкою "занадто старий".
Статична Бібліотека
Цей сценарій збірки створює статичну бібліотеку з коду Zig, а потім також виконуваний файл з іншого коду Zig, який його споживає.
export fn fizzbuzz(n: usize) ?[*:0]const u8 {
if (n % 5 == 0) {
if (n % 3 == 0) {
return "fizzbuzz";
} else {
return "fizz";
}
} else if (n % 3 == 0) {
return "buzz";
}
return null;
}
const std = @import("std");
extern fn fizzbuzz(n: usize) ?[*:0]const u8;
pub fn main() !void {
const stdout = std.io.getStdOut();
var bw = std.io.bufferedWriter(stdout.writer());
const w = bw.writer();
for (0..100) |n| {
if (fizzbuzz(n)) |s| {
try w.print("{s}\n", .{s});
} else {
try w.print("{d}\n", .{n});
}
}
try bw.flush();
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const libfizzbuzz = b.addStaticLibrary(.{
.name = "fizzbuzz",
.root_source_file = b.path("fizzbuzz.zig"),
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "demo",
.root_source_file = b.path("demo.zig"),
.target = target,
.optimize = optimize,
});
exe.linkLibrary(libfizzbuzz);
b.installArtifact(libfizzbuzz);
if (b.option(bool, "enable-demo", "install the demo too") orelse false) {
b.installArtifact(exe);
}
}
$ zig build--summary all
Build Summary: 3/3 steps succeeded
install cached
└─ install fizzbuzz cached
└─ zig build-lib fizzbuzz Debug native cached 56ms MaxRSS:36M
У цьому випадку буде встановлено лише статичну бібліотеку:
zig-out/
└── lib
└── libfizzbuzz.a
Однак, якщо ви уважно придивитеся, сценарій збірки містить опцію також встановити демонстрацію. Якщо ми додатково передаємо -Denable-demo
, то побачимо це в префіксі встановлення:
zig-out/
├── bin
│ └── demo
└── lib
└── libfizzbuzz.a
Зауважте, що незважаючи на безумовний виклик addExecutable
, система збирання фактично не витрачає час на створення виконуваного файлу demo
, якщо його не запитують за допомогою -Denable-demo
, оскільки система збирання базується на Спрямованому Ациклічному Графі з ребрами залежності.
Динамічна бібліотека
Тут ми зберігаємо всі файли такими ж, як у прикладі Static Library, за винятком файлу build.zig
.
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const libfizzbuzz = b.addSharedLibrary(.{
.name = "fizzbuzz",
.root_source_file = b.path("fizzbuzz.zig"),
.target = target,
.optimize = optimize,
.version = .{ .major = 1, .minor = 2, .patch = 3 },
});
b.installArtifact(libfizzbuzz);
}
$ zig build--summary all
Build Summary: 3/3 steps succeeded
install cached
└─ install fizzbuzz cached
└─ zig build-lib fizzbuzz Debug native cached 33ms MaxRSS:37M
Вивід
zig-out
└── lib
├── libfizzbuzz.so -> libfizzbuzz.so.1
├── libfizzbuzz.so.1 -> libfizzbuzz.so.1.2.3
└── libfizzbuzz.so.1.2.3
Як і у прикладі зі статичною бібліотекою, щоб створити для неї посилання на виконуваний файл, використовуйте такий код:
exe.linkLibrary(libfizzbuzz);
Тестування
Окремі файли можна протестувати безпосередньо за допомогою zig test foo.zig
, однак складніші випадки використання можна вирішити, організувавши тестування за допомогою сценарію збірки.
Під час використання сценарію побудови модульні тести розбиваються на два різні кроки в графі побудови: крок Компіляція та крок Виконання. Без виклику addRunArtifact
, який встановлює межу залежності між цими двома кроками, модульні тести не виконуватимуться.
Етап компіляції можна налаштувати так само, як і будь-який виконуваний файл, бібліотеку чи об’єктний файл, наприклад, шляхом зв’язування із системними бібліотеками, встановлення цільових параметрів або додавання додаткових одиниць компіляції.
Крок «Виконати» можна налаштувати так само, як і будь-який крок «Виконати», наприклад, пропускаючи виконання, коли хост не здатний виконати двійковий файл.
Під час використання системи збірки для запуску модульних тестів, виконавець збірки та засіб виконання тестів спілкуються через stdin і stdout, щоб одночасно запускати кілька пакетів модульних тестів і повідомляти про помилки тестування в змістовний спосіб, не змішуючи їхні результати. Це одна з причин, чому запис у стандартних модульних тестах є проблематичним - це заважатиме цьому каналу зв’язку. З іншого боку, цей механізм увімкне майбутню функцію, яка здатня очікувати паніку від модульного тесту.
const std = @import("std");
test "simple test" {
var list = std.ArrayList(i32).init(std.testing.allocator);
defer list.deinit();
try list.append(42);
try std.testing.expectEqual(@as(i32, 42), list.pop());
}
const std = @import("std");
const test_targets = [_]std.Target.Query{
.{}, // native
.{
.cpu_arch = .x86_64,
.os_tag = .linux,
},
.{
.cpu_arch = .aarch64,
.os_tag = .macos,
},
};
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run unit tests");
for (test_targets) |target| {
const unit_tests = b.addTest(.{
.root_source_file = b.path("main.zig"),
.target = b.resolveTargetQuery(target),
});
const run_unit_tests = b.addRunArtifact(unit_tests);
test_step.dependOn(&run_unit_tests.step);
}
}
$ zig buildtest --summary all
test
└─ run test failure
error: the host system (x86_64-linux.5.10...5.10-gnu.2.31) is unable to execute binaries from the target (aarch64-macos.11.7.1...14.1-none)
Build Summary: 5/7 steps succeeded; 1 failed
test transitive failure
├─ run test cached
│ └─ zig test Debug native cached 49ms MaxRSS:36M
├─ run test cached
│ └─ zig test Debug x86_64-linux cached 61ms MaxRSS:36M
└─ run test failure
└─ zig test Debug aarch64-macos cached 38ms MaxRSS:36M
error: the following build command failed with exit code 1:
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/build-system/unit-testing/.zig-cache/o/25475ace5ef269b60e98416da65a5cde/build /home/ci/deps/zig-linux-x86_64-0.13.0/zig /home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/build-system/unit-testing /home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/build-system/unit-testing/.zig-cache /home/ci/.cache/zig --seed 0x10a7accf -Zd5915bdf16628db4 --color on test --summary all
У цьому випадку було б корисно ввімкнути skip_foreign_checks
для модульних тестів:
@@ -23,6 +23,7 @@
});
const run_unit_tests = b.addRunArtifact(unit_tests);
+ run_unit_tests.skip_foreign_checks = true;
test_step.dependOn(&run_unit_tests.step);
}
}
const std = @import("std");
const test_targets = [_]std.Target.Query{
.{}, // native
.{
.cpu_arch = .x86_64,
.os_tag = .linux,
},
.{
.cpu_arch = .aarch64,
.os_tag = .macos,
},
};
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run unit tests");
for (test_targets) |target| {
const unit_tests = b.addTest(.{
.root_source_file = b.path("main.zig"),
.target = b.resolveTargetQuery(target),
});
const run_unit_tests = b.addRunArtifact(unit_tests);
run_unit_tests.skip_foreign_checks = true;
test_step.dependOn(&run_unit_tests.step);
}
}
// zig-doctest: build-system --collapseable -- test --summary all
$ zig build--summary all
Build Summary: 1/1 steps succeeded
install cached
Лінкування на Системні Бібліотеки
Для задоволення бібліотечних залежностей є два варіанти:
- Надайте ці бібліотеки через систему збірки Zig (див. Керування пакетами і Статична бібліотека).
- Використовуйте файли, надані хост-системою.
Для сценарію використання супроводжуючих проектів на початковому етапі отримання цих бібліотек за допомогою системи Zig Build System забезпечує найменше тертя та ередає повноваження щодо конфігурації в руки тих супроводжуючих. Кожен, хто будує таким чином, матиме придатні для електронного відтворення узгоджені результати, і він працюватиме на кожній операційній системі та навіть підтримуватиме крос-компіляцію. Крім того, це дозволяє проекту з ідеальною точністю визначити точні версії всього дерева залежностей, на основі якого він хоче створити. Очікується, що це буде найкращий спосіб залежати від зовнішніх бібліотек.
Однак для випадку використання пакування програмного забезпечення в репозиторії, такі як Debian, Homebrew або Nix, обов’язковим є зв’язування з системними бібліотеками. Отже, сценарії збірки мають визначати режим і відповідним чином налаштовувати.
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "zip",
.root_source_file = b.path("zip.zig"),
.target = b.host,
});
exe.linkSystemLibrary("z");
exe.linkLibC();
b.installArtifact(exe);
}
$ zig build--summary all
Build Summary: 3/3 steps succeeded
install cached
└─ install zip cached
└─ zig build-exe zip Debug native cached 95ms MaxRSS:37M
Користувачі zig build
можуть використовувати --search-prefix
для надання додаткових каталогів, які вважаються "системними каталогами" для цілей пошуку статичних і динамічних бібліотек.
Створення Файлів
Запуск Системних Шнструментів
Ця версія hello world очікує знайти файл word.txt
у тому самому шляху, і ми хочемо використати системний інструмент, щоб створити його, починаючи з файлу JSON.
Майте на увазі, що системні залежності ускладнять створення вашого проекту для ваших користувачів. Цей сценарій збірки залежить, наприклад, від jq
, якого за замовчуванням немає в більшості дистрибутивів Linux і який може бути незнайомим інструментом для користувачів Windows.
У наступному розділі буде замінено jq
на інструмент Zig, включений у вихідне дерево, що є кращим підходом.
words.json
{
"en": "world",
"it": "mondo",
"ja": "世界"
}
const std = @import("std");
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const self_exe_dir_path = try std.fs.selfExeDirPathAlloc(arena);
var self_exe_dir = try std.fs.cwd().openDir(self_exe_dir_path, .{});
defer self_exe_dir.close();
const word = try self_exe_dir.readFileAlloc(arena, "word.txt", 1000);
try std.io.getStdOut().writer().print("Hello {s}\n", .{word});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const lang = b.option([]const u8, "language", "language of the greeting") orelse "en";
const tool_run = b.addSystemCommand(&.{"jq"});
tool_run.addArgs(&.{
b.fmt(
\\.["{s}"]
, .{lang}),
"-r", // raw output to omit quotes around the selected string
});
tool_run.addFileArg(b.path("words.json"));
const output = tool_run.captureStdOut();
b.getInstallStep().dependOn(&b.addInstallFileWithDir(output, .prefix, "word.txt").step);
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const install_artifact = b.addInstallArtifact(exe, .{
.dest_dir = .{ .override = .prefix },
});
b.getInstallStep().dependOn(&install_artifact.step);
}
$ zig build-Dlanguage=ja --summary all
Build Summary: 5/5 steps succeeded
install cached
├─ install generated to word.txt cached
│ └─ run jq cached
└─ install hello cached
└─ zig build-exe hello Debug native cached 48ms MaxRSS:36M
Вивід
zig-out
├── hello
└── word.txt
Зверніть увагу, як captureStdOut
створює тимчасовий файл із результатом виклику jq
.
Запуск Інструментів Проекту
Ця версія hello world очікує знайти файл word.txt
за тим самим шляхом, і ми хочемо створити його під час збирання, викликавши програму Zig для файлу JSON.
tools/words.json
{
"en": "world",
"it": "mondo",
"ja": "世界"
}
const std = @import("std");
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const self_exe_dir_path = try std.fs.selfExeDirPathAlloc(arena);
var self_exe_dir = try std.fs.cwd().openDir(self_exe_dir_path, .{});
defer self_exe_dir.close();
const word = try self_exe_dir.readFileAlloc(arena, "word.txt", 1000);
try std.io.getStdOut().writer().print("Hello {s}\n", .{word});
}
const std = @import("std");
const usage =
\\Usage: ./word_select [options]
\\
\\Options:
\\ --input-file INPUT_JSON_FILE
\\ --output-file OUTPUT_TXT_FILE
\\ --lang LANG
\\
;
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(arena);
var opt_input_file_path: ?[]const u8 = null;
var opt_output_file_path: ?[]const u8 = null;
var opt_lang: ?[]const u8 = null;
{
var i: usize = 1;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
try std.io.getStdOut().writeAll(usage);
return std.process.cleanExit();
} else if (std.mem.eql(u8, "--input-file", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_input_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_input_file_path = args[i];
} else if (std.mem.eql(u8, "--output-file", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_output_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_output_file_path = args[i];
} else if (std.mem.eql(u8, "--lang", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_lang != null) fatal("duplicated {s} argument", .{arg});
opt_lang = args[i];
} else {
fatal("unrecognized arg: '{s}'", .{arg});
}
}
}
const input_file_path = opt_input_file_path orelse fatal("missing --input-file", .{});
const output_file_path = opt_output_file_path orelse fatal("missing --output-file", .{});
const lang = opt_lang orelse fatal("missing --lang", .{});
var input_file = std.fs.cwd().openFile(input_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ input_file_path, @errorName(err) });
};
defer input_file.close();
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
var json_reader = std.json.reader(arena, input_file.reader());
var words = try std.json.ArrayHashMap([]const u8).jsonParse(arena, &json_reader, .{
.allocate = .alloc_if_needed,
.max_value_len = 1000,
});
const w = words.map.get(lang) orelse fatal("Lang not found in JSON file", .{});
try output_file.writeAll(w);
return std.process.cleanExit();
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const lang = b.option([]const u8, "language", "language of the greeting") orelse "en";
const tool = b.addExecutable(.{
.name = "word_select",
.root_source_file = b.path("tools/word_select.zig"),
.target = b.host,
});
const tool_step = b.addRunArtifact(tool);
tool_step.addArg("--input-file");
tool_step.addFileArg(b.path("tools/words.json"));
tool_step.addArg("--output-file");
const output = tool_step.addOutputFileArg("word.txt");
tool_step.addArgs(&.{ "--lang", lang });
b.getInstallStep().dependOn(&b.addInstallFileWithDir(output, .prefix, "word.txt").step);
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const install_artifact = b.addInstallArtifact(exe, .{
.dest_dir = .{ .override = .prefix },
});
b.getInstallStep().dependOn(&install_artifact.step);
}
$ zig build--summary all
Build Summary: 6/6 steps succeeded
install cached
├─ install generated to word.txt cached
│ └─ run word_select (word.txt) cached
│ └─ zig build-exe word_select Debug native cached 43ms MaxRSS:36M
└─ install hello cached
└─ zig build-exe hello Debug native cached 83ms MaxRSS:36M
Вивід
zig-out
├── hello
└── word.txt
Створення Файлів для @embedFile
Ця версія hello world хоче @embedFile
ресурсу, створеного під час збирання, який ми збираємося створити за допомогою інструменту, написаного мовою Zig.
tools/words.json
{
"en": "world",
"it": "mondo",
"ja": "世界"
}
const std = @import("std");
const word = @embedFile("word");
pub fn main() !void {
try std.io.getStdOut().writer().print("Hello {s}\n", .{word});
}
const std = @import("std");
const usage =
\\Usage: ./word_select [options]
\\
\\Options:
\\ --input-file INPUT_JSON_FILE
\\ --output-file OUTPUT_TXT_FILE
\\ --lang LANG
\\
;
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(arena);
var opt_input_file_path: ?[]const u8 = null;
var opt_output_file_path: ?[]const u8 = null;
var opt_lang: ?[]const u8 = null;
{
var i: usize = 1;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
try std.io.getStdOut().writeAll(usage);
return std.process.cleanExit();
} else if (std.mem.eql(u8, "--input-file", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_input_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_input_file_path = args[i];
} else if (std.mem.eql(u8, "--output-file", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_output_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_output_file_path = args[i];
} else if (std.mem.eql(u8, "--lang", arg)) {
i += 1;
if (i > args.len) fatal("expected arg after '{s}'", .{arg});
if (opt_lang != null) fatal("duplicated {s} argument", .{arg});
opt_lang = args[i];
} else {
fatal("unrecognized arg: '{s}'", .{arg});
}
}
}
const input_file_path = opt_input_file_path orelse fatal("missing --input-file", .{});
const output_file_path = opt_output_file_path orelse fatal("missing --output-file", .{});
const lang = opt_lang orelse fatal("missing --lang", .{});
var input_file = std.fs.cwd().openFile(input_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ input_file_path, @errorName(err) });
};
defer input_file.close();
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
var json_reader = std.json.reader(arena, input_file.reader());
var words = try std.json.ArrayHashMap([]const u8).jsonParse(arena, &json_reader, .{
.allocate = .alloc_if_needed,
.max_value_len = 1000,
});
const w = words.map.get(lang) orelse fatal("Lang not found in JSON file", .{});
try output_file.writeAll(w);
return std.process.cleanExit();
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const lang = b.option([]const u8, "language", "language of the greeting") orelse "en";
const tool = b.addExecutable(.{
.name = "word_select",
.root_source_file = b.path("tools/word_select.zig"),
.target = b.host,
});
const tool_step = b.addRunArtifact(tool);
tool_step.addArg("--input-file");
tool_step.addFileArg(b.path("tools/words.json"));
tool_step.addArg("--output-file");
const output = tool_step.addOutputFileArg("word.txt");
tool_step.addArgs(&.{ "--lang", lang });
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addAnonymousImport("word", .{
.root_source_file = output,
});
b.installArtifact(exe);
}
$ zig build--summary all
Build Summary: 5/5 steps succeeded
install cached
└─ install hello cached
└─ zig build-exe hello Debug native cached 51ms MaxRSS:36M
└─ run word_select (word.txt) cached
└─ zig build-exe word_select Debug native cached 88ms MaxRSS:36M
Вивід
zig-out/
└── bin
└── hello
Створення Вихідного Коду Zig
Цей файл збірки використовує програму Zig для створення файлу Zig, а потім надає його основній програмі як залежність від модуля.
const std = @import("std");
const Person = @import("person").Person;
pub fn main() !void {
const p: Person = .{};
try std.io.getStdOut().writer().print("Hello {any}\n", .{p});
}
const std = @import("std");
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(arena);
if (args.len != 2) fatal("wrong number of arguments", .{});
const output_file_path = args[1];
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
try output_file.writeAll(
\\pub const Person = struct {
\\ age: usize = 18,
\\ name: []const u8 = "foo"
\\};
);
return std.process.cleanExit();
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const tool = b.addExecutable(.{
.name = "generate_struct",
.root_source_file = b.path("tools/generate_struct.zig"),
.target = b.host,
});
const tool_step = b.addRunArtifact(tool);
const output = tool_step.addOutputFileArg("person.zig");
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addAnonymousImport("person", .{
.root_source_file = output,
});
b.installArtifact(exe);
}
$ zig build--summary all
Build Summary: 5/5 steps succeeded
install cached
└─ install hello cached
└─ zig build-exe hello Debug native cached 39ms MaxRSS:36M
└─ run generate_struct (person.zig) cached
└─ zig build-exe generate_struct Debug native cached 53ms MaxRSS:36M
Вивід
zig-out/
└── bin
└── hello
Робота з Одним або Кількома Згенерованими Файлами
Крок WriteFiles забезпечує спосіб створення одного або кількох файлів, які спільно використовують батьківський каталог. Згенерований каталог знаходиться в локальному .zig-cache
, і кожен згенерований файл доступний окремо як std.Build.LazyPath
. Сам батьківський каталог також доступний як LazyPath
.
Цей API підтримує запис довільних рядків у створений каталог, а також копіювання файлів у нього.
const std = @import("std");
pub fn main() !void {
std.debug.print("hello world\n", .{});
}
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "app",
.root_source_file = b.path("src/main.zig"),
.target = b.host,
});
const version = b.option([]const u8, "version", "application version string") orelse "0.0.0";
const wf = b.addWriteFiles();
const app_exe_name = b.fmt("project/{s}", .{exe.out_filename});
_ = wf.addCopyFile(exe.getEmittedBin(), app_exe_name);
_ = wf.add("project/version.txt", version);
const tar = b.addSystemCommand(&.{ "tar", "czf" });
tar.setCwd(wf.getDirectory());
const out_file = tar.addOutputFileArg("project.tar.gz");
tar.addArgs(&.{"project/"});
const install_tar = b.addInstallFileWithDir(out_file, .prefix, "project.tar.gz");
b.getInstallStep().dependOn(&install_tar.step);
}
$ zig build--summary all
Build Summary: 5/5 steps succeeded
install cached
└─ install generated to project.tar.gz cached
└─ run tar (project.tar.gz) cached
└─ WriteFile project/app cached
└─ zig build-exe app Debug native cached 68ms MaxRSS:36M
Вивід
zig-out/
└── project.tar.gz
Зміна Вихідних Файлів на Місці
Нечасто, але іноді трапляється, що проект передає згенеровані файли в систему керування версіями. Це може бути корисним, коли згенеровані файли рідко оновлюються та мають обтяжливі системні залежності для процесу оновлення, але лише під час процесу оновлення.
Для цього WriteFiles пропонує спосіб виконання цього завдання. Це функція, яку буде витягнуто з WriteFiles у власний крок збірки у майбутній версії Zig.
Будьте обережні з цією функціональністю; його слід використовувати не під час звичайного процесу збирання, а як утиліту, яку запускає розробник з наміром оновити вихідні файли, які потім будуть передані контролю версій. Якщо це зробити під час звичайного процесу збірки, це спричинить кешування та помилки паралелізму.
const std = @import("std");
pub fn main() !void {
var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const args = try std.process.argsAlloc(arena);
if (args.len != 2) fatal("wrong number of arguments", .{});
const output_file_path = args[1];
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
try output_file.writeAll(
\\pub const Header = extern struct {
\\ magic: u64,
\\ width: u32,
\\ height: u32,
\\};
);
return std.process.cleanExit();
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
const std = @import("std");
const Protocol = @import("protocol.zig");
pub fn main() !void {
const header = try std.io.getStdIn().reader().readStruct(Protocol.Header);
std.debug.print("header: {any}\n", .{header});
}
pub const Header = extern struct {
magic: u64,
width: u32,
height: u32,
};
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe = b.addExecutable(.{
.name = "demo",
.root_source_file = b.path("src/main.zig"),
});
b.installArtifact(exe);
const proto_gen = b.addExecutable(.{
.name = "proto_gen",
.root_source_file = b.path("tools/proto_gen.zig"),
});
const run = b.addRunArtifact(proto_gen);
const generated_protocol_file = run.addOutputFileArg("protocol.zig");
const wf = b.addWriteFiles();
wf.addCopyFileToSource(generated_protocol_file, "src/protocol.zig");
const update_protocol_step = b.step("update-protocol", "update src/protocol.zig to latest");
update_protocol_step.dependOn(&wf.step);
}
fn detectWhetherToEnableLibFoo() bool {
return false;
}
$ zig build update-protocol --summary all
Build Summary: 4/4 steps succeeded
update-protocol success
└─ WriteFile success
└─ run proto_gen (protocol.zig) success 401us MaxRSS:1M
└─ zig build-exe proto_gen Debug native success 1s MaxRSS:183M
Після виконання цієї команди src/protocol.zig
оновлюється на місці.
Зручні Приклади
Збірка для Кількох Цілей, щоб Зробити Реліз
У цьому прикладі ми збираємося змінити деякі параметри за замовчуванням під час створення кроку InstallArtifact
, щоб розмістити збірку для кожної цілі в окремому підкаталозі всередині шляху встановлення.
const std = @import("std");
const targets: []const std.Target.Query = &.{
.{ .cpu_arch = .aarch64, .os_tag = .macos },
.{ .cpu_arch = .aarch64, .os_tag = .linux },
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .gnu },
.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl },
.{ .cpu_arch = .x86_64, .os_tag = .windows },
};
pub fn build(b: *std.Build) !void {
for (targets) |t| {
const exe = b.addExecutable(.{
.name = "hello",
.root_source_file = b.path("hello.zig"),
.target = b.resolveTargetQuery(t),
.optimize = .ReleaseSafe,
});
const target_output = b.addInstallArtifact(exe, .{
.dest_dir = .{
.override = .{
.custom = try t.zigTriple(b.allocator),
},
},
});
b.getInstallStep().dependOn(&target_output.step);
}
}
$ zig build--summary all
Build Summary: 11/11 steps succeeded
install cached
├─ install hello cached
│ └─ zig build-exe hello ReleaseSafe aarch64-macos cached 33ms MaxRSS:36M
├─ install hello cached
│ └─ zig build-exe hello ReleaseSafe aarch64-linux cached 98ms MaxRSS:36M
├─ install hello cached
│ └─ zig build-exe hello ReleaseSafe x86_64-linux-gnu cached 37ms MaxRSS:33M
├─ install hello cached
│ └─ zig build-exe hello ReleaseSafe x86_64-linux-musl cached 43ms MaxRSS:36M
└─ install hello cached
└─ zig build-exe hello ReleaseSafe x86_64-windows cached 45ms MaxRSS:36M
const std = @import("std");
pub fn main() !void {
std.debug.print("Hello World!\n", .{});
}
Вивід
zig-out
├── aarch64-linux
│ └── hello
├── aarch64-macos
│ └── hello
├── x86_64-linux-gnu
│ └── hello
├── x86_64-linux-musl
│ └── hello
└── x86_64-windows
├── hello.exe
└── hello.pdb