Skip to content

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 的构建系统。

本站内容仅供学习和研究使用。