zig: upgrade from 0.11 to 0.12.0
ci/woodpecker/push/woodpecker Pipeline was successful Details

mostly lots of language improvements and bugfixes, leading to better
code here, from the programming language point of view.

zig v0.12.0 release notes:
https://ziglang.org/download/0.12.0/release-notes.html
master^2
alex 7 months ago
parent e07b1557c7
commit 729af48569
Signed by: x1ddos
GPG Key ID: FDEFB4A63CBD8460

@ -9,27 +9,27 @@ clone:
recursive: false recursive: false
pipeline: pipeline:
lint: lint:
image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
commands: commands:
- ./tools/fmt-check.sh - ./tools/fmt-check.sh
test: test:
image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
commands: commands:
- zig build test - zig build test
sdl2: sdl2:
image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
commands: commands:
- zig build -Ddriver=sdl2 - zig build -Ddriver=sdl2
x11: x11:
image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
commands: commands:
- zig build -Ddriver=x11 - zig build -Ddriver=x11
aarch64: aarch64:
image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
commands: commands:
- zig build -Ddriver=fbev -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe -Dstrip - zig build -Ddriver=fbev -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe -Dstrip
- sha256sum zig-out/bin/nd zig-out/bin/ngui - sha256sum zig-out/bin/nd zig-out/bin/ngui
playground: playground:
image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
commands: commands:
- zig build guiplay btcrpc lndhc - zig build guiplay btcrpc lndhc

@ -61,17 +61,17 @@ to make a new image and switch the CI to use it, first modify the
[ci-containerfile](tools/ci-containerfile) and produce the image locally: [ci-containerfile](tools/ci-containerfile) and produce the image locally:
podman build --rm -t ndg-ci -f ./tools/ci-containerfile \ podman build --rm -t ndg-ci -f ./tools/ci-containerfile \
--build-arg ZIGURL=https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz --build-arg ZIGURL=https://ziglang.org/download/0.12.0/zig-linux-x86_64-0.12.0.tar.xz
then tag it with the target URL, for example: then tag it with the target URL, for example:
podman tag localhost/ndg-ci git.qcode.ch/nakamochi/ci-zig0.11.0:v2 podman tag localhost/ndg-ci git.qcode.ch/nakamochi/ci-zig0.12.0:v1
generate an [access token](https://git.qcode.ch/user/settings/applications), generate an [access token](https://git.qcode.ch/user/settings/applications),
login to the container registry and push the image to remote: login to the container registry and push the image to remote:
podman login git.qcode.ch podman login git.qcode.ch
podman push git.qcode.ch/nakamochi/ci-zig0.11.0:v2 podman push git.qcode.ch/nakamochi/ci-zig0.12.0:v1
the image will be available at the image will be available at
https://git.qcode.ch/nakamochi/-/packages/ https://git.qcode.ch/nakamochi/-/packages/

@ -11,18 +11,16 @@ pub fn build(b: *std.Build) void {
const inver = b.option([]const u8, "version", "semantic version of the build; must match git tag when available"); const inver = b.option([]const u8, "version", "semantic version of the build; must match git tag when available");
const buildopts = b.addOptions(); const buildopts = b.addOptions();
const buildopts_mod = buildopts.createModule();
buildopts.addOption(DriverTarget, "driver", drv); buildopts.addOption(DriverTarget, "driver", drv);
const semver_step = VersionStep.create(b, buildopts, inver); const semver_step = VersionStep.create(b, buildopts, inver);
buildopts.step.dependOn(semver_step); buildopts.step.dependOn(semver_step);
// network interface (nif) standalone library used by the daemon and tests. // network interface (nif) standalone library used by the daemon and tests.
const libnif_dep = b.anonymousDependency("lib/nif", @import("lib/nif/build.zig"), .{ const libnif_dep = b.lazyDependency("nif", .{ .target = target, .optimize = optimize }) orelse return;
.target = target,
.optimize = optimize,
});
const libnif = libnif_dep.artifact("nif"); const libnif = libnif_dep.artifact("nif");
// ini file format parser // ini file format parser
const libini = b.addModule("ini", .{ .source_file = .{ .path = "lib/ini/src/ini.zig" } }); const libini_dep = b.lazyDependency("ini", .{ .target = target, .optimize = optimize }) orelse return;
const common_cflags = .{ const common_cflags = .{
"-Wall", "-Wall",
@ -35,16 +33,16 @@ pub fn build(b: *std.Build) void {
// gui build // gui build
const ngui = b.addExecutable(.{ const ngui = b.addExecutable(.{
.name = "ngui", .name = "ngui",
.root_source_file = .{ .path = "src/ngui.zig" }, .root_source_file = b.path("src/ngui.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.link_libc = true, .link_libc = true,
.strip = strip,
}); });
ngui.pie = true; ngui.pie = true;
ngui.strip = strip; ngui.root_module.addImport("build_options", buildopts_mod);
ngui.addOptions("build_options", buildopts); ngui.addIncludePath(b.path("lib"));
ngui.addIncludePath(.{ .path = "lib" }); ngui.addIncludePath(b.path("src/ui/c"));
ngui.addIncludePath(.{ .path = "src/ui/c" });
const lvgl_flags = .{ const lvgl_flags = .{
"-std=c11", "-std=c11",
@ -52,7 +50,7 @@ pub fn build(b: *std.Build) void {
"-Wformat", "-Wformat",
"-Wformat-security", "-Wformat-security",
} ++ common_cflags; } ++ common_cflags;
ngui.addCSourceFiles(lvgl_generic_src, &lvgl_flags); ngui.addCSourceFiles(.{ .files = lvgl_generic_src, .flags = &lvgl_flags });
const ngui_cflags = .{ const ngui_cflags = .{
"-std=c11", "-std=c11",
@ -60,39 +58,46 @@ pub fn build(b: *std.Build) void {
"-Wunused-parameter", "-Wunused-parameter",
"-Werror", "-Werror",
} ++ common_cflags; } ++ common_cflags;
ngui.addCSourceFiles(&.{ ngui.addCSourceFiles(.{
"src/ui/c/ui.c", .root = b.path("src/ui/c"),
"src/ui/c/lv_font_courierprimecode_14.c", .files = &.{
"src/ui/c/lv_font_courierprimecode_16.c", "ui.c",
"src/ui/c/lv_font_courierprimecode_24.c", "lv_font_courierprimecode_14.c",
}, &ngui_cflags); "lv_font_courierprimecode_16.c",
"lv_font_courierprimecode_24.c",
ngui.defineCMacroRaw(b.fmt("NM_DISP_HOR={}", .{disp_horiz})); },
ngui.defineCMacroRaw(b.fmt("NM_DISP_VER={}", .{disp_vert})); .flags = &ngui_cflags,
ngui.defineCMacro("LV_CONF_INCLUDE_SIMPLE", null); });
ngui.root_module.addCMacro("NM_DISP_HOR", b.fmt("{d}", .{disp_horiz}));
ngui.root_module.addCMacro("NM_DISP_VER", b.fmt("{d}", .{disp_vert}));
ngui.defineCMacro("LV_CONF_INCLUDE_SIMPLE", "1");
ngui.defineCMacro("LV_LOG_LEVEL", lvgl_loglevel.text()); ngui.defineCMacro("LV_LOG_LEVEL", lvgl_loglevel.text());
ngui.defineCMacro("LV_TICK_CUSTOM", "1"); ngui.defineCMacro("LV_TICK_CUSTOM", "1");
ngui.defineCMacro("LV_TICK_CUSTOM_INCLUDE", "\"lv_custom_tick.h\""); ngui.defineCMacro("LV_TICK_CUSTOM_INCLUDE", "\"lv_custom_tick.h\"");
ngui.defineCMacro("LV_TICK_CUSTOM_SYS_TIME_EXPR", "(nm_get_curr_tick())"); ngui.defineCMacro("LV_TICK_CUSTOM_SYS_TIME_EXPR", "(nm_get_curr_tick())");
switch (drv) { switch (drv) {
.sdl2 => { .sdl2 => {
ngui.addCSourceFiles(lvgl_sdl2_src, &lvgl_flags); ngui.addCSourceFiles(.{ .files = lvgl_sdl2_src, .flags = &lvgl_flags });
ngui.addCSourceFile(.{ .file = .{ .path = "src/ui/c/drv_sdl2.c" }, .flags = &ngui_cflags }); ngui.addCSourceFile(.{ .file = b.path("src/ui/c/drv_sdl2.c"), .flags = &ngui_cflags });
ngui.defineCMacro("USE_SDL", "1"); ngui.defineCMacro("USE_SDL", "1");
ngui.linkSystemLibrary("SDL2"); ngui.linkSystemLibrary("SDL2");
}, },
.x11 => { .x11 => {
ngui.addCSourceFiles(lvgl_x11_src, &lvgl_flags); ngui.addCSourceFiles(.{ .files = lvgl_x11_src, .flags = &lvgl_flags });
ngui.addCSourceFiles(&.{ ngui.addCSourceFiles(.{
"src/ui/c/drv_x11.c", .files = &.{
"src/ui/c/mouse_cursor_icon.c", "src/ui/c/drv_x11.c",
}, &ngui_cflags); "src/ui/c/mouse_cursor_icon.c",
},
.flags = &ngui_cflags,
});
ngui.defineCMacro("USE_X11", "1"); ngui.defineCMacro("USE_X11", "1");
ngui.linkSystemLibrary("X11"); ngui.linkSystemLibrary("X11");
}, },
.fbev => { .fbev => {
ngui.addCSourceFiles(lvgl_fbev_src, &lvgl_flags); ngui.addCSourceFiles(.{ .files = lvgl_fbev_src, .flags = &lvgl_flags });
ngui.addCSourceFile(.{ .file = .{ .path = "src/ui/c/drv_fbev.c" }, .flags = &ngui_cflags }); ngui.addCSourceFile(.{ .file = b.path("src/ui/c/drv_fbev.c"), .flags = &ngui_cflags });
ngui.defineCMacro("USE_FBDEV", "1"); ngui.defineCMacro("USE_FBDEV", "1");
ngui.defineCMacro("USE_EVDEV", "1"); ngui.defineCMacro("USE_EVDEV", "1");
}, },
@ -104,15 +109,15 @@ pub fn build(b: *std.Build) void {
// daemon build // daemon build
const nd = b.addExecutable(.{ const nd = b.addExecutable(.{
.name = "nd", .name = "nd",
.root_source_file = .{ .path = "src/nd.zig" }, .root_source_file = b.path("src/nd.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.strip = strip,
}); });
nd.pie = true; nd.pie = true;
nd.strip = strip; nd.root_module.addImport("build_options", buildopts_mod);
nd.addOptions("build_options", buildopts); nd.root_module.addImport("nif", libnif_dep.module("nif"));
nd.addModule("nif", libnif_dep.module("nif")); nd.root_module.addImport("ini", libini_dep.module("ini"));
nd.addModule("ini", libini);
nd.linkLibrary(libnif); nd.linkLibrary(libnif);
const nd_build_step = b.step("nd", "build nd (nakamochi daemon)"); const nd_build_step = b.step("nd", "build nd (nakamochi daemon)");
@ -121,15 +126,15 @@ pub fn build(b: *std.Build) void {
// automated tests // automated tests
{ {
const tests = b.addTest(.{ const tests = b.addTest(.{
.root_source_file = .{ .path = "src/test.zig" }, .root_source_file = b.path("src/test.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.link_libc = true, .link_libc = true,
.filter = b.option([]const u8, "test-filter", "run tests matching the filter"), .filter = b.option([]const u8, "test-filter", "run tests matching the filter"),
}); });
tests.addOptions("build_options", buildopts); tests.root_module.addImport("build_options", buildopts_mod);
tests.addModule("nif", libnif_dep.module("nif")); tests.root_module.addImport("nif", libnif_dep.module("nif"));
tests.addModule("ini", libini); tests.root_module.addImport("ini", libini_dep.module("ini"));
tests.linkLibrary(libnif); tests.linkLibrary(libnif);
const run_tests = b.addRunArtifact(tests); const run_tests = b.addRunArtifact(tests);
@ -141,11 +146,11 @@ pub fn build(b: *std.Build) void {
{ {
const guiplay = b.addExecutable(.{ const guiplay = b.addExecutable(.{
.name = "guiplay", .name = "guiplay",
.root_source_file = .{ .path = "src/test/guiplay.zig" }, .root_source_file = b.path("src/test/guiplay.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
guiplay.addModule("comm", b.createModule(.{ .source_file = .{ .path = "src/comm.zig" } })); guiplay.root_module.addImport("comm", b.createModule(.{ .root_source_file = b.path("src/comm.zig") }));
const guiplay_build_step = b.step("guiplay", "build GUI playground"); const guiplay_build_step = b.step("guiplay", "build GUI playground");
guiplay_build_step.dependOn(&b.addInstallArtifact(guiplay, .{}).step); guiplay_build_step.dependOn(&b.addInstallArtifact(guiplay, .{}).step);
@ -156,12 +161,12 @@ pub fn build(b: *std.Build) void {
{ {
const btcrpc = b.addExecutable(.{ const btcrpc = b.addExecutable(.{
.name = "btcrpc", .name = "btcrpc",
.root_source_file = .{ .path = "src/test/btcrpc.zig" }, .root_source_file = b.path("src/test/btcrpc.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.strip = strip,
}); });
btcrpc.strip = strip; btcrpc.root_module.addImport("bitcoindrpc", b.createModule(.{ .root_source_file = b.path("src/bitcoindrpc.zig") }));
btcrpc.addModule("bitcoindrpc", b.createModule(.{ .source_file = .{ .path = "src/bitcoindrpc.zig" } }));
const btcrpc_build_step = b.step("btcrpc", "bitcoind RPC client playground"); const btcrpc_build_step = b.step("btcrpc", "bitcoind RPC client playground");
btcrpc_build_step.dependOn(&b.addInstallArtifact(btcrpc, .{}).step); btcrpc_build_step.dependOn(&b.addInstallArtifact(btcrpc, .{}).step);
@ -171,12 +176,12 @@ pub fn build(b: *std.Build) void {
{ {
const lndhc = b.addExecutable(.{ const lndhc = b.addExecutable(.{
.name = "lndhc", .name = "lndhc",
.root_source_file = .{ .path = "src/test/lndhc.zig" }, .root_source_file = b.path("src/test/lndhc.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.strip = strip,
}); });
lndhc.strip = strip; lndhc.root_module.addImport("lightning", b.createModule(.{ .root_source_file = b.path("src/lightning.zig") }));
lndhc.addModule("lightning", b.createModule(.{ .source_file = .{ .path = "src/lightning.zig" } }));
const lndhc_build_step = b.step("lndhc", "lnd HTTP API client playground"); const lndhc_build_step = b.step("lndhc", "lnd HTTP API client playground");
lndhc_build_step.dependOn(&b.addInstallArtifact(lndhc, .{}).step); lndhc_build_step.dependOn(&b.addInstallArtifact(lndhc, .{}).step);
@ -423,7 +428,7 @@ const VersionStep = struct {
} }
fn make(step: *std.Build.Step, _: *std.Progress.Node) anyerror!void { fn make(step: *std.Build.Step, _: *std.Progress.Node) anyerror!void {
const self = @fieldParentPtr(VersionStep, "step", step); const self: *@This() = @fieldParentPtr("step", step);
const semver = try self.eval(); const semver = try self.eval();
std.log.info("build version: {any}", .{semver}); std.log.info("build version: {any}", .{semver});
self.buildopts.addOption(std.SemanticVersion, "semver", semver); self.buildopts.addOption(std.SemanticVersion, "semver", semver);
@ -460,7 +465,7 @@ const VersionStep = struct {
const matchTag = self.b.fmt("{s}*.*.*", .{prefix}); const matchTag = self.b.fmt("{s}*.*.*", .{prefix});
const cmd = [_][]const u8{ git, "-C", self.b.pathFromRoot("."), "describe", "--match", matchTag, "--tags", "--abbrev=8" }; const cmd = [_][]const u8{ git, "-C", self.b.pathFromRoot("."), "describe", "--match", matchTag, "--tags", "--abbrev=8" };
var code: u8 = undefined; var code: u8 = undefined;
const git_describe = self.b.execAllowFail(&cmd, &code, .Ignore) catch return null; const git_describe = self.b.runAllowFail(&cmd, &code, .Ignore) catch return null;
const repotag = std.mem.trim(u8, git_describe, " \n\r")[prefix.len..]; const repotag = std.mem.trim(u8, git_describe, " \n\r")[prefix.len..];
return std.SemanticVersion.parse(repotag) catch |err| ret: { return std.SemanticVersion.parse(repotag) catch |err| ret: {
std.log.err("unparsable git tag semver '{s}': {any}", .{ repotag, err }); std.log.err("unparsable git tag semver '{s}': {any}", .{ repotag, err });

@ -0,0 +1,17 @@
.{
.name = "ndg",
.version = "0.8.1",
.dependencies = .{
.nif = .{
.path = "lib/nif",
},
.ini = .{
.path = "lib/ini",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

@ -0,0 +1,10 @@
.{
.name = "libini",
.version = "0.0.0",
.dependencies = .{},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

@ -1,30 +1,33 @@
const std = @import("std"); const std = @import("std");
pub fn build(b: *std.Build) void { pub fn build(b: *std.Build) void {
_ = b.addModule("nif", .{ .source_file = .{ .path = "nif.zig" } }); _ = b.addModule("nif", .{ .root_source_file = b.path("nif.zig") });
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const lib = b.addStaticLibrary(.{ const lib = b.addStaticLibrary(.{
.name = "nif", .name = "nif",
.root_source_file = .{ .path = "nif.zig" }, .root_source_file = b.path("nif.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.link_libc = true, .link_libc = true,
}); });
lib.defineCMacro("CONFIG_CTRL_IFACE", null); lib.defineCMacro("CONFIG_CTRL_IFACE", null);
lib.defineCMacro("CONFIG_CTRL_IFACE_UNIX", null); lib.defineCMacro("CONFIG_CTRL_IFACE_UNIX", null);
lib.addIncludePath(.{ .path = "wpa_supplicant" }); lib.addIncludePath(b.path("wpa_supplicant"));
lib.addCSourceFiles(&.{ lib.addCSourceFiles(.{
"wpa_supplicant/wpa_ctrl.c", .files = &.{
"wpa_supplicant/os_unix.c", "wpa_supplicant/wpa_ctrl.c",
}, &.{ "wpa_supplicant/os_unix.c",
"-Wall", },
"-Wextra", .flags = &.{
"-Wshadow", "-Wall",
"-Wundef", "-Wextra",
"-Wunused-parameter", "-Wshadow",
"-Werror", "-Wundef",
"-Wunused-parameter",
"-Werror",
},
}); });
b.installArtifact(lib); b.installArtifact(lib);
} }

@ -0,0 +1,10 @@
.{
.name = "libnif",
.version = "0.0.1",
.dependencies = .{},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const net = std.net; const net = std.net;
const os = std.os; const posix = std.posix;
pub const wpa = @import("wpa.zig"); pub const wpa = @import("wpa.zig");
@ -12,11 +12,11 @@ const ifaddrs = extern struct {
next: ?*ifaddrs, next: ?*ifaddrs,
name: [*:0]const u8, name: [*:0]const u8,
flags: c_uint, // see IFF_xxx SIOCGIFFLAGS in netdevice(7) flags: c_uint, // see IFF_xxx SIOCGIFFLAGS in netdevice(7)
addr: ?*std.os.sockaddr, addr: ?*std.posix.sockaddr,
netmask: ?*std.os.sockaddr, netmask: ?*std.posix.sockaddr,
ifu: extern union { ifu: extern union {
broad: *os.sockaddr, // flags & IFF_BROADCAST broad: *posix.sockaddr, // flags & IFF_BROADCAST
dst: *os.sockaddr, // flags & IFF_POINTOPOINT dst: *posix.sockaddr, // flags & IFF_POINTOPOINT
}, },
data: ?*anyopaque, data: ?*anyopaque,
}; };
@ -37,8 +37,8 @@ pub fn pubAddresses(allocator: mem.Allocator, ifname: ?[]const u8) ![]net.Addres
var list = std.ArrayList(net.Address).init(allocator); var list = std.ArrayList(net.Address).init(allocator);
var it: ?*ifaddrs = res; var it: ?*ifaddrs = res;
while (it) |ifa| : (it = ifa.next) { while (it) |ifa| : (it = ifa.next) {
const sa: *os.sockaddr = ifa.addr orelse continue; const sa: *posix.sockaddr = ifa.addr orelse continue;
if (sa.family != os.AF.INET and sa.family != os.AF.INET6) { if (sa.family != posix.AF.INET and sa.family != posix.AF.INET6) {
// not an IP address // not an IP address
continue; continue;
} }
@ -47,7 +47,7 @@ pub fn pubAddresses(allocator: mem.Allocator, ifname: ?[]const u8) ![]net.Addres
continue; continue;
} }
const ipaddr = net.Address.initPosix(@alignCast(sa)); // initPosix makes a copy const ipaddr = net.Address.initPosix(@alignCast(sa)); // initPosix makes a copy
if (ipaddr.any.family == os.AF.INET6 and ipaddr.in6.sa.scope_id > 0) { if (ipaddr.any.family == posix.AF.INET6 and ipaddr.in6.sa.scope_id > 0) {
// want only global, with 0 scope // want only global, with 0 scope
// non-zero scopes make sense for link-local addr only. // non-zero scopes make sense for link-local addr only.
continue; continue;

@ -2,7 +2,7 @@
const std = @import("std"); const std = @import("std");
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
const Atomic = std.atomic.Atomic; const Atomic = std.atomic.Value;
const base64enc = std.base64.standard.Encoder; const base64enc = std.base64.standard.Encoder;
const types = @import("types.zig"); const types = @import("types.zig");
@ -13,7 +13,7 @@ pub const Client = struct {
addr: []const u8 = "127.0.0.1", addr: []const u8 = "127.0.0.1",
port: u16 = 8332, port: u16 = 8332,
// each request gets a new ID with a value of reqid.fetchAdd(1, .Monotonic) // each request gets a new ID with a value of reqid.fetchAdd(1, .monotonic)
reqid: Atomic(u64) = Atomic(u64).init(1), reqid: Atomic(u64) = Atomic(u64).init(1),
pub const Method = enum { pub const Method = enum {
@ -153,7 +153,7 @@ pub const Client = struct {
/// callers own returned value. /// callers own returned value.
fn formatreq(self: *Client, comptime m: Method, args: MethodArgs(m)) ![]const u8 { fn formatreq(self: *Client, comptime m: Method, args: MethodArgs(m)) ![]const u8 {
const req = RpcRequest(m){ const req = RpcRequest(m){
.id = self.reqid.fetchAdd(1, .Monotonic), .id = self.reqid.fetchAdd(1, .monotonic),
.method = @tagName(m), .method = @tagName(m),
.params = args, .params = args,
}; };
@ -183,7 +183,7 @@ pub const Client = struct {
defer file.close(); defer file.close();
const cookie = try file.readToEndAlloc(self.allocator, 1024); const cookie = try file.readToEndAlloc(self.allocator, 1024);
defer self.allocator.free(cookie); defer self.allocator.free(cookie);
var auth = try self.allocator.alloc(u8, base64enc.calcSize(cookie.len)); const auth = try self.allocator.alloc(u8, base64enc.calcSize(cookie.len));
return base64enc.encode(auth, cookie); return base64enc.encode(auth, cookie);
} }

@ -56,15 +56,15 @@ pub const MessageTag = enum(u16) {
ping = 0x01, ping = 0x01,
pong = 0x02, pong = 0x02,
poweroff = 0x03, poweroff = 0x03,
wifi_connect = 0x04, // nd -> ngui: reports poweroff progress
network_report = 0x05, poweroff_progress = 0x09,
get_network_report = 0x06,
// ngui -> nd: screen timeout, no user activity; no reply // ngui -> nd: screen timeout, no user activity; no reply
standby = 0x07, standby = 0x07,
// ngui -> nd: resume screen due to user touch; no reply // ngui -> nd: resume screen due to user touch; no reply
wakeup = 0x08, wakeup = 0x08,
// nd -> ngui: reports poweroff progress wifi_connect = 0x04,
poweroff_progress = 0x09, network_report = 0x05,
get_network_report = 0x06,
// nd -> ngui: bitcoin core daemon status report // nd -> ngui: bitcoin core daemon status report
onchain_report = 0x0a, onchain_report = 0x0a,
// nd -> ngui: lnd status and stats report // nd -> ngui: lnd status and stats report
@ -103,12 +103,12 @@ pub const Message = union(MessageTag) {
ping: void, ping: void,
pong: void, pong: void,
poweroff: void, poweroff: void,
poweroff_progress: PoweroffProgress,
standby: void, standby: void,
wakeup: void, wakeup: void,
wifi_connect: WifiConnect, wifi_connect: WifiConnect,
network_report: NetworkReport, network_report: NetworkReport,
get_network_report: GetNetworkReport, get_network_report: GetNetworkReport,
poweroff_progress: PoweroffProgress,
onchain_report: OnchainReport, onchain_report: OnchainReport,
lightning_report: LightningReport, lightning_report: LightningReport,
lightning_error: LightningError, lightning_error: LightningError,
@ -294,8 +294,8 @@ pub const ParsedMessage = struct {
/// callers must deallocate resources with ParsedMessage.deinit when done. /// callers must deallocate resources with ParsedMessage.deinit when done.
pub fn read(allocator: mem.Allocator, reader: anytype) !ParsedMessage { pub fn read(allocator: mem.Allocator, reader: anytype) !ParsedMessage {
// alternative is @intToEnum(reader.ReadIntLittle(u16)) but it may panic. // alternative is @intToEnum(reader.ReadIntLittle(u16)) but it may panic.
const tag = try reader.readEnum(MessageTag, .Little); const tag = try reader.readEnum(MessageTag, .little);
const len = try reader.readIntLittle(u64); const len = try reader.readInt(u64, .little);
if (len == 0) { if (len == 0) {
return switch (tag) { return switch (tag) {
.lightning_get_ctrlconn => .{ .value = .lightning_get_ctrlconn }, .lightning_get_ctrlconn => .{ .value = .lightning_get_ctrlconn },
@ -318,7 +318,7 @@ pub fn read(allocator: mem.Allocator, reader: anytype) !ParsedMessage {
.wakeup, .wakeup,
=> unreachable, // handled above => unreachable, // handled above
inline else => |t| { inline else => |t| {
var bytes = try allocator.alloc(u8, len); const bytes = try allocator.alloc(u8, len);
defer allocator.free(bytes); defer allocator.free(bytes);
try reader.readNoEof(bytes); try reader.readNoEof(bytes);
@ -370,8 +370,8 @@ pub fn write(allocator: mem.Allocator, writer: anytype, msg: Message) !void {
return Error.CommWriteTooLarge; return Error.CommWriteTooLarge;
} }
try writer.writeIntLittle(u16, @intFromEnum(msg)); try writer.writeInt(u16, @intFromEnum(msg), .little);
try writer.writeIntLittle(u64, data.items.len); try writer.writeInt(u64, data.items.len, .little);
try writer.writeAll(data.items); try writer.writeAll(data.items);
} }
@ -401,8 +401,8 @@ test "read" {
var buf = std.ArrayList(u8).init(t.allocator); var buf = std.ArrayList(u8).init(t.allocator);
defer buf.deinit(); defer buf.deinit();
try buf.writer().writeIntLittle(u16, @intFromEnum(msg)); try buf.writer().writeInt(u16, @intFromEnum(msg), .little);
try buf.writer().writeIntLittle(u64, data.items.len); try buf.writer().writeInt(u64, data.items.len, .little);
try buf.writer().writeAll(data.items); try buf.writer().writeAll(data.items);
var bs = std.io.fixedBufferStream(buf.items); var bs = std.io.fixedBufferStream(buf.items);
@ -424,8 +424,8 @@ test "write" {
const payload = "{\"ssid\":\"wlan\",\"password\":\"secret\"}"; const payload = "{\"ssid\":\"wlan\",\"password\":\"secret\"}";
var js = std.ArrayList(u8).init(t.allocator); var js = std.ArrayList(u8).init(t.allocator);
defer js.deinit(); defer js.deinit();
try js.writer().writeIntLittle(u16, @intFromEnum(msg)); try js.writer().writeInt(u16, @intFromEnum(msg), .little);
try js.writer().writeIntLittle(u64, payload.len); try js.writer().writeInt(u64, payload.len, .little);
try js.appendSlice(payload); try js.appendSlice(payload);
try t.expectEqualStrings(js.items, buf.items); try t.expectEqualStrings(js.items, buf.items);
@ -442,8 +442,8 @@ test "write enum" {
const payload = "\"edge\""; const payload = "\"edge\"";
var js = std.ArrayList(u8).init(t.allocator); var js = std.ArrayList(u8).init(t.allocator);
defer js.deinit(); defer js.deinit();
try js.writer().writeIntLittle(u16, @intFromEnum(msg)); try js.writer().writeInt(u16, @intFromEnum(msg), .little);
try js.writer().writeIntLittle(u64, payload.len); try js.writer().writeInt(u64, payload.len, .little);
try js.appendSlice(payload); try js.appendSlice(payload);
try t.expectEqualStrings(js.items, buf.items); try t.expectEqualStrings(js.items, buf.items);

@ -36,7 +36,7 @@ pub const Section = struct {
const vdup = try self.alloc.dupe(u8, value); const vdup = try self.alloc.dupe(u8, value);
errdefer self.alloc.free(vdup); errdefer self.alloc.free(vdup);
var res = try self.props.getOrPut(try self.alloc.dupe(u8, key)); const res = try self.props.getOrPut(try self.alloc.dupe(u8, key));
if (!res.found_existing) { if (!res.found_existing) {
res.value_ptr.* = .{ .str = vdup }; res.value_ptr.* = .{ .str = vdup };
return; return;
@ -180,7 +180,7 @@ pub fn appendDefaultSection(self: *LndConf) !*Section {
/// the section name ascii is converted to lower case. /// the section name ascii is converted to lower case.
pub fn appendSection(self: *LndConf, name: []const u8) !*Section { pub fn appendSection(self: *LndConf, name: []const u8) !*Section {
const alloc = self.arena.allocator(); const alloc = self.arena.allocator();
var low_name = try std.ascii.allocLowerString(alloc, name); const low_name = try std.ascii.allocLowerString(alloc, name);
try self.sections.append(.{ try self.sections.append(.{
.name = low_name, .name = low_name,
.props = std.StringArrayHashMap(PropValue).init(alloc), .props = std.StringArrayHashMap(PropValue).init(alloc),

@ -143,22 +143,29 @@ pub const Client = struct {
pub fn call(self: *Client, comptime apimethod: ApiMethod, args: MethodArgs(apimethod)) !Result(apimethod) { pub fn call(self: *Client, comptime apimethod: ApiMethod, args: MethodArgs(apimethod)) !Result(apimethod) {
const formatted = try self.formatreq(apimethod, args); const formatted = try self.formatreq(apimethod, args);
defer formatted.deinit(); defer formatted.deinit();
var headersbuf: [8 * 1024]u8 = undefined;
const reqinfo = formatted.value; const reqinfo = formatted.value;
const opt = std.http.Client.Options{ .handle_redirects = false }; // no redirects in REST API const opt = std.http.Client.RequestOptions{
var req = try self.httpClient.request(reqinfo.httpmethod, reqinfo.url, reqinfo.headers, opt); .redirect_behavior = .not_allowed, // no redirects in REST API
.headers = reqinfo.stdheaders,
.privileged_headers = reqinfo.xheaders,
.server_header_buffer = &headersbuf,
};
var req = try self.httpClient.open(reqinfo.httpmethod, reqinfo.url, opt);
defer req.deinit(); defer req.deinit();
if (reqinfo.payload) |p| { if (reqinfo.payload) |p| {
req.transfer_encoding = .{ .content_length = p.len }; req.transfer_encoding = .{ .content_length = p.len };
} }
try req.start(); try req.send();
if (reqinfo.payload) |p| { if (reqinfo.payload) |p| {
req.writer().writeAll(p) catch return Error.LndPayloadWriteFail; req.writeAll(p) catch return Error.LndPayloadWriteFail;
try req.finish(); try req.finish();
} }
try req.wait(); try req.wait();
if (req.response.status.class() != .success) { if (req.response.status.class() != .success) {
// a structured error reporting in lnd is in a less than desirable state. // a structured error reporting in lnd is unclear:
// https://github.com/lightningnetwork/lnd/issues/5586 // https://github.com/lightningnetwork/lnd/issues/5586
// TODO: return a more detailed error when the upstream improves. // TODO: return a more detailed error when the upstream improves.
return Error.LndHttpBadStatusCode; return Error.LndHttpBadStatusCode;
@ -181,8 +188,9 @@ pub const Client = struct {
const HttpReqInfo = struct { const HttpReqInfo = struct {
httpmethod: std.http.Method, httpmethod: std.http.Method,
url: std.Uri, url: std.Uri,
headers: std.http.Headers, stdheaders: std.http.Client.Request.Headers = .{}, // overridable standard headers
payload: ?[]const u8, xheaders: []const std.http.Header = &.{}, // any extra headers
payload: ?[]const u8 = null,
}; };
fn formatreq(self: Client, comptime apimethod: ApiMethod, args: MethodArgs(apimethod)) !types.Deinitable(HttpReqInfo) { fn formatreq(self: Client, comptime apimethod: ApiMethod, args: MethodArgs(apimethod)) !types.Deinitable(HttpReqInfo) {
@ -194,12 +202,10 @@ pub const Client = struct {
.genseed, .walletstatus => |m| .{ .genseed, .walletstatus => |m| .{
.httpmethod = .GET, .httpmethod = .GET,
.url = try std.Uri.parse(try std.fmt.allocPrint(arena, "{s}/{s}", .{ self.apibase, m.apipath() })), .url = try std.Uri.parse(try std.fmt.allocPrint(arena, "{s}/{s}", .{ self.apibase, m.apipath() })),
.headers = std.http.Headers{ .allocator = arena },
.payload = null,
}, },
.initwallet => |m| blk: { .initwallet => |m| blk: {
const payload = p: { const payload = p: {
var params: struct { const params: struct {
wallet_password: []const u8, // base64 wallet_password: []const u8, // base64
cipher_seed_mnemonic: []const []const u8, cipher_seed_mnemonic: []const []const u8,
aezeed_passphrase: ?[]const u8 = null, // base64 aezeed_passphrase: ?[]const u8 = null, // base64
@ -215,13 +221,12 @@ pub const Client = struct {
break :blk .{ break :blk .{
.httpmethod = .POST, .httpmethod = .POST,
.url = try std.Uri.parse(try std.fmt.allocPrint(arena, "{s}/{s}", .{ self.apibase, m.apipath() })), .url = try std.Uri.parse(try std.fmt.allocPrint(arena, "{s}/{s}", .{ self.apibase, m.apipath() })),
.headers = std.http.Headers{ .allocator = arena },
.payload = payload, .payload = payload,
}; };
}, },
.unlockwallet => |m| blk: { .unlockwallet => |m| blk: {
const payload = p: { const payload = p: {
var params: struct { const params: struct {
wallet_password: []const u8, // base64 wallet_password: []const u8, // base64
} = .{ } = .{
.wallet_password = try base64EncodeAlloc(arena, args.unlock_password), .wallet_password = try base64EncodeAlloc(arena, args.unlock_password),
@ -233,20 +238,19 @@ pub const Client = struct {
break :blk .{ break :blk .{
.httpmethod = .POST, .httpmethod = .POST,
.url = try std.Uri.parse(try std.fmt.allocPrint(arena, "{s}/{s}", .{ self.apibase, m.apipath() })), .url = try std.Uri.parse(try std.fmt.allocPrint(arena, "{s}/{s}", .{ self.apibase, m.apipath() })),
.headers = std.http.Headers{ .allocator = arena },
.payload = payload, .payload = payload,
}; };
}, },
.feereport, .getinfo, .getnetworkinfo, .pendingchannels, .walletbalance => |m| .{ .feereport, .getinfo, .getnetworkinfo, .pendingchannels, .walletbalance => |m| .{
.httpmethod = .GET, .httpmethod = .GET,
.url = try std.Uri.parse(try std.fmt.allocPrint(arena, "{s}/{s}", .{ self.apibase, m.apipath() })), .url = try std.Uri.parse(try std.fmt.allocPrint(arena, "{s}/{s}", .{ self.apibase, m.apipath() })),
.headers = blk: { .xheaders = blk: {
if (self.macaroon.readonly == null) { if (self.macaroon.readonly == null) {
return Error.LndHttpMissingMacaroon; return Error.LndHttpMissingMacaroon;
} }
var h = std.http.Headers{ .allocator = arena }; var h = std.ArrayList(std.http.Header).init(arena);
try h.append(authHeaderName, self.macaroon.readonly.?); try h.append(.{ .name = authHeaderName, .value = self.macaroon.readonly.? });
break :blk h; break :blk try h.toOwnedSlice();
}, },
.payload = null, .payload = null,
}, },
@ -270,13 +274,13 @@ pub const Client = struct {
} }
break :blk try std.Uri.parse(buf.items); // uri point to the original buf break :blk try std.Uri.parse(buf.items); // uri point to the original buf
}, },
.headers = blk: { .xheaders = blk: {
if (self.macaroon.readonly == null) { if (self.macaroon.readonly == null) {
return Error.LndHttpMissingMacaroon; return Error.LndHttpMissingMacaroon;
} }
var h = std.http.Headers{ .allocator = arena }; var h = std.ArrayList(std.http.Header).init(arena);
try h.append(authHeaderName, self.macaroon.readonly.?); try h.append(.{ .name = authHeaderName, .value = self.macaroon.readonly.? });
break :blk h; break :blk try h.toOwnedSlice();
}, },
.payload = null, .payload = null,
}, },
@ -299,7 +303,7 @@ pub const Client = struct {
} }
fn base64EncodeAlloc(gpa: std.mem.Allocator, v: []const u8) ![]const u8 { fn base64EncodeAlloc(gpa: std.mem.Allocator, v: []const u8) ![]const u8 {
var buf = try gpa.alloc(u8, base64enc.calcSize(v.len)); const buf = try gpa.alloc(u8, base64enc.calcSize(v.len));
return base64enc.encode(buf, v); // always returns a slice of buf.len return base64enc.encode(buf, v); // always returns a slice of buf.len
} }
}; };

@ -1,7 +1,6 @@
const buildopts = @import("build_options"); const buildopts = @import("build_options");
const std = @import("std"); const std = @import("std");
const os = std.os; const posix = std.posix;
const sys = os.system;
const time = std.time; const time = std.time;
const Address = std.net.Address; const Address = std.net.Address;
@ -137,7 +136,7 @@ fn sighandler(sig: c_int) callconv(.C) void {
return; return;
} }
switch (sig) { switch (sig) {
os.SIG.INT, os.SIG.TERM => sigquit.set(), posix.SIG.INT, posix.SIG.TERM => sigquit.set(),
else => {}, else => {},
} }
} }
@ -220,13 +219,13 @@ pub fn main() !void {
try nd.start(); try nd.start();
// graceful shutdown; see sigaction(2) // graceful shutdown; see sigaction(2)
const sa = os.Sigaction{ const sa = posix.Sigaction{
.handler = .{ .handler = sighandler }, .handler = .{ .handler = sighandler },
.mask = os.empty_sigset, .mask = posix.empty_sigset,
.flags = 0, .flags = 0,
}; };
try os.sigaction(os.SIG.INT, &sa, null); try posix.sigaction(posix.SIG.INT, &sa, null);
try os.sigaction(os.SIG.TERM, &sa, null); try posix.sigaction(posix.SIG.TERM, &sa, null);
sigquit.wait(); sigquit.wait();
logger.info("sigquit: terminating ...", .{}); logger.info("sigquit: terminating ...", .{});

@ -145,7 +145,7 @@ fn inferStaticData(allocator: std.mem.Allocator) !StaticData {
} }
fn inferLndTorHostname(allocator: std.mem.Allocator) ![]const u8 { fn inferLndTorHostname(allocator: std.mem.Allocator) ![]const u8 {
var raw = try std.fs.cwd().readFileAlloc(allocator, TOR_DATA_DIR ++ "/lnd/hostname", 1024); const raw = try std.fs.cwd().readFileAlloc(allocator, TOR_DATA_DIR ++ "/lnd/hostname", 1024);
const hostname = std.mem.trim(u8, raw, &std.ascii.whitespace); const hostname = std.mem.trim(u8, raw, &std.ascii.whitespace);
logger.info("inferred lnd tor hostname: [{s}]", .{hostname}); logger.info("inferred lnd tor hostname: [{s}]", .{hostname});
return hostname; return hostname;
@ -156,7 +156,7 @@ fn inferBitcoindRpcPass(allocator: std.mem.Allocator) ![]const u8 {
// the password was placed on a separate comment line, preceding another comment // the password was placed on a separate comment line, preceding another comment
// line containing "rpcauth.py". // line containing "rpcauth.py".
// TODO: get rid of the hack; do something more robust // TODO: get rid of the hack; do something more robust
var conf = try std.fs.cwd().readFileAlloc(allocator, BITCOIND_CONFIG_PATH, 1024 * 1024); const conf = try std.fs.cwd().readFileAlloc(allocator, BITCOIND_CONFIG_PATH, 1024 * 1024);
var it = std.mem.tokenizeScalar(u8, conf, '\n'); var it = std.mem.tokenizeScalar(u8, conf, '\n');
var next_is_pass = false; var next_is_pass = false;
while (it.next()) |line| { while (it.next()) |line| {
@ -346,7 +346,7 @@ fn genSysupdatesCronScript(self: Config) !void {
/// ///
/// the caller must serialize this function calls. /// the caller must serialize this function calls.
fn runSysupdates(allocator: std.mem.Allocator, scriptpath: []const u8) !void { fn runSysupdates(allocator: std.mem.Allocator, scriptpath: []const u8) !void {
const res = try std.ChildProcess.exec(.{ .allocator = allocator, .argv = &.{scriptpath} }); const res = try std.ChildProcess.run(.{ .allocator = allocator, .argv = &.{scriptpath} });
defer { defer {
allocator.free(res.stdout); allocator.free(res.stdout);
allocator.free(res.stderr); allocator.free(res.stderr);
@ -383,7 +383,7 @@ pub fn lndConnectWaitMacaroonFile(self: Config, allocator: std.mem.Allocator, ty
defer allocator.free(macaroon); defer allocator.free(macaroon);
const base64enc = std.base64.url_safe_no_pad.Encoder; const base64enc = std.base64.url_safe_no_pad.Encoder;
var buf = try allocator.alloc(u8, base64enc.calcSize(macaroon.len)); const buf = try allocator.alloc(u8, base64enc.calcSize(macaroon.len));
defer allocator.free(buf); defer allocator.free(buf);
const macaroon_b64 = base64enc.encode(buf, macaroon); const macaroon_b64 = base64enc.encode(buf, macaroon);
const port: u16 = switch (typ) { const port: u16 = switch (typ) {
@ -599,7 +599,7 @@ test "ndconfig: switch sysupdates with .run=true" {
const tt = @import("../test.zig"); const tt = @import("../test.zig");
// no arena deinit here: expecting Config to auto-deinit. // no arena deinit here: expecting Config to auto-deinit.
var conf_arena = try std.testing.allocator.create(std.heap.ArenaAllocator); const conf_arena = try std.testing.allocator.create(std.heap.ArenaAllocator);
conf_arena.* = std.heap.ArenaAllocator.init(std.testing.allocator); conf_arena.* = std.heap.ArenaAllocator.init(std.testing.allocator);
var tmp = try tt.TempDir.create(); var tmp = try tt.TempDir.create();
defer tmp.cleanup(); defer tmp.cleanup();
@ -644,7 +644,7 @@ test "ndconfig: genLndConfig" {
const tt = @import("../test.zig"); const tt = @import("../test.zig");
// Config auto-deinits the arena. // Config auto-deinits the arena.
var conf_arena = try std.testing.allocator.create(std.heap.ArenaAllocator); const conf_arena = try std.testing.allocator.create(std.heap.ArenaAllocator);
conf_arena.* = std.heap.ArenaAllocator.init(std.testing.allocator); conf_arena.* = std.heap.ArenaAllocator.init(std.testing.allocator);
var tmp = try tt.TempDir.create(); var tmp = try tt.TempDir.create();
defer tmp.cleanup(); defer tmp.cleanup();
@ -695,7 +695,7 @@ test "ndconfig: mutate LndConf" {
const tt = @import("../test.zig"); const tt = @import("../test.zig");
// Config auto-deinits the arena. // Config auto-deinits the arena.
var conf_arena = try std.testing.allocator.create(std.heap.ArenaAllocator); const conf_arena = try std.testing.allocator.create(std.heap.ArenaAllocator);
conf_arena.* = std.heap.ArenaAllocator.init(t.allocator); conf_arena.* = std.heap.ArenaAllocator.init(t.allocator);
var tmp = try tt.TempDir.create(); var tmp = try tt.TempDir.create();
defer tmp.cleanup(); defer tmp.cleanup();
@ -737,7 +737,7 @@ test "ndconfig: screen lock" {
const tt = @import("../test.zig"); const tt = @import("../test.zig");
// Config auto-deinits the arena. // Config auto-deinits the arena.
var conf_arena = try std.testing.allocator.create(std.heap.ArenaAllocator); const conf_arena = try std.testing.allocator.create(std.heap.ArenaAllocator);
conf_arena.* = std.heap.ArenaAllocator.init(t.allocator); conf_arena.* = std.heap.ArenaAllocator.init(t.allocator);
var tmp = try tt.TempDir.create(); var tmp = try tt.TempDir.create();
defer tmp.cleanup(); defer tmp.cleanup();

@ -548,7 +548,9 @@ fn unlockScreen(self: *Daemon, pincode: []const u8) !void {
defer self.allocator.free(pindup); defer self.allocator.free(pindup);
// TODO: slow down // TODO: slow down
self.conf.verifySlockPin(pindup) catch |err| { self.conf.verifySlockPin(pindup) catch |err| {
logger.err("verifySlockPin: {!}", .{err}); if (!builtin.is_test) { // logging err makes some tests fail
logger.err("verifySlockPin: {!}", .{err});
}
const errmsg: comm.Message = .{ .screen_unlock_result = .{ const errmsg: comm.Message = .{ .screen_unlock_result = .{
.ok = false, .ok = false,
.err = if (err == error.IncorrectSlockPin) "incorrect pin code" else "unlock failed", .err = if (err == error.IncorrectSlockPin) "incorrect pin code" else "unlock failed",
@ -562,7 +564,7 @@ fn unlockScreen(self: *Daemon, pincode: []const u8) !void {
/// sends poweroff progress to uiwriter in comm.Message.PoweroffProgress format. /// sends poweroff progress to uiwriter in comm.Message.PoweroffProgress format.
fn sendPoweroffReport(self: *Daemon) !void { fn sendPoweroffReport(self: *Daemon) !void {
var svstat = try self.allocator.alloc(comm.Message.PoweroffProgress.Service, self.services.list.len); const svstat = try self.allocator.alloc(comm.Message.PoweroffProgress.Service, self.services.list.len);
defer self.allocator.free(svstat); defer self.allocator.free(svstat);
for (self.services.list, svstat) |*sv, *stat| { for (self.services.list, svstat) |*sv, *stat| {
stat.* = .{ stat.* = .{
@ -962,7 +964,7 @@ fn processLndReportError(self: *Daemon, err: anyerror) !void {
error.FileNotFound, // tls cert file missing, not re-generated by lnd yet error.FileNotFound, // tls cert file missing, not re-generated by lnd yet
=> return comm.write(self.allocator, self.uiwriter, msg_starting), => return comm.write(self.allocator, self.uiwriter, msg_starting),
// old tls cert, refused by our http client // old tls cert, refused by our http client
std.http.Client.ConnectUnproxiedError.TlsInitializationFailed => { std.http.Client.ConnectTcpError.TlsInitializationFailed => {
try self.resetLndTlsUnguarded(); try self.resetLndTlsUnguarded();
return error.LndReportRetryLater; return error.LndReportRetryLater;
}, },
@ -1006,7 +1008,7 @@ fn sendLightningPairingConn(self: *Daemon) !void {
defer self.allocator.free(tor_rpc); defer self.allocator.free(tor_rpc);
const tor_http = try self.conf.lndConnectWaitMacaroonFile(self.allocator, .tor_http); const tor_http = try self.conf.lndConnectWaitMacaroonFile(self.allocator, .tor_http);
defer self.allocator.free(tor_http); defer self.allocator.free(tor_http);
var conn: comm.Message.LightningCtrlConn = &.{ const conn: comm.Message.LightningCtrlConn = &.{
.{ .url = tor_rpc, .typ = .lnd_rpc, .perm = .admin }, .{ .url = tor_rpc, .typ = .lnd_rpc, .perm = .admin },
.{ .url = tor_http, .typ = .lnd_http, .perm = .admin }, .{ .url = tor_http, .typ = .lnd_http, .perm = .admin },
}; };
@ -1257,7 +1259,7 @@ fn setNodenameInternal(self: *Daemon, newname: []const u8) !void {
/// replaces whitespace with space literal and ignores ascii control chars. /// replaces whitespace with space literal and ignores ascii control chars.
/// caller owns returned value. /// caller owns returned value.
fn allocSanitizeNodename(allocator: std.mem.Allocator, name: []const u8) ![]const u8 { fn allocSanitizeNodename(allocator: std.mem.Allocator, name: []const u8) ![]const u8 {
if (name.len == 0 or try std.unicode.utf8CountCodepoints(name) > std.os.HOST_NAME_MAX) { if (name.len == 0 or try std.unicode.utf8CountCodepoints(name) > std.posix.HOST_NAME_MAX) {
return error.InvalidNodenameLength; return error.InvalidNodenameLength;
} }
var sanitized = try std.ArrayList(u8).initCapacity(allocator, name.len); var sanitized = try std.ArrayList(u8).initCapacity(allocator, name.len);
@ -1448,7 +1450,7 @@ test "daemon: screen unlock" {
fn dummyTestConfig() !Config { fn dummyTestConfig() !Config {
const talloc = std.testing.allocator; const talloc = std.testing.allocator;
var arena = try talloc.create(std.heap.ArenaAllocator); const arena = try talloc.create(std.heap.ArenaAllocator);
arena.* = std.heap.ArenaAllocator.init(talloc); arena.* = std.heap.ArenaAllocator.init(talloc);
return Config{ return Config{
.arena = arena, .arena = arena,

@ -86,7 +86,7 @@ pub fn sendReport(gpa: mem.Allocator, wpa_ctrl: *types.WpaControl, w: anytype) !
}; };
// fetch available wifi networks from scan results using WPA ctrl // fetch available wifi networks from scan results using WPA ctrl
var wifi_networks: ?types.StringList = if (queryWifiScanResults(arena, wpa_ctrl)) |v| v else |err| blk: { const wifi_networks: ?types.StringList = if (queryWifiScanResults(arena, wpa_ctrl)) |v| v else |err| blk: {
logger.err("queryWifiScanResults: {any}", .{err}); logger.err("queryWifiScanResults: {any}", .{err});
break :blk null; break :blk null;
}; };

@ -1,6 +1,6 @@
const buildopts = @import("build_options"); const buildopts = @import("build_options");
const std = @import("std"); const std = @import("std");
const os = std.os; const posix = std.posix;
const time = std.time; const time = std.time;
const comm = @import("comm.zig"); const comm = @import("comm.zig");
@ -291,7 +291,7 @@ fn commThreadLoopCycle() !void {
fn uiThreadLoop() void { fn uiThreadLoop() void {
while (true) { while (true) {
ui_mutex.lock(); ui_mutex.lock();
var till_next_ms = lvgl.loopCycle(); // UI loop const till_next_ms = lvgl.loopCycle(); // UI loop
const do_state = state; const do_state = state;
ui_mutex.unlock(); ui_mutex.unlock();
@ -391,7 +391,7 @@ fn sighandler(sig: c_int) callconv(.C) void {
return; return;
} }
switch (sig) { switch (sig) {
os.SIG.INT, os.SIG.TERM => sigquit.set(), posix.SIG.INT, posix.SIG.TERM => sigquit.set(),
else => {}, else => {},
} }
} }
@ -440,13 +440,13 @@ pub fn main() anyerror!void {
} }
// set up a sigterm handler for clean exit. // set up a sigterm handler for clean exit.
const sa = os.Sigaction{ const sa = posix.Sigaction{
.handler = .{ .handler = sighandler }, .handler = .{ .handler = sighandler },
.mask = os.empty_sigset, .mask = posix.empty_sigset,
.flags = 0, .flags = 0,
}; };
try os.sigaction(os.SIG.INT, &sa, null); try posix.sigaction(posix.SIG.INT, &sa, null);
try os.sigaction(os.SIG.TERM, &sa, null); try posix.sigaction(posix.SIG.TERM, &sa, null);
sigquit.wait(); sigquit.wait();
logger.info("sigquit: terminating ...", .{}); logger.info("sigquit: terminating ...", .{});

@ -5,8 +5,8 @@ const types = @import("../types.zig");
/// caller owns memory; must dealloc using `allocator`. /// caller owns memory; must dealloc using `allocator`.
pub fn hostname(allocator: std.mem.Allocator) ![]const u8 { pub fn hostname(allocator: std.mem.Allocator) ![]const u8 {
var buf: [std.os.HOST_NAME_MAX]u8 = undefined; var buf: [std.posix.HOST_NAME_MAX]u8 = undefined;
const name = try std.os.gethostname(&buf); const name = try std.posix.gethostname(&buf);
return allocator.dupe(u8, name); return allocator.dupe(u8, name);
} }
@ -40,8 +40,8 @@ pub fn setHostname(allocator: std.mem.Allocator, name: []const u8) !void {
const newname = sanitized.items; const newname = sanitized.items;
// need not continue if current name matches the new one. // need not continue if current name matches the new one.
var buf: [std.os.HOST_NAME_MAX]u8 = undefined; var buf: [std.posix.HOST_NAME_MAX]u8 = undefined;
const currname = try std.os.gethostname(&buf); const currname = try std.posix.gethostname(&buf);
if (std.mem.eql(u8, currname, newname)) { if (std.mem.eql(u8, currname, newname)) {
return; return;
} }

@ -37,7 +37,7 @@ pub fn initGlobal() void {
fn initGlobalFn() void { fn initGlobalFn() void {
global_gpa_state = std.heap.GeneralPurposeAllocator(.{}){}; global_gpa_state = std.heap.GeneralPurposeAllocator(.{}){};
global_gpa = global_gpa_state.allocator(); global_gpa = global_gpa_state.allocator();
var pipe = types.IoPipe.create() catch |err| { const pipe = types.IoPipe.create() catch |err| {
std.debug.panic("IoPipe.create: {any}", .{err}); std.debug.panic("IoPipe.create: {any}", .{err});
}; };
comm.initPipe(global_gpa, pipe); comm.initPipe(global_gpa, pipe);
@ -118,7 +118,7 @@ pub const TestChildProcess = struct {
argv: []const []const u8, argv: []const []const u8,
pub fn init(argv: []const []const u8, allocator: std.mem.Allocator) TestChildProcess { pub fn init(argv: []const []const u8, allocator: std.mem.Allocator) TestChildProcess {
var adup = allocator.alloc([]u8, argv.len) catch unreachable; const adup = allocator.alloc([]u8, argv.len) catch unreachable;
for (argv, adup) |v, *dup| { for (argv, adup) |v, *dup| {
dup.* = allocator.dupe(u8, v) catch unreachable; dup.* = allocator.dupe(u8, v) catch unreachable;
} }
@ -242,7 +242,7 @@ pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void {
.Slice => { .Slice => {
switch (@typeInfo(p.child)) { switch (@typeInfo(p.child)) {
.Pointer, .Struct, .Optional, .Union => { .Pointer, .Struct, .Optional, .Union => {
var err: ?anyerror = blk: { const err: ?anyerror = blk: {
if (expected.len != actual.len) { if (expected.len != actual.len) {
std.debug.print("expected.len = {d}, actual.len = {d}\n", .{ expected.len, actual.len }); std.debug.print("expected.len = {d}, actual.len = {d}\n", .{ expected.len, actual.len });
break :blk error.ExpectDeepEqual; break :blk error.ExpectDeepEqual;
@ -331,6 +331,7 @@ test {
_ = @import("ngui.zig"); _ = @import("ngui.zig");
_ = @import("lightning.zig"); _ = @import("lightning.zig");
_ = @import("sys.zig"); _ = @import("sys.zig");
_ = @import("xfmt.zig");
std.testing.refAllDecls(@This()); std.testing.refAllDecls(@This());
} }

@ -1,6 +1,6 @@
const std = @import("std"); const std = @import("std");
const time = std.time; const time = std.time;
const os = std.os; const posix = std.posix;
const comm = @import("comm"); const comm = @import("comm");
const types = @import("../types.zig"); const types = @import("../types.zig");
@ -12,9 +12,9 @@ var ngui_proc: std.ChildProcess = undefined;
var sigquit: std.Thread.ResetEvent = .{}; var sigquit: std.Thread.ResetEvent = .{};
fn sighandler(sig: c_int) callconv(.C) void { fn sighandler(sig: c_int) callconv(.C) void {
logger.info("received signal {} (TERM={} INT={})", .{ sig, os.SIG.TERM, os.SIG.INT }); logger.info("received signal {} (TERM={} INT={})", .{ sig, posix.SIG.TERM, posix.SIG.INT });
switch (sig) { switch (sig) {
os.SIG.INT, os.SIG.TERM => sigquit.set(), posix.SIG.INT, posix.SIG.TERM => sigquit.set(),
else => {}, else => {},
} }
} }
@ -79,7 +79,7 @@ fn parseArgs(gpa: std.mem.Allocator) !Flags {
/// global vars for comm read/write threads /// global vars for comm read/write threads
var state: struct { var state: struct {
mu: std.Thread.Mutex = .{}, mu: std.Thread.Mutex = .{},
nodename: types.BufTrimString(std.os.HOST_NAME_MAX) = .{}, nodename: types.BufTrimString(std.posix.HOST_NAME_MAX) = .{},
slock_pincode: ?[]const u8 = null, // disabled when null slock_pincode: ?[]const u8 = null, // disabled when null
settings_sent: bool = false, settings_sent: bool = false,
@ -114,7 +114,7 @@ fn commReadThread(gpa: std.mem.Allocator, r: anytype, w: anytype) void {
}, },
.poweroff => { .poweroff => {
logger.info("sending poweroff status1", .{}); logger.info("sending poweroff status1", .{});
var s1: comm.Message.PoweroffProgress = .{ .services = &.{ const s1: comm.Message.PoweroffProgress = .{ .services = &.{
.{ .name = "lnd", .stopped = false, .err = null }, .{ .name = "lnd", .stopped = false, .err = null },
.{ .name = "bitcoind", .stopped = false, .err = null }, .{ .name = "bitcoind", .stopped = false, .err = null },
} }; } };
@ -122,7 +122,7 @@ fn commReadThread(gpa: std.mem.Allocator, r: anytype, w: anytype) void {
time.sleep(2 * time.ns_per_s); time.sleep(2 * time.ns_per_s);
logger.info("sending poweroff status2", .{}); logger.info("sending poweroff status2", .{});
var s2: comm.Message.PoweroffProgress = .{ .services = &.{ const s2: comm.Message.PoweroffProgress = .{ .services = &.{
.{ .name = "lnd", .stopped = true, .err = null }, .{ .name = "lnd", .stopped = true, .err = null },
.{ .name = "bitcoind", .stopped = false, .err = null }, .{ .name = "bitcoind", .stopped = false, .err = null },
} }; } };
@ -130,7 +130,7 @@ fn commReadThread(gpa: std.mem.Allocator, r: anytype, w: anytype) void {
time.sleep(3 * time.ns_per_s); time.sleep(3 * time.ns_per_s);
logger.info("sending poweroff status3", .{}); logger.info("sending poweroff status3", .{});
var s3: comm.Message.PoweroffProgress = .{ .services = &.{ const s3: comm.Message.PoweroffProgress = .{ .services = &.{
.{ .name = "lnd", .stopped = true, .err = null }, .{ .name = "lnd", .stopped = true, .err = null },
.{ .name = "bitcoind", .stopped = true, .err = null }, .{ .name = "bitcoind", .stopped = true, .err = null },
} }; } };
@ -149,7 +149,7 @@ fn commReadThread(gpa: std.mem.Allocator, r: anytype, w: anytype) void {
time.sleep(3 * time.ns_per_s); time.sleep(3 * time.ns_per_s);
}, },
.lightning_get_ctrlconn => { .lightning_get_ctrlconn => {
var conn: comm.Message.LightningCtrlConn = &.{ const conn: comm.Message.LightningCtrlConn = &.{
.{ .url = "lndconnect://adfkjhadwaepoijsadflkjtrpoijawokjafulkjsadfkjhgjfdskjszd.onion:10009?macaroon=Adasjsadkfljhfjhasdpiuhfiuhawfffoihgpoiadsfjharpoiuhfdsgpoihafdsgpoiheafoiuhasdfhisdufhiuhfewiuhfiuhrfl6prrx", .typ = .lnd_rpc, .perm = .admin }, .{ .url = "lndconnect://adfkjhadwaepoijsadflkjtrpoijawokjafulkjsadfkjhgjfdskjszd.onion:10009?macaroon=Adasjsadkfljhfjhasdpiuhfiuhawfffoihgpoiadsfjharpoiuhfdsgpoihafdsgpoiheafoiuhasdfhisdufhiuhfewiuhfiuhrfl6prrx", .typ = .lnd_rpc, .perm = .admin },
.{ .url = "lndconnect://adfkjhadwaepoijsadflkjtrpoijawokjafulkjsadfkjhgjfdskjszd.onion:10010?macaroon=Adasjsadkfljhfjhasdpiuhfiuhawfffoihgpoiadsfjharpoiuhfdsgpoihafdsgpoiheafoiuhasdfhisdufhiuhfewiuhfiuhrfl6prrx", .typ = .lnd_http, .perm = .admin }, .{ .url = "lndconnect://adfkjhadwaepoijsadflkjtrpoijawokjafulkjsadfkjhgjfdskjszd.onion:10010?macaroon=Adasjsadkfljhfjhasdpiuhfiuhawfffoihgpoiadsfjharpoiuhfdsgpoihafdsgpoiheafoiuhasdfhisdufhiuhfewiuhfiuhrfl6prrx", .typ = .lnd_http, .perm = .admin },
}; };
@ -374,13 +374,13 @@ pub fn main() !void {
const th2 = try std.Thread.spawn(.{}, commWriteThread, .{ gpa, uiwriter }); const th2 = try std.Thread.spawn(.{}, commWriteThread, .{ gpa, uiwriter });
th2.detach(); th2.detach();
const sa = os.Sigaction{ const sa = posix.Sigaction{
.handler = .{ .handler = sighandler }, .handler = .{ .handler = sighandler },
.mask = os.empty_sigset, .mask = posix.empty_sigset,
.flags = 0, .flags = 0,
}; };
try os.sigaction(os.SIG.INT, &sa, null); try posix.sigaction(posix.SIG.INT, &sa, null);
try os.sigaction(os.SIG.TERM, &sa, null); try posix.sigaction(posix.SIG.TERM, &sa, null);
sigquit.wait(); sigquit.wait();
logger.info("killing ngui", .{}); logger.info("killing ngui", .{});

@ -40,8 +40,8 @@ pub const IoPipe = struct {
w: std.fs.File, w: std.fs.File,
/// a pipe must be close'ed when done. /// a pipe must be close'ed when done.
pub fn create() std.os.PipeError!IoPipe { pub fn create() std.posix.PipeError!IoPipe {
const fds = try std.os.pipe(); const fds = try std.posix.pipe();
return .{ return .{
.r = std.fs.File{ .handle = fds[0] }, .r = std.fs.File{ .handle = fds[0] },
.w = std.fs.File{ .handle = fds[1] }, .w = std.fs.File{ .handle = fds[1] },
@ -138,7 +138,7 @@ pub fn Deinitable(comptime T: type) type {
const Self = @This(); const Self = @This();
pub fn init(allocator: std.mem.Allocator) !Self { pub fn init(allocator: std.mem.Allocator) !Self {
var res = Self{ const res = Self{
.arena = try allocator.create(std.heap.ArenaAllocator), .arena = try allocator.create(std.heap.ArenaAllocator),
.value = undefined, .value = undefined,
}; };

@ -46,9 +46,9 @@ pub usingnamespace switch (buildopts.driver) {
} }
}, },
.fbev => struct { .fbev => struct {
extern "c" fn nm_open_evdev_nonblock() std.os.fd_t; extern "c" fn nm_open_evdev_nonblock() std.posix.fd_t;
extern "c" fn nm_close_evdev(fd: std.os.fd_t) void; extern "c" fn nm_close_evdev(fd: std.posix.fd_t) void;
extern "c" fn nm_consume_input_events(fd: std.os.fd_t) bool; extern "c" fn nm_consume_input_events(fd: std.posix.fd_t) bool;
pub fn InputWatcher() !EvdevWatcher { pub fn InputWatcher() !EvdevWatcher {
const fd = nm_open_evdev_nonblock(); const fd = nm_open_evdev_nonblock();
@ -59,7 +59,7 @@ pub usingnamespace switch (buildopts.driver) {
} }
pub const EvdevWatcher = struct { pub const EvdevWatcher = struct {
evdev_fd: std.os.fd_t, evdev_fd: std.posix.fd_t,
pub fn consume(self: @This()) bool { pub fn consume(self: @This()) bool {
return nm_consume_input_events(self.evdev_fd); return nm_consume_input_events(self.evdev_fd);

@ -99,7 +99,7 @@ var tab: struct {
} = null, } = null,
fn initSetup(self: *@This(), topwin: lvgl.Window) !void { fn initSetup(self: *@This(), topwin: lvgl.Window) !void {
var arena = try self.allocator.create(std.heap.ArenaAllocator); const arena = try self.allocator.create(std.heap.ArenaAllocator);
arena.* = std.heap.ArenaAllocator.init(tab.allocator); arena.* = std.heap.ArenaAllocator.init(tab.allocator);
self.seed_setup = .{ .arena = arena, .topwin = topwin }; self.seed_setup = .{ .arena = arena, .topwin = topwin };
} }

@ -719,7 +719,7 @@ pub const Label = struct {
/// the text value is copied into a heap-allocated alloc. /// the text value is copied into a heap-allocated alloc.
pub fn new(parent: anytype, text: ?[*:0]const u8, opt: Opt) !Label { pub fn new(parent: anytype, text: ?[*:0]const u8, opt: Opt) !Label {
var lv_label = lv_label_create(parent.lvobj) orelse return error.OutOfMemory; const lv_label = lv_label_create(parent.lvobj) orelse return error.OutOfMemory;
if (text) |s| { if (text) |s| {
lv_label_set_text(lv_label, s); lv_label_set_text(lv_label, s);
} }
@ -757,7 +757,7 @@ pub const Label = struct {
/// formats a new label text and passes it on to `setText`. /// formats a new label text and passes it on to `setText`.
/// the buffer can be dropped once the function returns. /// the buffer can be dropped once the function returns.
pub fn setTextFmt(self: Label, buf: []u8, comptime format: []const u8, args: anytype) !void { pub fn setTextFmt(self: Label, buf: []u8, comptime format: []const u8, args: anytype) !void {
var s = try std.fmt.bufPrintZ(buf, format, args); const s = try std.fmt.bufPrintZ(buf, format, args);
self.setText(s); self.setText(s);
} }

@ -75,7 +75,7 @@ var tab: struct {
var state: struct { var state: struct {
// node name // node name
nodename_change_inprogress: bool = false, nodename_change_inprogress: bool = false,
curr_nodename: types.BufTrimString(std.os.HOST_NAME_MAX) = .{}, curr_nodename: types.BufTrimString(std.posix.HOST_NAME_MAX) = .{},
// screenlock // screenlock
slock_pin_input1: ?[]const u8 = null, // verified against a second time input slock_pin_input1: ?[]const u8 = null, // verified against a second time input
// sysupdates channel // sysupdates channel
@ -111,7 +111,7 @@ pub fn initNodenamePanel(cont: lvgl.Container) !lvgl.Card {
right.setPad(0, .column, .{}); right.setPad(0, .column, .{});
tab.nodename.textarea = try lvgl.TextArea.new(right, .{ tab.nodename.textarea = try lvgl.TextArea.new(right, .{
.maxlen = std.os.HOST_NAME_MAX, .maxlen = std.posix.HOST_NAME_MAX,
.oneline = true, .oneline = true,
}); });
tab.nodename.textarea.setWidth(lvgl.sizePercent(100)); tab.nodename.textarea.setWidth(lvgl.sizePercent(100));

@ -41,12 +41,11 @@ fn formatUnix(sec: u64, comptime fmt: []const u8, opts: std.fmt.FormatOptions, w
} }
fn formatMetricI(value: i64, comptime fmt: []const u8, opts: std.fmt.FormatOptions, w: anytype) !void { fn formatMetricI(value: i64, comptime fmt: []const u8, opts: std.fmt.FormatOptions, w: anytype) !void {
const uval: u64 = std.math.absCast(value); const uval: u64 = @abs(value);
const base: u64 = 1000; const base: u64 = 1000;
if (uval < base) { if (uval < base) {
return std.fmt.formatIntValue(value, fmt, opts, w); return std.fmt.formatIntValue(value, fmt, opts, w);
} }
if (value < 0) { if (value < 0) {
try w.writeByte('-'); try w.writeByte('-');
} }
@ -64,8 +63,60 @@ fn formatMetricU(value: u64, comptime fmt: []const u8, opts: std.fmt.FormatOptio
const mags_si = " kMGTPEZY"; const mags_si = " kMGTPEZY";
const log2 = std.math.log2(value); const log2 = std.math.log2(value);
const m = @min(log2 / comptime std.math.log2(base), mags_si.len - 1); const m = @min(log2 / comptime std.math.log2(base), mags_si.len - 1);
const newval = lossyCast(f64, value) / std.math.pow(f64, lossyCast(f64, base), lossyCast(f64, m));
const suffix = mags_si[m]; const suffix = mags_si[m];
try std.fmt.formatFloatDecimal(newval, opts, w); const newval: f64 = lossyCast(f64, value) / std.math.pow(f64, lossyCast(f64, base), lossyCast(f64, m));
try std.fmt.formatType(newval, "d", opts, w, 0);
try w.writeByte(suffix); try w.writeByte(suffix);
} }
test "unix" {
const t = std.testing;
var buf: [1024]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
try std.fmt.format(fbs.writer(), "{}", .{unix(1136239445)});
try t.expectEqualStrings("2006-01-02 22:04:05 UTC", fbs.getWritten());
}
test "imetric" {
const t = std.testing;
var buf: [1024]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
const table: []const struct { val: i64, str: []const u8 } = &.{
.{ .val = 0, .str = "0" },
.{ .val = -13, .str = "-13" },
.{ .val = 1000, .str = "1k" },
.{ .val = -1234, .str = "-1.234k" },
.{ .val = 12340, .str = "12.34k" },
.{ .val = -123400, .str = "-123.4k" },
.{ .val = 1234000, .str = "1.234M" },
.{ .val = -1234000000, .str = "-1.234G" },
};
for (table) |item| {
fbs.reset();
try std.fmt.format(fbs.writer(), "{}", .{imetric(item.val)});
try t.expectEqualStrings(item.str, fbs.getWritten());
}
}
test "umetric" {
const t = std.testing;
var buf: [1024]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
const table: []const struct { val: u64, str: []const u8 } = &.{
.{ .val = 0, .str = "0" },
.{ .val = 13, .str = "13" },
.{ .val = 1000, .str = "1k" },
.{ .val = 1234, .str = "1.234k" },
.{ .val = 12340, .str = "12.34k" },
.{ .val = 123400, .str = "123.4k" },
.{ .val = 1234000, .str = "1.234M" },
.{ .val = 1234000000, .str = "1.234G" },
};
for (table) |item| {
fbs.reset();
try std.fmt.format(fbs.writer(), "{}", .{umetric(item.val)});
try t.expectEqualStrings(item.str, fbs.getWritten());
}
}

@ -1,9 +1,9 @@
# ci container file for compiling and testing zig projects. # ci container file for compiling and testing zig projects.
# requires a ZIGURL build arg. for instance: # requires a ZIGURL build arg. for instance:
# podman build --rm -t ci-zig0.11.0 -f ci-containerfile \ # podman build --rm -t ci-zig0.12.0 -f ci-containerfile \
# --build-arg ZIGURL=https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz # --build-arg ZIGURL=https://ziglang.org/download/0.12.0/zig-linux-x86_64-0.12.0.tar.xz
FROM alpine:3.18.3 FROM alpine:3.18.6
ARG ZIGURL ARG ZIGURL
RUN apk add --no-cache git curl xz libc-dev sdl2-dev clang16-extra-tools && \ RUN apk add --no-cache git curl xz libc-dev sdl2-dev clang16-extra-tools && \