Zig 工程化和包管理
本章将介绍如何组织 Zig 项目、管理依赖包以及构建大型应用程序的最佳实践。
项目结构
标准项目布局
my-zig-project/
├── build.zig # 构建脚本
├── build.zig.zon # 项目配置和依赖
├── src/ # 源代码目录
│ ├── main.zig # 主程序入口
│ ├── lib.zig # 库入口
│ ├── utils/ # 工具模块
│ │ ├── string.zig
│ │ └── math.zig
│ └── tests/ # 测试文件
│ ├── main_test.zig
│ └── utils_test.zig
├── examples/ # 示例代码
│ └── basic_usage.zig
├── docs/ # 文档
│ └── README.md
├── assets/ # 资源文件
│ └── config.json
└── zig-out/ # 构建输出目录
├── bin/
└── lib/创建新项目
bash
# 创建可执行程序项目
zig init-exe
# 创建库项目
zig init-lib
# 手动创建项目结构
mkdir my-project
cd my-project
mkdir src examples tests docs assets
touch build.zig build.zig.zon src/main.zig模块系统
基本模块结构
创建 src/utils/math.zig:
zig
const std = @import("std");
/// 计算两个数的最大公约数
pub fn gcd(a: u32, b: u32) u32 {
if (b == 0) return a;
return gcd(b, a % b);
}
/// 计算两个数的最小公倍数
pub fn lcm(a: u32, b: u32) u32 {
return (a * b) / gcd(a, b);
}
/// 检查一个数是否为质数
pub fn isPrime(n: u32) bool {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
var i: u32 = 3;
while (i * i <= n) : (i += 2) {
if (n % i == 0) return false;
}
return true;
}
/// 生成指定范围内的质数
pub fn generatePrimes(allocator: std.mem.Allocator, limit: u32) ![]u32 {
var primes = std.ArrayList(u32).init(allocator);
defer primes.deinit();
for (2..limit + 1) |n| {
if (isPrime(@intCast(n))) {
try primes.append(@intCast(n));
}
}
return primes.toOwnedSlice();
}
// 测试
test "math utilities" {
const testing = std.testing;
// 测试 GCD
try testing.expect(gcd(12, 8) == 4);
try testing.expect(gcd(17, 13) == 1);
// 测试 LCM
try testing.expect(lcm(4, 6) == 12);
try testing.expect(lcm(3, 7) == 21);
// 测试质数检查
try testing.expect(isPrime(2) == true);
try testing.expect(isPrime(17) == true);
try testing.expect(isPrime(4) == false);
try testing.expect(isPrime(1) == false);
}创建 src/utils/string.zig:
zig
const std = @import("std");
/// 反转字符串
pub fn reverse(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
var result = try allocator.alloc(u8, input.len);
for (input, 0..) |char, i| {
result[input.len - 1 - i] = char;
}
return result;
}
/// 检查字符串是否为回文
pub fn isPalindrome(input: []const u8) bool {
if (input.len <= 1) return true;
var left: usize = 0;
var right: usize = input.len - 1;
while (left < right) {
if (input[left] != input[right]) {
return false;
}
left += 1;
right -= 1;
}
return true;
}
/// 统计字符出现次数
pub fn countChars(allocator: std.mem.Allocator, input: []const u8) !std.HashMap(u8, u32) {
var map = std.HashMap(u8, u32).init(allocator);
for (input) |char| {
const result = try map.getOrPut(char);
if (result.found_existing) {
result.value_ptr.* += 1;
} else {
result.value_ptr.* = 1;
}
}
return map;
}
/// 分割字符串
pub fn split(allocator: std.mem.Allocator, input: []const u8, delimiter: u8) ![][]const u8 {
var parts = std.ArrayList([]const u8).init(allocator);
defer parts.deinit();
var start: usize = 0;
for (input, 0..) |char, i| {
if (char == delimiter) {
if (i > start) {
try parts.append(input[start..i]);
}
start = i + 1;
}
}
// 添加最后一部分
if (start < input.len) {
try parts.append(input[start..]);
}
return parts.toOwnedSlice();
}
// 测试
test "string utilities" {
const testing = std.testing;
const allocator = testing.allocator;
// 测试反转
const reversed = try reverse(allocator, "hello");
defer allocator.free(reversed);
try testing.expectEqualStrings("olleh", reversed);
// 测试回文检查
try testing.expect(isPalindrome("racecar") == true);
try testing.expect(isPalindrome("hello") == false);
try testing.expect(isPalindrome("") == true);
try testing.expect(isPalindrome("a") == true);
// 测试分割
const parts = try split(allocator, "a,b,c,d", ',');
defer allocator.free(parts);
try testing.expect(parts.len == 4);
try testing.expectEqualStrings("a", parts[0]);
try testing.expectEqualStrings("d", parts[3]);
}模块导入和使用
创建 src/utils.zig:
zig
// 重新导出工具模块
pub const math = @import("utils/math.zig");
pub const string = @import("utils/string.zig");
// 也可以选择性导出
pub const gcd = math.gcd;
pub const isPalindrome = string.isPalindrome;在 src/main.zig 中使用:
zig
const std = @import("std");
const utils = @import("utils.zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
std.debug.print("Zig 工程化示例\n");
// 使用数学工具
const a: u32 = 48;
const b: u32 = 18;
std.debug.print("gcd({}, {}) = {}\n", .{ a, b, utils.math.gcd(a, b) });
std.debug.print("lcm({}, {}) = {}\n", .{ a, b, utils.math.lcm(a, b) });
// 生成质数
const primes = try utils.math.generatePrimes(allocator, 30);
defer allocator.free(primes);
std.debug.print("30以内的质数: ");
for (primes) |prime| {
std.debug.print("{} ", .{prime});
}
std.debug.print("\n");
// 使用字符串工具
const test_string = "racecar";
std.debug.print("'{s}' 是回文: {}\n", .{ test_string, utils.string.isPalindrome(test_string) });
const reversed = try utils.string.reverse(allocator, "hello world");
defer allocator.free(reversed);
std.debug.print("'hello world' 反转: {s}\n", .{reversed});
// 分割字符串
const csv_data = "apple,banana,cherry,date";
const parts = try utils.string.split(allocator, csv_data, ',');
defer allocator.free(parts);
std.debug.print("CSV 数据分割:\n");
for (parts, 0..) |part, i| {
std.debug.print(" [{}]: {s}\n", .{ i, part });
}
}包管理
build.zig.zon 配置
创建 build.zig.zon:
zig
.{
.name = "my-zig-project",
.version = "0.1.0",
.description = "一个示例 Zig 项目",
.author = "Your Name",
.license = "MIT",
.dependencies = .{
// 示例依赖(实际使用时需要真实的包)
.json = .{
.url = "https://github.com/example/zig-json/archive/main.tar.gz",
.hash = "1234567890abcdef...", // 包的哈希值
},
.http = .{
.url = "https://github.com/example/zig-http/archive/v1.0.0.tar.gz",
.hash = "abcdef1234567890...",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"examples",
"docs/README.md",
},
}构建脚本
创建 build.zig:
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 = "my-zig-project",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
// 添加依赖
// const json_dep = b.dependency("json", .{});
// exe.addModule("json", json_dep.module("json"));
b.installArtifact(exe);
// 运行命令
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
// 库目标
const lib = b.addStaticLibrary(.{
.name = "my-zig-project",
.root_source_file = .{ .path = "src/lib.zig" },
.target = target,
.optimize = optimize,
});
b.installArtifact(lib);
// 测试
const unit_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
// 示例程序
const examples = [_][]const u8{
"basic_usage",
};
for (examples) |example_name| {
const example = b.addExecutable(.{
.name = example_name,
.root_source_file = .{ .path = b.fmt("examples/{s}.zig", .{example_name}) },
.target = target,
.optimize = optimize,
});
const example_run = b.addRunArtifact(example);
const example_step = b.step(
b.fmt("run-{s}", .{example_name}),
b.fmt("Run the {s} example", .{example_name})
);
example_step.dependOn(&example_run.step);
}
// 文档生成
const docs = b.addTest(.{
.root_source_file = .{ .path = "src/lib.zig" },
.target = target,
.optimize = optimize,
});
const docs_step = b.step("docs", "Generate documentation");
docs_step.dependOn(&docs.step);
// 格式化
const fmt_step = b.step("fmt", "Format source code");
const fmt_cmd = b.addFmt(.{
.paths = &[_][]const u8{ "src", "build.zig", "examples" },
});
fmt_step.dependOn(&fmt_cmd.step);
// 清理
const clean_step = b.step("clean", "Clean build artifacts");
const clean_cmd = b.addRemoveDirTree("zig-out");
clean_step.dependOn(&clean_cmd.step);
}测试组织
单元测试
创建 src/tests/math_test.zig:
zig
const std = @import("std");
const testing = std.testing;
const math_utils = @import("../utils/math.zig");
test "GCD 计算" {
try testing.expect(math_utils.gcd(12, 8) == 4);
try testing.expect(math_utils.gcd(17, 13) == 1);
try testing.expect(math_utils.gcd(0, 5) == 5);
try testing.expect(math_utils.gcd(7, 0) == 7);
}
test "LCM 计算" {
try testing.expect(math_utils.lcm(4, 6) == 12);
try testing.expect(math_utils.lcm(3, 7) == 21);
try testing.expect(math_utils.lcm(1, 5) == 5);
}
test "质数检查" {
// 质数
try testing.expect(math_utils.isPrime(2) == true);
try testing.expect(math_utils.isPrime(3) == true);
try testing.expect(math_utils.isPrime(17) == true);
try testing.expect(math_utils.isPrime(97) == true);
// 非质数
try testing.expect(math_utils.isPrime(1) == false);
try testing.expect(math_utils.isPrime(4) == false);
try testing.expect(math_utils.isPrime(15) == false);
try testing.expect(math_utils.isPrime(100) == false);
}
test "质数生成" {
const allocator = testing.allocator;
const primes = try math_utils.generatePrimes(allocator, 20);
defer allocator.free(primes);
const expected = [_]u32{ 2, 3, 5, 7, 11, 13, 17, 19 };
try testing.expectEqualSlices(u32, &expected, primes);
}集成测试
创建 src/tests/integration_test.zig:
zig
const std = @import("std");
const testing = std.testing;
const utils = @import("../utils.zig");
test "数学和字符串工具集成" {
const allocator = testing.allocator;
// 生成质数并转换为字符串
const primes = try utils.math.generatePrimes(allocator, 10);
defer allocator.free(primes);
// 将质数转换为字符串并连接
var result = std.ArrayList(u8).init(allocator);
defer result.deinit();
for (primes, 0..) |prime, i| {
if (i > 0) try result.append(',');
try result.writer().print("{}", .{prime});
}
const prime_string = try result.toOwnedSlice();
defer allocator.free(prime_string);
// 分割字符串并验证
const parts = try utils.string.split(allocator, prime_string, ',');
defer allocator.free(parts);
try testing.expect(parts.len == primes.len);
for (parts, 0..) |part, i| {
const parsed = try std.fmt.parseInt(u32, part, 10);
try testing.expect(parsed == primes[i]);
}
}配置管理
配置文件处理
创建 assets/config.json:
json
{
"app_name": "My Zig Project",
"version": "0.1.0",
"debug": true,
"server": {
"host": "localhost",
"port": 8080,
"max_connections": 100
},
"database": {
"url": "sqlite:///data.db",
"pool_size": 10
},
"features": {
"logging": true,
"metrics": false,
"auth": true
}
}创建 src/config.zig:
zig
const std = @import("std");
pub const ServerConfig = struct {
host: []const u8,
port: u16,
max_connections: u32,
};
pub const DatabaseConfig = struct {
url: []const u8,
pool_size: u32,
};
pub const FeatureFlags = struct {
logging: bool,
metrics: bool,
auth: bool,
};
pub const Config = struct {
app_name: []const u8,
version: []const u8,
debug: bool,
server: ServerConfig,
database: DatabaseConfig,
features: FeatureFlags,
const Self = @This();
pub fn loadFromFile(allocator: std.mem.Allocator, path: []const u8) !Self {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const content = try file.readToEndAlloc(allocator, 1024 * 1024);
defer allocator.free(content);
return try std.json.parseFromSlice(Self, allocator, content, .{});
}
pub fn save(self: Self, allocator: std.mem.Allocator, path: []const u8) !void {
const file = try std.fs.cwd().createFile(path, .{});
defer file.close();
try std.json.stringify(self, .{ .whitespace = .indent_2 }, file.writer());
}
pub fn validate(self: Self) !void {
if (self.server.port == 0) {
return error.InvalidPort;
}
if (self.server.max_connections == 0) {
return error.InvalidMaxConnections;
}
if (self.database.pool_size == 0) {
return error.InvalidPoolSize;
}
}
};
// 测试
test "配置加载和验证" {
const testing = std.testing;
const allocator = testing.allocator;
// 创建测试配置
const test_config = Config{
.app_name = "Test App",
.version = "1.0.0",
.debug = true,
.server = ServerConfig{
.host = "127.0.0.1",
.port = 3000,
.max_connections = 50,
},
.database = DatabaseConfig{
.url = "memory://",
.pool_size = 5,
},
.features = FeatureFlags{
.logging = true,
.metrics = false,
.auth = true,
},
};
// 验证配置
try test_config.validate();
// 测试无效配置
var invalid_config = test_config;
invalid_config.server.port = 0;
try testing.expectError(error.InvalidPort, invalid_config.validate());
}日志系统
创建 src/logger.zig:
zig
const std = @import("std");
pub const LogLevel = enum {
Debug,
Info,
Warn,
Error,
pub fn toString(self: LogLevel) []const u8 {
return switch (self) {
.Debug => "DEBUG",
.Info => "INFO",
.Warn => "WARN",
.Error => "ERROR",
};
}
pub fn color(self: LogLevel) []const u8 {
return switch (self) {
.Debug => "\x1b[36m", // 青色
.Info => "\x1b[32m", // 绿色
.Warn => "\x1b[33m", // 黄色
.Error => "\x1b[31m", // 红色
};
}
};
pub const Logger = struct {
level: LogLevel,
use_color: bool,
writer: std.fs.File.Writer,
const Self = @This();
pub fn init(level: LogLevel, use_color: bool) Self {
return Self{
.level = level,
.use_color = use_color,
.writer = std.io.getStdErr().writer(),
};
}
pub fn log(self: Self, comptime level: LogLevel, comptime fmt: []const u8, args: anytype) void {
if (@intFromEnum(level) < @intFromEnum(self.level)) return;
const timestamp = std.time.timestamp();
const dt = std.time.epoch.EpochSeconds{ .secs = @intCast(timestamp) };
const day_seconds = dt.getDaySeconds();
const hours = day_seconds.getHoursIntoDay();
const minutes = day_seconds.getMinutesIntoHour();
const seconds = day_seconds.getSecondsIntoMinute();
if (self.use_color) {
self.writer.print("{s}[{:02}:{:02}:{:02}] {s}: " ++ fmt ++ "\x1b[0m\n",
.{ level.color(), hours, minutes, seconds, level.toString() } ++ args) catch {};
} else {
self.writer.print("[{:02}:{:02}:{:02}] {s}: " ++ fmt ++ "\n",
.{ hours, minutes, seconds, level.toString() } ++ args) catch {};
}
}
pub fn debug(self: Self, comptime fmt: []const u8, args: anytype) void {
self.log(.Debug, fmt, args);
}
pub fn info(self: Self, comptime fmt: []const u8, args: anytype) void {
self.log(.Info, fmt, args);
}
pub fn warn(self: Self, comptime fmt: []const u8, args: anytype) void {
self.log(.Warn, fmt, args);
}
pub fn err(self: Self, comptime fmt: []const u8, args: anytype) void {
self.log(.Error, fmt, args);
}
};
// 全局日志实例
var global_logger: ?Logger = null;
pub fn initGlobalLogger(level: LogLevel, use_color: bool) void {
global_logger = Logger.init(level, use_color);
}
pub fn debug(comptime fmt: []const u8, args: anytype) void {
if (global_logger) |logger| logger.debug(fmt, args);
}
pub fn info(comptime fmt: []const u8, args: anytype) void {
if (global_logger) |logger| logger.info(fmt, args);
}
pub fn warn(comptime fmt: []const u8, args: anytype) void {
if (global_logger) |logger| logger.warn(fmt, args);
}
pub fn err(comptime fmt: []const u8, args: anytype) void {
if (global_logger) |logger| logger.err(fmt, args);
}示例程序
创建 examples/basic_usage.zig:
zig
const std = @import("std");
const utils = @import("../src/utils.zig");
const config = @import("../src/config.zig");
const logger = @import("../src/logger.zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// 初始化日志
logger.initGlobalLogger(.Info, true);
logger.info("启动基本使用示例", .{});
// 加载配置(如果存在)
const app_config = config.Config.loadFromFile(allocator, "assets/config.json") catch |err| {
logger.warn("无法加载配置文件: {}", .{err});
// 使用默认配置
config.Config{
.app_name = "示例应用",
.version = "0.1.0",
.debug = true,
.server = config.ServerConfig{
.host = "localhost",
.port = 8080,
.max_connections = 100,
},
.database = config.DatabaseConfig{
.url = "memory://",
.pool_size = 10,
},
.features = config.FeatureFlags{
.logging = true,
.metrics = false,
.auth = false,
},
};
};
defer app_config.deinit();
logger.info("应用配置: {s} v{s}", .{ app_config.value.app_name, app_config.value.version });
// 使用数学工具
logger.info("演示数学工具", .{});
const primes = try utils.math.generatePrimes(allocator, 50);
defer allocator.free(primes);
logger.info("50以内的质数数量: {}", .{primes.len});
// 使用字符串工具
logger.info("演示字符串工具", .{});
const test_text = "A man a plan a canal Panama";
const cleaned = try std.ascii.allocLowerString(allocator, test_text);
defer allocator.free(cleaned);
// 移除空格
var no_spaces = std.ArrayList(u8).init(allocator);
defer no_spaces.deinit();
for (cleaned) |char| {
if (char != ' ') {
try no_spaces.append(char);
}
}
const is_palindrome = utils.string.isPalindrome(no_spaces.items);
logger.info("'{s}' 是回文: {}", .{ test_text, is_palindrome });
logger.info("示例程序完成", .{});
}构建和运行
常用命令
bash
# 构建项目
zig build
# 运行主程序
zig build run
# 运行测试
zig build test
# 运行示例
zig build run-basic_usage
# 格式化代码
zig build fmt
# 生成文档
zig build docs
# 清理构建产物
zig build clean
# 构建发布版本
zig build -Doptimize=ReleaseFast
# 交叉编译
zig build -Dtarget=x86_64-linux-gnu持续集成
创建 .github/workflows/ci.yml:
yaml
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
with:
version: 0.12.0
- name: Run tests
run: zig build test
- name: Check formatting
run: zig build fmt -- --check
- name: Build examples
run: |
zig build run-basic_usage
- name: Build release
run: zig build -Doptimize=ReleaseFast最佳实践
1. 项目组织
- 使用清晰的目录结构
- 将相关功能组织到模块中
- 保持模块间的低耦合
- 编写全面的测试
2. 依赖管理
- 最小化外部依赖
- 固定依赖版本
- 定期更新依赖
- 审查依赖的安全性
3. 代码质量
- 使用一致的代码风格
- 编写文档注释
- 进行代码审查
- 使用静态分析工具
4. 构建和部署
- 自动化构建流程
- 使用持续集成
- 进行跨平台测试
- 优化构建性能
总结
本章介绍了 Zig 项目的工程化实践:
- ✅ 标准项目结构和模块组织
- ✅ 包管理和依赖处理
- ✅ 测试组织和配置管理
- ✅ 日志系统和示例程序
- ✅ 构建脚本和持续集成
- ✅ 工程化最佳实践
良好的工程化实践是构建可维护、可扩展 Zig 项目的基础。通过合理的项目组织、完善的测试覆盖和自动化的构建流程,可以大大提高开发效率和代码质量。
在下一章中,我们将学习 Zig 的构建系统。