概述
第22章介绍了构建系统的API,用于创建构件和配置构建;第23章演示了包含库和可执行文件的工作区组织。本章通过检查依赖管理来完成构建系统基础——Zig项目如何通过build.zig.zon清单和内置在Zig工具链中的包管理器来声明、获取、验证、缓存和集成外部包。Build.zig
与作为独立工具运行、拥有自己的元数据格式和解析算法的传统包管理器不同,Zig的包管理器是构建系统本身的一个组成部分,利用了用于编译构件的相同确定性缓存基础设施(参见Cache.zig)。build.zig.zon文件——一个Zig对象表示法(ZON)文档——作为包元数据、依赖声明和包含规则的单一事实来源,而build.zig则编排这些依赖如何集成到项目的模块图中。20
在本章结束时,你将理解依赖的完整生命周期:从在build.zig.zon中声明,通过加密验证和缓存,到在Zig源代码中进行模块注册和导入。你还将学习可重现构建的模式、懒加载依赖以及平衡便利性与安全性的本地开发工作流。
学习目标
- 理解
build.zig.zon清单文件的结构和语义(参见build.zig.zon模板)。 - 使用基于URL的获取和基于路径的本地引用声明依赖项。
- 解释加密哈希在依赖验证和内容寻址中的作用。
- 导航依赖解析管道:从获取到缓存再到可用性。
- 使用
b.dependency()和b.lazyDependency()将获取的依赖项集成到build.zig中。 - 区分急切和懒加载依赖策略。
- 理解可重现性保证:锁定文件、哈希验证和确定性清单。
- 使用全局包缓存并理解离线构建工作流。
- 使用
zig fetch命令进行依赖管理。
架构
build.zig.zon文件是一种Zig原生数据格式——本质上是一个单一匿名结构体字面量——用于描述包元数据。它在构建时由Zig编译器解析,提供强类型和熟悉的语法,同时保持人类可读性和简单的编写方式。与JSON或TOML不同,ZON受益于Zig的编译时评估,允许在构建过程中验证和转换结构化数据。
最小清单
每个build.zig.zon文件必须至少声明包名称、版本和最低支持的Zig版本:
.{
.name = "myproject",
.version = "0.1.0",
.minimum_zig_version = "0.15.2",
.fingerprint = 0x1234567890abcdef,
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"LICENSE",
},
}
.paths字段指定当此包被其他项目获取时包含哪些文件和目录。此包含列表直接影响计算的包哈希——只有列出的文件才计入哈希,确保确定性内容寻址。
.paths字段既作为包含过滤器又作为文档辅助。始终列出build.zig、build.zig.zon和你的源目录。排除生成的文件、测试工件和不应成为包规范内容的编辑器特定文件。
包标识和版本控制
.name和.version字段共同建立包标识。截至Zig 0.15.2,包管理器尚未执行自动版本解析或去重,但这些字段为未来增强做准备,并帮助人类维护者理解包关系。
.minimum_zig_version字段传达兼容性期望。当包声明最低版本时,如果当前Zig工具链较旧,构建系统将拒绝继续,防止由于缺少功能或更改语义而导致的模糊编译失败。
.fingerprint字段(在最小示例中省略但在模板中显示)是在包创建时生成一次且此后永不更改的唯一标识符。此指纹能够明确检测包分叉和更新,防止试图冒充上游项目的恶意分叉。
更改.fingerprint具有安全和信任影响。它表示此包与其来源是不同的实体,这可能会破坏信任链并在未来的Zig版本中混淆依赖解析。
声明依赖项
依赖项在.dependencies结构体中声明。每个依赖项必须提供.url和.hash对(对于远程包)或.path(对于本地包):
.{
.name = "consumer",
.version = "0.2.0",
.minimum_zig_version = "0.15.2",
.dependencies = .{
// Path-based dependency (local development)
.mylib = .{
.path = "../mylib",
},
// URL-based dependency would look like:
// .known_folders = .{
// .url = "https://github.com/ziglibs/known-folders/archive/refs/tags/v1.1.0.tar.gz",
// .hash = "1220c1aa96c9cf0a7df5848c9d50e0e1f1e8b6ac8e7f5e4c0f4c5e6f7a8b9c0d",
// },
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
基于URL的依赖项从网络获取,根据提供的哈希进行验证,并全局缓存。基于路径的依赖项引用相对于构建根目录的目录,在本地开发或vendoring依赖项时很有用。
哈希使用multihash格式,其中前缀1220表示SHA-256。这种内容寻址方法确保包由其内容而非URL标识,使包管理器对URL更改和镜像可用性具有弹性。
.hash字段是真相来源——包不是来自URL;它们来自哈希。URL只是获取与哈希匹配的内容的一个可能镜像。这种设计将包标识(内容)与包位置(URL)分开。
懒加载与急切加载依赖项
默认情况下,所有声明的依赖项都是急切的:它们在构建脚本运行之前被获取和验证。对于仅在特定条件下需要的可选依赖项(例如,调试工具、基准测试实用程序或平台特定扩展),你可以使用.lazy = true将它们标记为懒加载:
.{
.name = "app",
.version = "1.0.0",
.minimum_zig_version = "0.15.2",
.dependencies = .{
// Eager dependency: always fetched
.core = .{
.path = "../core",
},
// Lazy dependency: only fetched when actually used
.benchmark_utils = .{
.path = "../benchmark_utils",
.lazy = true,
},
.debug_visualizer = .{
.path = "../debug_visualizer",
.lazy = true,
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
懒加载依赖项直到build.zig通过b.lazyDependency()明确请求它们时才会被获取。如果构建脚本从未为给定包调用lazyDependency(),则该包保持未获取状态,节省下载时间和磁盘空间。
这种两阶段方法允许构建脚本声明可选依赖项,而不强制所有用户下载它们。当请求懒加载依赖项但尚不可用时,构建运行器将获取它,然后重新运行构建脚本——这是一个在灵活性和确定性之间取得平衡的透明过程。
依赖解析管道
理解Zig如何将.dependencies声明转换为可用模块,阐明了包管理器的设计,并有助于调试获取失败或集成问题。
1. 解析和验证
当你运行zig build时,编译器首先将build.zig.zon解析为ZON字面量(参见build_runner.zig)。此解析步骤验证语法并确保所有必需字段都存在。编译器检查:
- 每个依赖项具有
.url+.hash或.path(但不能同时具有两者) - 哈希字符串使用有效的multihash编码
.minimum_zig_version不比运行的工具链更新
2. 获取和验证
对于每个具有.url的急切依赖项,构建运行器:
从哈希计算唯一缓存键
检查包是否存在于全局缓存中(在类Unix系统上为
~/.cache/zig/p/<hash>/)如果未缓存,则下载URL内容
如果需要,提取存档(支持
.tar.gz、.tar.xz、.zip)应用依赖项自己的
build.zig.zon中的.paths过滤器计算过滤内容的哈希
验证其匹配声明的
.hash字段将验证的内容存储在全局缓存中
如果哈希验证失败,构建将中止,并显示指示哈希不匹配的清晰错误消息。这防止了供应链攻击,其中受损的镜像提供不同的内容。
基于路径的依赖项跳过获取步骤——它们始终相对于构建根目录可用。
3. 缓存查找和重用
一旦包被缓存,后续构建将重用缓存的版本,而无需重新下载或重新验证。全局缓存在系统上的所有Zig项目之间共享,因此获取一次流行的依赖项会使所有项目受益。
缓存目录结构是内容寻址的:每个包的哈希直接映射到缓存子目录。这使得缓存管理透明且可预测——你可以检查缓存的包或清除缓存,而不会破坏构建状态的风险。
4. 依赖图构建
在所有急切依赖项可用后,构建运行器构建依赖图。每个包的build.zig作为Zig模块加载,并调用build()函数来注册构件和步骤。
懒加载依赖项不在此阶段加载。相反,构建运行器将它们标记为"可能需要的"并继续。如果build.zig为尚未获取的懒加载包调用b.lazyDependency(),构建运行器记录请求,完成当前构建过程,获取懒加载依赖项,然后重新运行构建脚本。
这种延迟获取机制允许构建脚本根据用户选项或目标特征有条件地加载依赖项,而不强制所有用户下载每个可选包。
在内部,Zig在InternPool中记录对ZON清单和其他依赖项的依赖关系,以便对build.zig.zon或嵌入文件的更改仅使依赖于它们的分析单元失效:
ZON files participate in the same incremental compilation graph as source hashes and embedded files: updating build.zig.zon updates the corresponding zon_file_deps entries, which in turn mark dependent analysis units and build steps as outdated.
More broadly, ZON manifests are just one of several dependee categories that the compiler tracks; at a high level these groups look like this:
The package manager sits on top of this infrastructure: .dependencies entries in build.zig.zon ultimately translate into ZON-file dependees and cached content that participate in the same dependency system.
Conceptual Example: Resolution Pipeline
The following example demonstrates the logical flow of dependency resolution:
// Conceptual example showing the dependency resolution pipeline
const std = @import("std");
const DependencyState = enum {
declared, // Listed in build.zig.zon
downloading, // URL being fetched
verifying, // Hash being checked
cached, // Stored in global cache
available, // Ready for use
};
const Dependency = struct {
name: []const u8,
url: ?[]const u8,
path: ?[]const u8,
hash: ?[]const u8,
lazy: bool,
state: DependencyState,
};
pub fn main() !void {
std.debug.print("--- Zig Package Manager Resolution Pipeline ---\n\n", .{});
// Stage 1: Parse build.zig.zon
std.debug.print("1. Parse build.zig.zon dependencies\n", .{});
var deps = [_]Dependency{
.{
.name = "core",
.path = "../core",
.url = null,
.hash = null,
.lazy = false,
.state = .declared,
},
.{
.name = "utils",
.url = "https://example.com/utils.tar.gz",
.path = null,
.hash = "1220abcd...",
.lazy = false,
.state = .declared,
},
.{
.name = "optional_viz",
.url = "https://example.com/viz.tar.gz",
.path = null,
.hash = "1220ef01...",
.lazy = true,
.state = .declared,
},
};
// Stage 2: Resolve eager dependencies
std.debug.print("\n2. Resolve eager dependencies\n", .{});
for (&deps) |*dep| {
if (!dep.lazy) {
std.debug.print(" - {s}: ", .{dep.name});
if (dep.path) |p| {
std.debug.print("local path '{s}' → available\n", .{p});
dep.state = .available;
} else if (dep.url) |_| {
std.debug.print("fetching → verifying → cached → available\n", .{});
dep.state = .available;
}
}
}
// Stage 3: Lazy dependencies deferred
std.debug.print("\n3. Lazy dependencies (deferred until used)\n", .{});
for (deps) |dep| {
if (dep.lazy) {
std.debug.print(" - {s}: waiting for lazyDependency() call\n", .{dep.name});
}
}
// Stage 4: Build script execution triggers lazy fetch
std.debug.print("\n4. Build script requests lazy dependency\n", .{});
std.debug.print(" - optional_viz requested → fetching now\n", .{});
// Stage 5: Cache lookup
std.debug.print("\n5. Cache locations\n", .{});
std.debug.print(" - Global: ~/.cache/zig/p/<hash>/\n", .{});
std.debug.print(" - Project: .zig-cache/\n", .{});
std.debug.print("\n=== Resolution Complete ===\n", .{});
}
$ zig run 07_resolution_pipeline_demo.zig=== Zig Package Manager Resolution Pipeline ===
1. Parse build.zig.zon dependencies
2. Resolve eager dependencies
- core: local path '../core' → available
- utils: fetching → verifying → cached → available
3. Lazy dependencies (deferred until used)
- optional_viz: waiting for lazyDependency() call
4. Build script requests lazy dependency
- optional_viz requested → fetching now
5. Cache locations
- Global: ~/.cache/zig/p/<hash>/
- Project: .zig-cache/
=== Resolution Complete ===This conceptual model matches the actual implementation in the 构建运行器 and standard library.
Integrating Dependencies in
Declaring a dependency in build.zig.zon makes it available for fetching; integrating it into your build requires calling b.dependency() or b.lazyDependency() in build.zig to obtain a *std.Build.Dependency handle, then extracting modules or artifacts from that dependency.
Using
For eager dependencies, use b.dependency(name, args) where name matches a key in .dependencies and args is a struct containing build options to pass down to the dependency’s build script:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 获取 build.zig.zon 中定义的依赖项
const mylib_dep = b.dependency("mylib", .{
.target = target,
.optimize = optimize,
});
// 从依赖项中获取模块
const mylib_module = mylib_dep.module("mylib");
const exe = b.addExecutable(.{
.name = "app",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
// 导入依赖项模块
exe.root_module.addImport("mylib", mylib_module);
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);
}
The b.dependency() call returns a *Dependency, which provides methods to access the dependency’s artifacts (.artifact()), modules (.module()), lazy paths (.path()), and named write-files (.namedWriteFiles()).
The args parameter forwards build options to the dependency, allowing you to configure the dependency’s target, optimization level, or custom features. This ensures the dependency is built with compatible settings.
Always pass .target and .optimize to dependencies unless you have a specific reason not to. Mismatched target settings can cause link errors or subtle ABI incompatibilities.
Using
For lazy dependencies, use b.lazyDependency(name, args) instead. This function returns ?*Dependency—null if the dependency has not yet been fetched:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 核心依赖项始终加载
const core_dep = b.dependency("core", .{
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "app",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
exe.root_module.addImport("core", core_dep.module("core"));
b.installArtifact(exe);
// 根据构建选项有条件地使用延迟依赖项
const enable_benchmarks = b.option(bool, "benchmarks", "Enable benchmark mode") orelse false;
const enable_debug_viz = b.option(bool, "debug-viz", "Enable debug visualizations") orelse false;
if (enable_benchmarks) {
// lazyDependency 如果未获取,则返回 null
if (b.lazyDependency("benchmark_utils", .{
.target = target,
.optimize = optimize,
})) |bench_dep| {
exe.root_module.addImport("benchmark", bench_dep.module("benchmark"));
}
}
if (enable_debug_viz) {
if (b.lazyDependency("debug_visualizer", .{
.target = target,
.optimize = optimize,
})) |viz_dep| {
exe.root_module.addImport("visualizer", viz_dep.module("visualizer"));
}
}
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
When lazyDependency() returns null, the build runner records the request and re-runs the build script after fetching the missing dependency. On the second pass, lazyDependency() will succeed, and the build proceeds normally.
This pattern allows build scripts to conditionally include optional features without forcing all users to fetch those dependencies:
$ zig build # Core functionality only
$ zig build -Dbenchmarks=true # Fetches benchmark_utils if needed
$ zig build -Ddebug-viz=true # Fetches debug_visualizer if neededMixing b.dependency() and b.lazyDependency() for the same package is an error. If a dependency is marked .lazy = true in build.zig.zon, you must use b.lazyDependency(). If it’s eager (default), you must use b.dependency(). The build system enforces this to prevent inconsistent fetch behavior.
Hash Verification and Multihash Format
Cryptographic hashes are central to Zig’s package manager, ensuring that fetched content matches expectations and protecting against tampering or corruption.
Multihash Format
Zig uses the multihash format to encode hash digests. A multihash string consists of:
A prefix indicating the hash algorithm (e.g.,
1220for SHA-256)The hex-encoded hash digest
For SHA-256, the prefix 1220 breaks down as:
12(hex) = SHA-256 algorithm identifier20(hex) = 32 bytes = SHA-256 digest length
The following example demonstrates conceptual hash computation (the actual implementation lives in the 构建运行器 and cache system):
// This example demonstrates how hash verification works conceptually.
// In practice, Zig handles this automatically during `zig fetch`.
const std = @import("std");
pub fn main() !void {
// Simulate fetching a package
const package_contents = "This is the package source code.";
// Compute the hash
var hasher = std.crypto.hash.sha2.Sha256.init(.{});
hasher.update(package_contents);
var digest: [32]u8 = undefined;
hasher.final(&digest); // Format as hex for display
std.debug.print("Package hash: {x}\n", .{digest});
std.debug.print("Expected hash in build.zig.zon: 1220{x}\n", .{digest});
std.debug.print("\nNote: The '1220' prefix indicates SHA-256 in multihash format.\n", .{});
}
$ zig run 06_hash_verification_example.zigPackage hash: 69b2de89d968f316b3679f2e68ecacb50fd3064e0e0ee7922df4e1ced43744d2
Expected hash in build.zig.zon: 122069b2de89d968f316b3679f2e68ecacb50fd3064e0e0ee7922df4e1ced43744d2
Note: The `1220` prefix indicates SHA-256 in multihash format.The compiler uses a similar "hash → compare → reuse" pattern for incremental compilation when deciding whether to reuse cached IR for a declaration:
This is conceptually the same as package hashing: for both source and dependencies, Zig computes a content hash, compares it with a cached value, and either reuses cached 构件 or recomputes them.
In practice, you rarely need to compute hashes manually. The zig fetch command automates this:
$ zig fetch https://example.com/package.tar.gzZig downloads the package, computes the hash, and prints the complete multihash string you can copy into build.zig.zon.
The multihash format is forward-compatible with future hash algorithms. If Zig adopts SHA-3 or BLAKE3, new prefix codes will identify those algorithms without breaking existing manifests.
可重现性 and Deterministic Builds
可重现性—the ability to recreate identical build outputs given the same inputs—is a cornerstone of reliable software distribution. Zig’s package manager contributes to reproducibility through content addressing, hash verification, and explicit versioning.
Content Addressing
Because packages are identified by hash rather than URL, the package manager is inherently resilient to URL changes, mirror failures, and upstream relocations. As long as some mirror provides content matching the hash, the package is usable.
This content-addressed design also prevents certain classes of supply-chain attacks: an attacker who compromises a single mirror cannot inject malicious code unless they also break the hash function (SHA-256), which is computationally infeasible.
The same content-addressing principle appears elsewhere in Zig’s implementation: the InternPool stores each distinct type or value exactly once and identifies it by an index, with dependency tracking built on top of these content-derived keys rather than on file paths or textual names.
Lockfile Semantics and Transitive Dependencies
As of Zig 0.15.2, the package manager does not generate a separate lockfile—build.zig.zon itself serves as the lockfile. Each dependency’s hash locks its content, and transitive dependencies are locked by the direct dependency’s hash (since the direct dependency’s build.zig.zon specifies its own dependencies).
This approach simplifies the mental model: there is one source of truth (build.zig.zon), and the hash chain ensures transitivity without additional metadata files.
Future Zig versions may introduce explicit lockfiles for advanced use cases (e.g., tracking resolved URLs or deduplicating transitive dependencies), but the core content-addressing principle will remain. v0.15.2
Offline Builds and Cache Portability
Once all dependencies are cached, you can build offline indefinitely. The global cache persists across projects, so fetching a dependency once benefits all future projects that use it.
To prepare for offline builds:
Run
zig build --fetchto fetch all declared dependencies without buildingVerify the cache is populated:
ls ~/.cache/zig/p/Disconnect from the network and run
zig buildnormally
If you need to transfer a project with its dependencies to an air-gapped environment, you can:
Fetch all dependencies on a networked machine
Archive the
~/.cache/zig/p/directoryExtract the archive on the air-gapped machine to the same cache location
Run
zig buildnormally
Path-based dependencies (.path = "…") do not require network access and work immediately offline.
Using for Dependency Management
The zig fetch command provides a CLI for managing dependencies without editing build.zig.zon manually.
Fetching and Saving Dependencies
To add a new dependency:
$ zig fetch --save https://github.com/example/package/archive/v1.0.0.tar.gzThis command:
Downloads the URL
Computes the hash
Adds an entry to
.dependenciesinbuild.zig.zonSaves the package name and hash
You can then reference the dependency by name in build.zig.
Fetching Without Saving
To fetch a URL and print its hash without modifying build.zig.zon:
$ zig fetch https://example.com/package.tar.gzThis is useful for verifying package integrity or preparing vendored dependencies.
Recursive Fetch
To fetch all dependencies transitively (including dependencies of dependencies):
$ zig build --fetchThis populates the cache with everything needed for a complete build, ensuring offline builds will succeed.
练习
Minimal Package: Create a new Zig library with
zig init-lib, examine the generatedbuild.zig.zon, and explain the purpose of each top-level field. 21Path-Based Dependency: Set up two sibling directories (
mylib/andmyapp/). Makemyappdepend onmylibusing.path, implement a simple function inmylib, call it frommyapp, and build successfully.Hash Verification Failure: Intentionally corrupt a dependency’s hash in
build.zig.zon(change one character) and runzig build. Observe and interpret the error message.Lazy Dependency Workflow: Create a project with a lazy dependency for a benchmarking module. Verify that
zig build(without options) does not fetch the dependency, butzig build -Dbenchmarks=truedoes.Cache Inspection: Run
zig build --fetchon a project with remote dependencies, then explore the global cache directory (~/.cache/zig/p/on Unix). Identify the package directories by their hash prefixes.Offline Build Test: Fetch all dependencies for a project, disconnect from the network (or block DNS resolution), and confirm
zig buildsucceeds. Reconnect and add a new dependency to verify fetch works again.
Notes & Caveats
- URL Stability: While content addressing makes the package manager resilient to URL changes, always prefer stable release URLs (tagged releases, not
mainbranch archives) to minimize maintenance burden. - Path Dependencies in Distributed Packages: If your package uses
.pathdependencies, those paths must exist relative to the package root when fetched by consumers. Prefer URL-based dependencies for distributed packages to avoid path resolution issues. - Transitive Dependency Deduplication: Zig 0.15.2 does not deduplicate transitive dependencies with different hash strings, even if they refer to the same content. Future versions may implement smarter deduplication.
- Security and Trust: Hash verification protects against transport corruption and most tampering, but does not validate package provenance. Trust the source of the hash (e.g., a project’s official repository or release page), not just any mirror.
- Build Option Forwarding: When calling
b.dependency(), carefully choose which build options to forward. Forwarding too many can cause build failures if the dependency doesn’t recognize an option; forwarding too few can result in mismatched configurations.
Caveats, Alternatives, and Edge Cases
- Lazy Dependency Refetch: If you delete a lazy dependency from the cache and re-run
zig buildwithout the option that triggers it, the dependency remains unfetched. Only when the build script callslazyDependency()again will the fetch occur. - Hash Mismatches After Upstream Changes: If an upstream package changes its content without changing its version tag, and you re-fetch the URL, you’ll encounter a hash mismatch. Always delete the old
.hashinbuild.zig.zonwhen updating a URL to signal that you expect new content. - Vendoring Dependencies: For projects with strict supply-chain requirements, consider vendoring dependencies by committing them to your repository (using
.pathreferences) instead of relying on URL-based fetches. This trades repository size for control. - Mirror Configuration: Zig 0.15.2 does not yet support mirror lists or fallback URLs per dependency. If your primary URL becomes unavailable, you must manually update
build.zig.zonto a new URL (the hash remains the same, ensuring content integrity). - Fingerprint Collisions: The
.fingerprintfield is a 64-bit value chosen randomly. Collisions are statistically unlikely but not impossible. Future Zig versions may detect and handle fingerprint conflicts during dependency resolution.
Summary
This chapter explored the full lifecycle of Zig package management:
- schema: Package metadata, dependency declarations, inclusion rules, and fingerprint identity.
- Dependency types: URL-based vs path-based; eager vs lazy loading strategies.
- Resolution pipeline: Parse → fetch → verify → cache → construct dependency graph.
- Integration in : Using
b.dependency()andb.lazyDependency()to access modules and artifacts. - Hash verification: Multihash format, SHA-256 content addressing, supply-chain protection.
- 可重现性:内容寻址、锁定文件语义、离线构建、缓存可移植性。
- 命令:从CLI添加、获取和验证依赖项。
你现在对Zig构建系统有了完整的心理模型:构件创建、工作区组织和依赖管理(本章)。下一章将通过深入探讨模块解析机制和发现模式来扩展这一基础。
理解包管理器的设计——内容寻址、懒加载、加密验证——使你能够构建可重现、安全且可维护的Zig项目,无论是独立工作还是将第三方库集成到生产系统中。