I’m trying to automate getting licenses and readmes from my dependencies, as well as urls in order to link to library source code in my gui (I’m trying to statically compile, and I want to be extra cautious about copyleft compliance). Right now, there seems to be no built-in way to parse the zon file directly, as my dependencies’ zon files aren’t available at compile time from the perspective of my build script, and I don’t know the type ahead of time since dependencies is a struct with named fields. I’m currently looking into using std.zig.Zoir directly, but I want to be sure there isn’t a better way before continuing.
Thanks!
vulpesx July 9, 2026, 4:12am 2
create a script that goes through zig-pkg, you can then wire that up in the build system to provide the results to your code
The build system generates a file, dependencies.zig, which contains information about all fetched packages (more details in this recent answer of mine). This file is exposed by the build runner and can be accessed from your build script:
const build_runner = @import("root");
const packages = build_runner.dependencies.packages;
inline for (@typeInfo(packages).@"struct".decl_names |hash| {
const package = @field(packages, hash);
// Check for a 'const available = false;' decl;
// a package that is a lazy dependency might not have been fetched yet.
const available = !@hasDecl(package, "available") or package.available;
if (available) {
const build_zig_zon_path = b.pathJoin(&.{ package.build_root, "build.zig.zon" });
// ...Do something with 'build_zig_zon_path'...
}
}
This way you can programmatically iterate over all potential build.zig.zon paths for all packages.
Do with this whatever you want; for example, you could compile a utility Zig program as part of your build for processing information in build.zig.zon files, run it with b.runArtifact and pass all paths as arguments to the run step.
Note that the above loop won’t cover the root build.zig zon, which you will need to include in your processing logic manually. Also note that build.zig.zon is optional and may not exist for all packages, so your processing logic will need to account for that possibility.
I understand that not all my packages have build.zig.zon, and that I am able to get the build.zig.zon file. My issue is that I don’t know how to parse the zon file once I have it at runtime, since I don’t know the exact type that matches it.
Your best bet is probably to use std.zig.Zoir, like you suggested yourself. std.zon requires a destination type, and while @import supports importing ZON as an implicitly typed literal, it only accepts string literals (not comptime-known string values).
Take a look at the source code for fromSliceAlloc, fromZoirNodeAlloc and Parser in lib/std/zon/parse.zig for a demonstration on how to navigate the tree of Zoir nodes. I haven’t personally used std.zig.Zoir but I would assume that you would assert that the root std.zig.Zoir.Node union is a struct_literal, iterate over the field names until you get a match for std.mem.eql(u8, name.get(zoir), "dependencies"), then do the same recursively for the matched field and each of the dependencies’ url fields.
Sze July 9, 2026, 3:20pm 6
This seems related:
also maybe take a look at the code for Zigistry: A registry for all Zig-lang packages and programs which is by the OP and likely uses an updated version to parse the dependencies of packages. (Haven’t looked into its current implementation)
Thanks! looking through the parse source code was easier than I expected.
here is the test file I came up with for others to reference
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const io = init.io;
var arena = std.heap.ArenaAllocator.init(init.gpa);
defer arena.deinit();
const alloc = arena.allocator();
const root_dir = std.Io.Dir.cwd();
const zon_file = try root_dir.readFileAllocOptions(io, "build.zig.zon", alloc, .unlimited, .of(u8), 0);
const zon_ast = try std.zig.Ast.parse(alloc, zon_file, .zon);
const zoir = try std.zig.ZonGen.generate(alloc, zon_ast, .{});
const start: std.zig.Zoir.Node.Index = .root;
const repr = start.get(zoir).struct_literal;
const dependencies = for (repr.names, 0..) |name, i| {
if (std.mem.eql(u8, name.get(zoir), "dependencies")) {
break repr.vals.at(@intCast(i)).get(zoir).struct_literal;
}
} else return error.NoDependenciesFound;
const Dependency = struct {
url: [:0]const u8,
hash: [:0]const u8,
};
for (dependencies.names, 0..) |name, i| {
std.debug.print("{s}:\n", .{name.get(zoir)});
const dependency = try std.zon.parse.fromZoirNodeAlloc(
Dependency,
alloc,
zon_ast,
zoir,
dependencies.vals.at(@intCast(i)),
null,
.{ .ignore_unknown_fields = true },
);
std.debug.print("\turl: {s}\n\thash: {s}\n\n", .{ dependency.url, dependency.hash });
}
}
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.