From bdcc7051aab3d4e4920084954a623629c47a302f Mon Sep 17 00:00:00 2001 From: Tristan Ross Date: Mon, 8 Jun 2026 20:45:00 -0700 Subject: [PATCH 1/2] refactor: zig 0.16 and comptime reading --- .envrc | 1 + .gitignore | 7 ++- build.zig | 43 ++++++++++----- build.zig.zon | 7 +-- dtree.zig | 5 ++ dtree/reader.zig | 128 +++++++++++++++++++++++++------------------- dtree/types.zig | 24 +++++---- example.zig | 32 +++++------ flake.lock | 47 ++++++++++++++++ flake.nix | 55 +++++++++++++++++++ test/dtb.zig | 54 +++++++++++++++++++ test/riscv-virt.dtb | Bin 0 -> 5028 bytes test/root.zig | 3 ++ treefmt.nix | 9 ++++ 14 files changed, 318 insertions(+), 97 deletions(-) create mode 100644 .envrc create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 test/dtb.zig create mode 100644 test/riscv-virt.dtb create mode 100644 test/root.zig create mode 100644 treefmt.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index f366cdb..46968ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ -zig-cache +.zig-cache zig-out *.dtb +!test/*.dtb + +.direnv +result +result-* diff --git a/build.zig b/build.zig index 56779fe..3c28cbd 100644 --- a/build.zig +++ b/build.zig @@ -7,23 +7,40 @@ pub fn build(b: *std.Build) void { const no_docs = b.option(bool, "no-docs", "skip installing documentation") orelse false; const dtree = b.addModule("dtree", .{ - .root_source_file = .{ .path = b.pathFromRoot("dtree.zig") }, + .root_source_file = b.path("dtree.zig"), + .target = target, + .optimize = optimize, }); if (!no_tests) { const step_test = b.step("test", "Run all unit tests"); const unit_tests = b.addTest(.{ - .root_source_file = .{ - .path = b.pathFromRoot("dtree.zig"), - }, - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("dtree.zig"), + .target = target, + .optimize = optimize, + }), }); const run_unit_tests = b.addRunArtifact(unit_tests); step_test.dependOn(&run_unit_tests.step); + const integration_tests = b.addTest(.{ + .name = "integration-test", + .root_module = b.createModule(.{ + .root_source_file = b.path("test/root.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "dtree", .module = dtree }, + }, + }), + }); + + const run_integration_tests = b.addRunArtifact(integration_tests); + step_test.dependOn(&run_integration_tests.step); + if (!no_docs) { const docs = b.addInstallDirectory(.{ .source_dir = unit_tests.getEmittedDocs(), @@ -37,13 +54,15 @@ pub fn build(b: *std.Build) void { const exe_example = b.addExecutable(.{ .name = "example", - .root_source_file = .{ - .path = b.pathFromRoot("example.zig"), - }, - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("example.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "dtree", .module = dtree }, + }, + }), }); - exe_example.root_module.addImport("dtree", dtree); b.installArtifact(exe_example); } diff --git a/build.zig.zon b/build.zig.zon index 3b32fce..3bd2471 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,5 +1,6 @@ .{ - .name = "dtree", - .version = "0.1.0", - .paths = .{"."}, + .name = .dtree, + .version = "0.1.0", + .fingerprint = 0x3ceaa750f068abef, + .paths = .{"."}, } diff --git a/dtree.zig b/dtree.zig index 7ab5db7..3eef734 100644 --- a/dtree.zig +++ b/dtree.zig @@ -1,2 +1,7 @@ pub const Reader = @import("dtree/reader.zig"); pub const types = @import("dtree/types.zig"); + +test { + _ = Reader; + _ = types; +} diff --git a/dtree/reader.zig b/dtree/reader.zig index 8154652..8f834ee 100644 --- a/dtree/reader.zig +++ b/dtree/reader.zig @@ -13,11 +13,9 @@ pub const Node = union(enum) { depth: usize, name: []const u8, - pub fn format(self: Begin, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = options; - + pub fn format(self: Begin, writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.writeAll(@typeName(Begin)); - try writer.print("{{ .depth = {}, .name = \"{s}\" }}", .{ + try writer.print("{{ .depth = {d}, .name = \"{s}\" }}", .{ self.depth, self.name, }); @@ -33,11 +31,9 @@ pub const Node = union(enum) { name: []const u8, value: []const u8, - pub fn format(self: Prop, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = options; - + pub fn format(self: Prop, writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.writeAll(@typeName(Prop)); - try writer.print("{{ .depth = {}, .name = \"{s}\", .value = {any} }}", .{ + try writer.print("{{ .depth = {d}, .name = \"{s}\", .value = {any} }}", .{ self.depth, self.name, self.value, @@ -98,7 +94,7 @@ pub const NodeIterator = struct { } pub fn readInt(self: *NodeIterator, comptime T: type) T { - const len = @divExact(@typeInfo(T).Int.bits, 8); + const len = @divExact(@typeInfo(T).int.bits, 8); const pos = self.offset(); const value = self.reader.buff[pos..][0..len]; self.pos += len; @@ -114,8 +110,8 @@ pub const NodeIterator = struct { return res[0]; } - pub fn token(self: *NodeIterator) std.meta.IntToEnumError!types.Token { - return std.meta.intToEnum(types.Token, self.readInt(u32)); + pub fn token(self: *NodeIterator) error{InvalidToken}!types.Token { + return std.enums.fromInt(types.Token, self.readInt(u32)) orelse error.InvalidToken; } pub fn stringAt(self: *NodeIterator, off: usize) []const u8 { @@ -170,21 +166,15 @@ allocator: ?Allocator, hdr: types.Header, buff: []const u8, -fn init( - allocator: ?Allocator, - reader: anytype, - args: anytype, - errors: anytype, - initBufferFunc: fn (?Allocator, types.Header, @TypeOf(args)) (Allocator.Error || @TypeOf(reader).NoEofError || errors)![]const u8, -) !Self { - const hdr = try reader.readStructBig(types.Header); - if (hdr.magic != types.magic) return error.InvalidMagic; - - const buff = try initBufferFunc(allocator, hdr, args); - errdefer { - if (allocator) |alloc| alloc.free(buff); +fn readHeader(bytes: *const [@sizeOf(types.Header)]u8) types.Header { + var hdr: types.Header = @bitCast(bytes.*); + if (builtin.cpu.arch.endian() != std.builtin.Endian.big) { + std.mem.byteSwapAllFields(types.Header, &hdr); } + return hdr; +} +fn finish(allocator: ?Allocator, hdr: types.Header, buff: []const u8) error{ Truncated, OverRead, InvalidToken }!Self { const buffSize = hdr.totalsize - @sizeOf(types.Header); if (buff.len < buffSize) return error.Truncated; if (buff.len > buffSize) return error.OverRead; @@ -198,41 +188,26 @@ fn init( } pub fn initBuffer(buff: []const u8) !Self { - var stream = std.io.fixedBufferStream(buff); - return try init(null, stream.reader(), stream, error{}, (struct { - fn func( - _: ?Allocator, - hdr: types.Header, - argStream: std.io.FixedBufferStream([]const u8), - ) (Allocator.Error || std.io.FixedBufferStream([]const u8).Reader.NoEofError)![]const u8 { - return argStream.buffer[argStream.pos..hdr.totalsize]; - } - }).func); + if (buff.len < @sizeOf(types.Header)) return error.Truncated; + const hdr = readHeader(buff[0..@sizeOf(types.Header)]); + if (hdr.magic != types.magic) return error.InvalidMagic; + if (buff.len < hdr.totalsize) return error.Truncated; + return finish(null, hdr, buff[@sizeOf(types.Header)..hdr.totalsize]); } -pub fn initReader(alloc: Allocator, reader: anytype) !Self { - return try init(alloc, reader, reader, error{StreamTooLong}, (struct { - fn func( - argAlloc: ?Allocator, - hdr: types.Header, - argReader: @TypeOf(reader), - ) (Allocator.Error || @TypeOf(reader).NoEofError || error{StreamTooLong})![]const u8 { - return try argReader.readAllAlloc(argAlloc.?, hdr.totalsize - @sizeOf(types.Header)); - } - }).func); +pub fn initReader(alloc: Allocator, reader: *std.Io.Reader) !Self { + const hdr = readHeader(try reader.takeArray(@sizeOf(types.Header))); + if (hdr.magic != types.magic) return error.InvalidMagic; + + const buff = try reader.readAlloc(alloc, hdr.totalsize - @sizeOf(types.Header)); + errdefer alloc.free(buff); + return finish(alloc, hdr, buff); } -pub fn initFile(alloc: Allocator, file: std.fs.File) !Self { - return try init(alloc, file.reader(), file, std.fs.File.ReadError || std.fs.File.MetadataError || error{FileTooBig}, (struct { - fn func( - argAlloc: ?Allocator, - _: types.Header, - argFile: std.fs.File, - ) (Allocator.Error || std.fs.File.Reader.NoEofError || std.fs.File.ReadError || std.fs.File.MetadataError || error{FileTooBig})![]const u8 { - const metadata = try argFile.metadata(); - return try argFile.readToEndAlloc(argAlloc.?, metadata.size() - @sizeOf(types.Header)); - } - }).func); +pub fn initFile(alloc: Allocator, io: std.Io, file: std.Io.File) !Self { + var buf: [4096]u8 = undefined; + var file_reader = file.reader(io, &buf); + return initReader(alloc, &file_reader.interface); } pub fn deinit(self: *const Self) void { @@ -337,3 +312,46 @@ pub fn findLoose(self: *const Self, path: []const []const u8) ![]const u8 { } return error.NotFound; } + +pub fn findAs(self: *const Self, comptime T: type, path: []const []const u8) !T { + if (T == bool) { + _ = self.find(path) catch |err| { + if (err == error.NotFound) return false; + return err; + }; + return true; + } + + return decode(T, try self.find(path)); +} + +fn decode(comptime T: type, value: []const u8) error{WrongSize}!T { + return switch (@typeInfo(T)) { + .int => |info| blk: { + if (info.signedness != .unsigned) @compileError("findAs only supports unsigned integers, got " ++ @typeName(T)); + const len = @divExact(info.bits, 8); + if (value.len != len) return error.WrongSize; + break :blk std.mem.readInt(T, value[0..len], .big); + }, + .pointer => |info| blk: { + if (info.size != .slice or info.child != u8) @compileError("findAs only supports []const u8 slices, got " ++ @typeName(T)); + const end = std.mem.indexOfScalar(u8, value, 0) orelse value.len; + break :blk value[0..end]; + }, + else => @compileError("findAs does not support " ++ @typeName(T)), + }; +} + +test "decode reads an unsigned cell big-endian" { + try std.testing.expectEqual(@as(u32, 0x989680), try decode(u32, &.{ 0x00, 0x98, 0x96, 0x80 })); + try std.testing.expectEqual(@as(u64, 0x1), try decode(u64, &.{ 0, 0, 0, 0, 0, 0, 0, 1 })); +} + +test "decode rejects a mismatched width" { + try std.testing.expectError(error.WrongSize, decode(u32, &.{ 0x00, 0x98 })); +} + +test "decode strips a trailing NUL from a string" { + try std.testing.expectEqualStrings("ok", try decode([]const u8, "ok\x00")); + try std.testing.expectEqualStrings("nonul", try decode([]const u8, "nonul")); +} diff --git a/dtree/types.zig b/dtree/types.zig index 1ea7d73..916ae59 100644 --- a/dtree/types.zig +++ b/dtree/types.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub const magic: u32 = 0xd00dfeed; -pub const Header = packed struct { +pub const Header = extern struct { magic: u32, totalsize: u32, off_dt_struct: u32, @@ -14,31 +14,29 @@ pub const Header = packed struct { size_dt_strings: u32, size_dt_struct: u32, - pub fn format(self: Header, comptime _: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { - _ = options; - + pub fn format(self: Header, writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.writeAll(@typeName(Header)); - try writer.print("{{ .magic = 0x{x}, .totalsize = {}, .off_dt_struct = 0x{x}, .off_dt_strings = 0x{x}, .off_mem_rsvmap = 0x{x}, .version = {}, .last_comp_version = {}, .boot_cpuid_phys = {}, .size_dt_strings = {}, .size_dt_struct = {} }}", .{ + try writer.print("{{ .magic = 0x{x}, .totalsize = {d}, .off_dt_struct = 0x{x}, .off_dt_strings = 0x{x}, .off_mem_rsvmap = 0x{x}, .version = {d}, .last_comp_version = {d}, .boot_cpuid_phys = {d}, .size_dt_strings = {d}, .size_dt_struct = {d} }}", .{ self.magic, - std.fmt.fmtIntSizeDec(self.totalsize), + self.totalsize, self.off_dt_struct, self.off_dt_strings, self.off_mem_rsvmap, self.version, self.last_comp_version, self.boot_cpuid_phys, - std.fmt.fmtIntSizeDec(self.size_dt_strings), - std.fmt.fmtIntSizeDec(self.size_dt_struct), + self.size_dt_strings, + self.size_dt_struct, }); } }; -pub const ReserveEntry = packed struct { +pub const ReserveEntry = extern struct { address: u64, size: u64, }; -pub const Prop = packed struct { +pub const Prop = extern struct { len: u32, name: u32, }; @@ -50,3 +48,9 @@ pub const Token = enum(u32) { nop = 0x00000004, end = 0x00000009, }; + +test "on-disk layouts match the FDT spec" { + try std.testing.expectEqual(@as(usize, 40), @sizeOf(Header)); + try std.testing.expectEqual(@as(usize, 16), @sizeOf(ReserveEntry)); + try std.testing.expectEqual(@as(usize, 8), @sizeOf(Prop)); +} diff --git a/example.zig b/example.zig index 60444a5..3897777 100644 --- a/example.zig +++ b/example.zig @@ -1,28 +1,28 @@ const std = @import("std"); const dtree = @import("dtree"); -const alloc = std.heap.page_allocator; +pub fn main(init: std.process.Init) !void { + const io = init.io; + const gpa = init.gpa; -pub fn main() !void { - var args = try std.process.ArgIterator.initWithAllocator(alloc); + var args = try init.minimal.args.iterateAllocator(gpa); defer args.deinit(); - _ = args.next(); + _ = args.skip(); - const path = blk: { - const tmp = args.next() orelse return error.MissingArgument; - if (std.fs.path.isAbsolute(tmp)) break :blk try alloc.dupe(u8, tmp); + const path = args.next() orelse return error.MissingArgument; - const cwd = try std.process.getCwdAlloc(alloc); - defer alloc.free(cwd); - break :blk try std.fs.path.join(alloc, &.{ cwd, tmp }); - }; + const file = if (std.fs.path.isAbsolute(path)) + try std.Io.Dir.openFileAbsolute(io, path, .{}) + else + try std.Io.Dir.cwd().openFile(io, path, .{}); + defer file.close(io); - const file = try std.fs.openFileAbsolute(path, .{}); - defer file.close(); - - const fdt = try dtree.Reader.initFile(alloc, file); + const fdt = try dtree.Reader.initFile(gpa, io, file); defer fdt.deinit(); - try fdt.writeDts(std.io.getStdOut().writer()); + var stdout_buf: [4096]u8 = undefined; + var stdout = std.Io.File.stdout().writer(io, &stdout_buf); + try fdt.writeDts(&stdout.interface); + try stdout.interface.flush(); } diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6b9337e --- /dev/null +++ b/flake.lock @@ -0,0 +1,47 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1780971357, + "narHash": "sha256-GPzGbD+uyVwyYD5fcO6cw4UakmrpcKmDGEUUWtNSBwg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4ea89ff69f570c228c6d5f0d18eae61ea5a27ef5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..398140a --- /dev/null +++ b/flake.nix @@ -0,0 +1,55 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs"; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + self, + nixpkgs, + treefmt-nix, + ... + }@inputs: + let + inherit (nixpkgs) lib; + + nameValuePair = name: value: { inherit name value; }; + genAttrs = names: f: builtins.listToAttrs (map (n: nameValuePair n (f n)) names); + allSystems = [ + "x86_64-linux" + "aarch64-linux" + "aarch64-darwin" + ]; + + forAllSystems = + f: + genAttrs allSystems ( + system: + f { + inherit system; + pkgs = import nixpkgs { inherit system; }; + } + ); + + treefmtEval = forAllSystems ({ pkgs, ... }: treefmt-nix.lib.evalModule pkgs (import ./treefmt.nix)); + in + { + devShells = forAllSystems ( + { pkgs, ... }: + { + default = pkgs.mkShell { + name = "dtree-dev-shell"; + packages = with pkgs; [ + zig + ]; + }; + } + ); + + formatter = forAllSystems ({ system, ... }: treefmtEval.${system}.config.build.wrapper); + }; +} diff --git a/test/dtb.zig b/test/dtb.zig new file mode 100644 index 0000000..76ae44e --- /dev/null +++ b/test/dtb.zig @@ -0,0 +1,54 @@ +const std = @import("std"); +const dtree = @import("dtree"); + +const blob = @embedFile("riscv-virt.dtb").*; + +fn parsed() dtree.Reader { + @setEvalBranchQuota(1_000_000); + return dtree.Reader.initBuffer(&blob) catch unreachable; +} + +test "comptime: bake a u32 cell into a constant" { + @setEvalBranchQuota(1_000_000); + const timebase = comptime try parsed().findAs(u32, &.{ "", "cpus", "timebase-frequency" }); + try std.testing.expectEqual(@as(u32, 0x989680), timebase); +} + +test "comptime: string has its trailing NUL stripped" { + @setEvalBranchQuota(1_000_000); + const model = comptime try parsed().findAs([]const u8, &.{ "", "model" }); + try std.testing.expectEqualStrings("riscv-virtio,qemu", model); +} + +test "comptime: bool reports presence without erroring" { + @setEvalBranchQuota(1_000_000); + try std.testing.expect(comptime try parsed().findAs(bool, &.{ "", "model" })); + try std.testing.expect(!(comptime try parsed().findAs(bool, &.{ "", "not-a-real-prop" }))); +} + +test "findAs is strict about integer width" { + @setEvalBranchQuota(1_000_000); + try std.testing.expectError(error.WrongSize, comptime parsed().findAs(u32, &.{ "", "model" })); +} + +test "runtime: the same parse path works without comptime" { + const fdt = try dtree.Reader.initBuffer(&blob); + try std.testing.expectEqual(@as(u32, 0x989680), try fdt.findAs(u32, &.{ "", "cpus", "timebase-frequency" })); + try std.testing.expectEqualStrings("riscv-virtio,qemu", try fdt.findAs([]const u8, &.{ "", "model" })); +} + +test "runtime: find returns the raw big-endian value" { + const fdt = try dtree.Reader.initBuffer(&blob); + const raw = try fdt.find(&.{ "", "cpus", "timebase-frequency" }); + try std.testing.expectEqualSlices(u8, &.{ 0x00, 0x98, 0x96, 0x80 }, raw); +} + +test "runtime: writeDts renders the tree" { + const fdt = try dtree.Reader.initBuffer(&blob); + var buf: [16 * 1024]u8 = undefined; + var writer = std.Io.Writer.fixed(&buf); + try fdt.writeDts(&writer); + const out = writer.buffered(); + try std.testing.expect(std.mem.indexOf(u8, out, "cpus {") != null); + try std.testing.expect(std.mem.indexOf(u8, out, "timebase-frequency = <") != null); +} diff --git a/test/riscv-virt.dtb b/test/riscv-virt.dtb new file mode 100644 index 0000000000000000000000000000000000000000..b44c3beb4bf9ed441c928b6b1331d6471988e2c1 GIT binary patch literal 5028 zcmbVQJ&Ytr6|M$*78?v_%%5dId%gsuX?qs$tuL^|IB5Gp*}Cch$_^ zZghx%6cWoasU28yKtKip2{K0(B7=Yo3E7eZ0wXMr?|W6%(>v_>bS|m-)qC~od#}H) zs;=&LAN%k7N`&pmW$G6Fl2eU#e>T72 zvtg=&cL1mZ-$#?!$lD84?(l*_1B&424!FL=eE9rhK1`qavBZ9-UVHTp^aNw)5_#xL zo}hi_n;gIg>M&OF2>QcxZEs~~K_~u%RpyW%6J!1p_Bm?Q|FWn5ey1ldqWxB>YL%%% z-Zin-Yz6*|^44q19y@ z@m+TqS=Srbvykj!sju5N`tah+E&5Or@^g}i$$G0nGw^_cyb=$!S zmcTck$?N_0xGmM7QypUm$U>H&uUI@du3d5jK=JRJEe=c)=YPZ*)S6q(? zCYR~^P3KZG(d9jA5H z!GtwkZ7d>(WevUU&E6X4`eNKTKFXx?yFK&cI{$I-OVq*Le-Hi#cR#}DC#4?T{Sfjg zslI&`?W^$r5dOq@6gAun(IpecON!G}7pwWv>}c4I>Cf_gRJO06%6@Z&EnmI{!9MP3 zexLO~X1KMp-p{wc4~Nd@(|hgCDh5PZC90vp15cP3Pu8nuqjXAodf5>4seK_r$5D_a8!DUfy&6H3_c>NE`O!?Cfd_IFJ@%$-l!Raeuc_2U^OT0(kGyS7J zzUR?B-hm5?+%0`ShdS~2)37`)kNN!NF>w~TU(60JEDs*@U}i9Pf+IGtMQ>t$RGr`6rI~+~qMDKjv_cm@(A{=axHdNI1se zeR7UR{T?w7!>|XB$$d2pjxiXMi#+Q0h$*q)cK?`NRqDA$p#P7>J%Mpzte>F02s(k~ zy~0H(0j7k$Y~kmQE@C*^3*9nJyYggx#hOd~ydX7d6dkav}SSu5gZs??ZZdzi}?ap>JIG!#&U_g=l^p&nhuDfsT0vh4&+IJ6+B>A6YmU zC35hVQhc2cCB#kcAnjibR^N!v^l;D47{t4}XQz$z={@^H!G6tesY7qX2M)K|p+tP( zT(C#{eVmAY80J<4iuk}GtwV|Uz`0tuka2N4`bHN_*fpftg@%M2eKJpa) zegrus__%Pe6m&2E%UrG(W$+^=%f4jyFvsHL2rR@9@hk}PlV1voQg&o104Mw(c@l zE-Rz1+49_^+EkTQ*RnkCjkQ7Tvu1_kVc)D$YfP?svr@~fxs{%lxm&Be&hU}2=C`5? ztJ`eRPFrtlq=y0gxNaiz_6t(3ka)!cTbv1QvJ z75)L5@>4HeR;|93GWnhL{8DQ-wpy1t{CUXDw$x@JDRk?s$-%^>pFY<{R^f<076zM5 zCApmBNNNp$cim~12{}7|h3#x}d&;Mqw{?bn5dT+FA$l~7A4S6$ej=gTTjZZT_?u;o bQ~1!^7^Q`V9kJHs?ZzQ69*{qe&eZ<^(ug<9 literal 0 HcmV?d00001 diff --git a/test/root.zig b/test/root.zig new file mode 100644 index 0000000..a803675 --- /dev/null +++ b/test/root.zig @@ -0,0 +1,3 @@ +test { + _ = @import("dtb.zig"); +} diff --git a/treefmt.nix b/treefmt.nix new file mode 100644 index 0000000..b2d6f6d --- /dev/null +++ b/treefmt.nix @@ -0,0 +1,9 @@ +{ lib, pkgs, ... }: +{ + projectRootFile = "flake.nix"; + + programs = { + nixfmt.enable = true; + zig.enable = true; + }; +} From f3bfc1cd7ccbeffb419bdacf71a623e937092351 Mon Sep 17 00:00:00 2001 From: Tristan Ross Date: Mon, 8 Jun 2026 20:55:30 -0700 Subject: [PATCH 2/2] chore: add nix ci --- .github/workflows/ci.yml | 19 +++++++++++++++++++ flake.lock | 16 ++++++++++++++++ flake.nix | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..258a74e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,19 @@ +on: + pull_request: + workflow_dispatch: + push: + branches: + - master + tags: + - v?[0-9]+.[0-9]+.[0-9]+* + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + DeterminateCI: + uses: DeterminateSystems/ci/.github/workflows/workflow.yml@main + permissions: + id-token: write + contents: read diff --git a/flake.lock b/flake.lock index 6b9337e..b073635 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,20 @@ { "nodes": { + "flakever": { + "locked": { + "lastModified": 1763450705, + "narHash": "sha256-TUSrRfT76OAXty9A4fXlOOfVfJGDglFQs06b8b+f5NY=", + "owner": "numinit", + "repo": "flakever", + "rev": "a69629e4133fbcdf3c7aae477bd6687bb19e0778", + "type": "github" + }, + "original": { + "owner": "numinit", + "repo": "flakever", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1780971357, @@ -17,6 +32,7 @@ }, "root": { "inputs": { + "flakever": "flakever", "nixpkgs": "nixpkgs", "treefmt-nix": "treefmt-nix" } diff --git a/flake.nix b/flake.nix index 398140a..814faab 100644 --- a/flake.nix +++ b/flake.nix @@ -5,6 +5,7 @@ url = "github:numtide/treefmt-nix"; inputs.nixpkgs.follows = "nixpkgs"; }; + flakever.url = "github:numinit/flakever"; }; outputs = @@ -12,6 +13,7 @@ self, nixpkgs, treefmt-nix, + flakever, ... }@inputs: let @@ -25,6 +27,16 @@ "aarch64-darwin" ]; + flakeverConfig = flakever.lib.mkFlakever { + inherit inputs; + + digits = [ + 1 + 2 + 2 + ]; + }; + forAllSystems = f: genAttrs allSystems ( @@ -38,6 +50,8 @@ treefmtEval = forAllSystems ({ pkgs, ... }: treefmt-nix.lib.evalModule pkgs (import ./treefmt.nix)); in { + versionTemplate = "1.1pre--"; + devShells = forAllSystems ( { pkgs, ... }: { @@ -51,5 +65,25 @@ ); formatter = forAllSystems ({ system, ... }: treefmtEval.${system}.config.build.wrapper); + + checks = forAllSystems ( + { system, pkgs, ... }: + { + default = pkgs.stdenv.mkDerivation (finalAttrs: { + pname = "dtree"; + inherit (flakeverConfig) version; + + src = lib.cleanSource ./.; + + nativeBuildInputs = with pkgs; [ + zig + ]; + + doCheck = true; + }); + + formatting = treefmtEval.${system}.config.build.check self; + } + ); }; }