From 328df67c5d7667bdd46453e51d7f56d0853e5303 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 10 Aug 2023 16:04:57 +0200 Subject: [PATCH] zig: upgrade from 0.10.x to 0.11.0 while there's lots of changes and new features in zig v0.11.0, the most important for this project at the moment is the HTTP client. the client is most likely what will connect to lnd lightning node to fetch stats and info for the UI to then visualize it on "lightning" tab, similar to the bitcoind RPC client. see all zig 0.11 release notes here: https://ziglang.org/download/0.11.0/release-notes.html --- .woodpecker.yml | 14 ++-- README.md | 10 +-- build.zig | 147 +++++++++++++++++++++++------------------ lib/nif/build.zig | 31 ++++----- lib/nif/nif.zig | 2 +- src/comm.zig | 99 +++++++++++++-------------- src/nd.zig | 2 +- src/nd/Daemon.zig | 18 ++--- src/nd/bitcoindrpc.zig | 36 +++------- src/nd/network.zig | 30 ++++----- src/ngui.zig | 65 +++++++++--------- src/test.zig | 30 ++++----- src/test/btcrpc.zig | 2 +- src/test/guiplay.zig | 15 +++-- src/ui/bitcoin.zig | 6 +- src/ui/lvgl.zig | 38 +++++------ src/ui/widget.zig | 12 ++-- src/xfmt.zig | 2 +- tools/ci-containerfile | 8 +-- 19 files changed, 285 insertions(+), 282 deletions(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 4db927b..bb30a5d 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -9,19 +9,23 @@ clone: recursive: false pipeline: lint: - image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3 + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 commands: - ./tools/fmt-check.sh test: - image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3 + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 commands: - zig build test sdl2: - image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3 + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 commands: - zig build -Ddriver=sdl2 aarch64: - image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3 + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 commands: - - zig build -Ddriver=fbev -Dtarget=aarch64-linux-musl -Drelease-safe -Dstrip + - zig build -Ddriver=fbev -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe -Dstrip - sha256sum zig-out/bin/nd zig-out/bin/ngui + playground: + image: git.qcode.ch/nakamochi/ci-zig0.11.0:v2 + commands: + - zig build guiplay btcrpc diff --git a/README.md b/README.md index e3e1d95..8cc86b8 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ build for rpi: - zig build -Dtarget=aarch64-linux-musl -Ddriver=fbev -Drelease-safe -Dstrip + zig build -Dtarget=aarch64-linux-musl -Ddriver=fbev -Doptimize=ReleaseSafe -Dstrip otherwise just `zig build` on dev host ## local development -you'll need [zig v0.10.x](https://ziglang.org/download/). +you'll need [zig v0.11.x](https://ziglang.org/download/). if working on the gui, also [SDL2](https://www.libsdl.org/). note that compiling the daemon on macOS is currently unsupported since @@ -58,17 +58,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: podman build --rm -t ndg-ci -f ./tools/ci-containerfile \ - --build-arg ZIGURL=https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz + --build-arg ZIGURL=https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz then tag it with the target URL, for example: - podman tag localhost/ndg-ci git.qcode.ch/nakamochi/ci-zig0.10.1:v1 + podman tag localhost/ndg-ci git.qcode.ch/nakamochi/ci-zig0.11.0:v2 generate an [access token](https://git.qcode.ch/user/settings/applications), login to the container registry and push the image to remote: podman login git.qcode.ch - podman push git.qcode.ch/nakamochi/ci-zig0.10.1:v1 + podman push git.qcode.ch/nakamochi/ci-zig0.11.0:v2 the image will be available at https://git.qcode.ch/nakamochi/-/packages/ diff --git a/build.zig b/build.zig index f1eb2d2..7cc13ca 100644 --- a/build.zig +++ b/build.zig @@ -1,20 +1,26 @@ const std = @import("std"); -const nifbuild = @import("lib/nif/build.zig"); -pub fn build(b: *std.build.Builder) void { +pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); - const mode = b.standardReleaseOptions(); - + const optimize = b.standardOptimizeOption(.{}); const strip = b.option(bool, "strip", "strip output binary; default: false") orelse false; const drv = b.option(DriverTarget, "driver", "display and input drivers combo; default: sdl2") orelse .sdl2; const disp_horiz = b.option(u32, "horiz", "display horizontal pixels count; default: 800") orelse 800; const disp_vert = b.option(u32, "vert", "display vertical pixels count; default: 480") orelse 480; - const lvgl_loglevel = b.option(LVGLLogLevel, "lvgl_loglevel", "LVGL lib logging level") orelse LVGLLogLevel.default(mode); + const lvgl_loglevel = b.option(LVGLLogLevel, "lvgl_loglevel", "LVGL lib logging level") orelse LVGLLogLevel.default(optimize); const inver = b.option([]const u8, "version", "semantic version of the build; must match git tag when available"); const buildopts = b.addOptions(); buildopts.addOption(DriverTarget, "driver", drv); const semver_step = VersionStep.create(b, buildopts, inver); + buildopts.step.dependOn(semver_step); + + // network interface (nif) standalone library used by the daemon and tests. + const libnif_dep = b.anonymousDependency("lib/nif", @import("lib/nif/build.zig"), .{ + .target = target, + .optimize = optimize, + }); + const libnif = libnif_dep.artifact("nif"); const common_cflags = .{ "-Wall", @@ -25,17 +31,18 @@ pub fn build(b: *std.build.Builder) void { }; // gui build - const ngui = b.addExecutable("ngui", "src/ngui.zig"); - ngui.setTarget(target); - ngui.setBuildMode(mode); + const ngui = b.addExecutable(.{ + .name = "ngui", + .root_source_file = .{ .path = "src/ngui.zig" }, + .target = target, + .optimize = optimize, + .link_libc = true, + }); ngui.pie = true; ngui.strip = strip; - ngui.step.dependOn(semver_step); - - ngui.addPackage(buildopts.getPackage("build_options")); - ngui.addIncludePath("lib"); - ngui.addIncludePath("src/ui/c"); - ngui.linkLibC(); + ngui.addOptions("build_options", buildopts); + ngui.addIncludePath(.{ .path = "lib" }); + ngui.addIncludePath(.{ .path = "src/ui/c" }); const lvgl_flags = .{ "-std=c11", @@ -68,14 +75,14 @@ pub fn build(b: *std.build.Builder) void { switch (drv) { .sdl2 => { ngui.addCSourceFiles(lvgl_sdl2_src, &lvgl_flags); - ngui.addCSourceFile("src/ui/c/drv_sdl2.c", &ngui_cflags); + ngui.addCSourceFile(.{ .file = .{ .path = "src/ui/c/drv_sdl2.c" }, .flags = &ngui_cflags }); ngui.defineCMacro("NM_DRV_SDL2", null); ngui.defineCMacro("USE_SDL", null); ngui.linkSystemLibrary("SDL2"); }, .fbev => { ngui.addCSourceFiles(lvgl_fbev_src, &lvgl_flags); - ngui.addCSourceFile("src/ui/c/drv_fbev.c", &ngui_cflags); + ngui.addCSourceFile(.{ .file = .{ .path = "src/ui/c/drv_fbev.c" }, .flags = &ngui_cflags }); ngui.defineCMacro("NM_DRV_FBEV", null); ngui.defineCMacro("USE_FBDEV", null); ngui.defineCMacro("USE_EVDEV", null); @@ -83,68 +90,77 @@ pub fn build(b: *std.build.Builder) void { } const ngui_build_step = b.step("ngui", "build ngui (nakamochi gui)"); - ngui_build_step.dependOn(&b.addInstallArtifact(ngui).step); + ngui_build_step.dependOn(&b.addInstallArtifact(ngui, .{}).step); // daemon build - const nd = b.addExecutable("nd", "src/nd.zig"); - nd.setTarget(target); - nd.setBuildMode(mode); + const nd = b.addExecutable(.{ + .name = "nd", + .root_source_file = .{ .path = "src/nd.zig" }, + .target = target, + .optimize = optimize, + }); nd.pie = true; nd.strip = strip; - nd.step.dependOn(semver_step); - - nd.addPackage(buildopts.getPackage("build_options")); - nifbuild.addPkg(b, nd, "lib/nif"); - const niflib = nifbuild.library(b, "lib/nif"); - niflib.setTarget(target); - niflib.setBuildMode(mode); - nd.linkLibrary(niflib); + nd.addOptions("build_options", buildopts); + nd.addModule("nif", libnif_dep.module("nif")); + nd.linkLibrary(libnif); const nd_build_step = b.step("nd", "build nd (nakamochi daemon)"); - nd_build_step.dependOn(&b.addInstallArtifact(nd).step); - - // default build - const build_all_step = b.step("all", "build nd and ngui"); - build_all_step.dependOn(ngui_build_step); - build_all_step.dependOn(nd_build_step); - b.default_step.dependOn(build_all_step); + nd_build_step.dependOn(&b.addInstallArtifact(nd, .{}).step); + // automated tests { - const tests = b.addTest("src/test.zig"); - tests.setTarget(target); - tests.setBuildMode(mode); - tests.linkLibC(); - tests.addPackage(buildopts.getPackage("build_options")); - nifbuild.addPkg(b, tests, "lib/nif"); - - const f = b.option([]const u8, "test-filter", "run tests matching the filter"); - tests.setFilter(f); - + const tests = b.addTest(.{ + .root_source_file = .{ .path = "src/test.zig" }, + .target = target, + .optimize = optimize, + .link_libc = true, + .filter = b.option([]const u8, "test-filter", "run tests matching the filter"), + }); + tests.addOptions("build_options", buildopts); + tests.addModule("nif", libnif_dep.module("nif")); + tests.linkLibrary(libnif); + + const run_tests = b.addRunArtifact(tests); const test_step = b.step("test", "run tests"); - test_step.dependOn(&tests.step); + test_step.dependOn(&run_tests.step); } + // GUI playground { - const guiplay = b.addExecutable("guiplay", "src/test/guiplay.zig"); - guiplay.setTarget(target); - guiplay.setBuildMode(mode); - guiplay.step.dependOn(semver_step); - guiplay.addPackagePath("comm", "src/comm.zig"); + const guiplay = b.addExecutable(.{ + .name = "guiplay", + .root_source_file = .{ .path = "src/test/guiplay.zig" }, + .target = target, + .optimize = optimize, + }); + guiplay.addModule("comm", b.createModule(.{ .source_file = .{ .path = "src/comm.zig" } })); 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); guiplay_build_step.dependOn(ngui_build_step); } + // bitcoind RPC client playground { - const btcrpc = b.addExecutable("btcrpc", "src/test/btcrpc.zig"); - btcrpc.setTarget(target); - btcrpc.setBuildMode(mode); + const btcrpc = b.addExecutable(.{ + .name = "btcrpc", + .root_source_file = .{ .path = "src/test/btcrpc.zig" }, + .target = target, + .optimize = optimize, + }); btcrpc.strip = strip; - btcrpc.addPackagePath("bitcoindrpc", "src/nd/bitcoindrpc.zig"); + btcrpc.addModule("bitcoindrpc", b.createModule(.{ .source_file = .{ .path = "src/nd/bitcoindrpc.zig" } })); + 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); } + + // default build step + const build_all_step = b.step("all", "build nd and ngui (default step)"); + build_all_step.dependOn(ngui_build_step); + build_all_step.dependOn(nd_build_step); + b.default_step.dependOn(build_all_step); } const DriverTarget = enum { @@ -354,23 +370,28 @@ const LVGLLogLevel = enum { /// git tag is found. const VersionStep = struct { inver: ?[]const u8, // input version in std.SemanticVersion.parse format - buildopts: *std.build.OptionsStep, // where to store the build version + buildopts: *std.Build.Step.Options, // where to store the build version - b: *std.build.Builder, - step: std.build.Step, + b: *std.Build, + step: std.Build.Step, - fn create(b: *std.build.Builder, o: *std.build.OptionsStep, inver: ?[]const u8) *std.build.Step { + fn create(b: *std.Build, o: *std.Build.Step.Options, inver: ?[]const u8) *std.Build.Step { const vstep = b.allocator.create(VersionStep) catch unreachable; vstep.* = VersionStep{ .inver = inver, .buildopts = o, .b = b, - .step = std.build.Step.init(.custom, "VersionStep: ndg semver", b.allocator, make), + .step = std.Build.Step.init(.{ + .id = .custom, + .name = "VersionStep: ndg semver", + .owner = b, + .makeFn = make, + }), }; return &vstep.step; } - fn make(step: *std.build.Step) anyerror!void { + fn make(step: *std.Build.Step, _: *std.Progress.Node) anyerror!void { const self = @fieldParentPtr(VersionStep, "step", step); const semver = try self.eval(); std.log.info("build version: {any}", .{semver}); diff --git a/lib/nif/build.zig b/lib/nif/build.zig index dfb9207..6a32113 100644 --- a/lib/nif/build.zig +++ b/lib/nif/build.zig @@ -1,21 +1,23 @@ -const build = @import("std").build; +const std = @import("std"); -pub fn addPkg(b: *build.Builder, obj: *build.LibExeObjStep, prefix: []const u8) void { - obj.addPackagePath("nif", pkgPath(b, prefix)); -} - -pub fn pkgPath(b: *build.Builder, prefix: []const u8) []const u8 { - return b.pathJoin(&.{ prefix, "nif.zig" }); -} +pub fn build(b: *std.Build) void { + _ = b.addModule("nif", .{ .source_file = .{ .path = "nif.zig" } }); -pub fn library(b: *build.Builder, prefix: []const u8) *build.LibExeObjStep { - const lib = b.addStaticLibrary("nif", b.pathJoin(&.{ prefix, "nif.zig" })); - lib.addIncludePath(b.pathJoin(&.{ prefix, "wpa_supplicant" })); + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const lib = b.addStaticLibrary(.{ + .name = "nif", + .root_source_file = .{ .path = "nif.zig" }, + .target = target, + .optimize = optimize, + .link_libc = true, + }); lib.defineCMacro("CONFIG_CTRL_IFACE", null); lib.defineCMacro("CONFIG_CTRL_IFACE_UNIX", null); + lib.addIncludePath(.{ .path = "wpa_supplicant" }); lib.addCSourceFiles(&.{ - b.pathJoin(&.{ prefix, "wpa_supplicant/wpa_ctrl.c" }), - b.pathJoin(&.{ prefix, "wpa_supplicant/os_unix.c" }), + "wpa_supplicant/wpa_ctrl.c", + "wpa_supplicant/os_unix.c", }, &.{ "-Wall", "-Wextra", @@ -24,6 +26,5 @@ pub fn library(b: *build.Builder, prefix: []const u8) *build.LibExeObjStep { "-Wunused-parameter", "-Werror", }); - lib.linkLibC(); - return lib; + b.installArtifact(lib); } diff --git a/lib/nif/nif.zig b/lib/nif/nif.zig index 7a43961..e9c6556 100644 --- a/lib/nif/nif.zig +++ b/lib/nif/nif.zig @@ -46,7 +46,7 @@ pub fn pubAddresses(allocator: mem.Allocator, ifname: ?[]const u8) ![]net.Addres // skip loopbacks and those which are not "up" continue; } - const ipaddr = net.Address.initPosix(@alignCast(4, 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) { // want only global, with 0 scope // non-zero scopes make sense for link-local addr only. diff --git a/src/comm.zig b/src/comm.zig index dd2db44..2b5c811 100644 --- a/src/comm.zig +++ b/src/comm.zig @@ -103,22 +103,37 @@ pub const MessageTag = enum(u16) { // next: 0x0b }; +/// the return value type from `read` fn. +pub const ParsedMessage = struct { + value: Message, + arena: ?*std.heap.ArenaAllocator = null, // null for void message tags + + /// releases all resources used by the message. + pub fn deinit(self: @This()) void { + if (self.arena) |a| { + const allocator = a.child_allocator; + a.deinit(); + allocator.destroy(a); + } + } +}; + /// reads and parses a single message from the input stream reader. /// propagates reader errors as is. for example, a closed reader returns /// error.EndOfStream. /// -/// callers must deallocate resources with Message.free when done. -pub fn read(allocator: mem.Allocator, reader: anytype) !Message { +/// callers must deallocate resources with ParsedMessage.deinit when done. +pub fn read(allocator: mem.Allocator, reader: anytype) !ParsedMessage { // alternative is @intToEnum(reader.ReadIntLittle(u16)) but it may panic. const tag = try reader.readEnum(MessageTag, .Little); const len = try reader.readIntLittle(u64); if (len == 0) { return switch (tag) { - .ping => Message{ .ping = {} }, - .pong => Message{ .pong = {} }, - .poweroff => Message{ .poweroff = {} }, - .standby => Message{ .standby = {} }, - .wakeup => Message{ .wakeup = {} }, + .ping => .{ .value = .{ .ping = {} } }, + .pong => .{ .value = .{ .pong = {} } }, + .poweroff => .{ .value = .{ .poweroff = {} } }, + .standby => .{ .value = .{ .standby = {} } }, + .wakeup => .{ .value = .{ .wakeup = {} } }, else => Error.CommReadZeroLenInNonVoidTag, }; } @@ -126,24 +141,22 @@ pub fn read(allocator: mem.Allocator, reader: anytype) !Message { var bytes = try allocator.alloc(u8, len); defer allocator.free(bytes); try reader.readNoEof(bytes); - const jopt = json.ParseOptions{ .allocator = allocator, .ignore_unknown_fields = true }; - var jstream = json.TokenStream.init(bytes); return switch (tag) { .ping, .pong, .poweroff, .standby, .wakeup => unreachable, // handled above - .wifi_connect => Message{ - .wifi_connect = try json.parse(Message.WifiConnect, &jstream, jopt), - }, - .network_report => Message{ - .network_report = try json.parse(Message.NetworkReport, &jstream, jopt), - }, - .get_network_report => Message{ - .get_network_report = try json.parse(Message.GetNetworkReport, &jstream, jopt), - }, - .poweroff_progress => Message{ - .poweroff_progress = try json.parse(Message.PoweroffProgress, &jstream, jopt), - }, - .bitcoind_report => Message{ - .bitcoind_report = try json.parse(Message.BitcoindReport, &jstream, jopt), + inline else => |t| { + var arena = try allocator.create(std.heap.ArenaAllocator); + arena.* = std.heap.ArenaAllocator.init(allocator); + errdefer { + arena.deinit(); + allocator.destroy(arena); + } + const jopt = std.json.ParseOptions{ .ignore_unknown_fields = true, .allocate = .alloc_always }; + const v = try json.parseFromSliceLeaky(std.meta.TagPayload(Message, t), arena.allocator(), bytes, jopt); + const parsed = ParsedMessage{ + .arena = arena, + .value = @unionInit(Message, @tagName(t), v), + }; + return parsed; }, }; } @@ -151,35 +164,25 @@ pub fn read(allocator: mem.Allocator, reader: anytype) !Message { /// outputs the message msg using writer. /// all allocated resources are freed upon return. pub fn write(allocator: mem.Allocator, writer: anytype, msg: Message) !void { - const jopt = .{ .whitespace = null }; var data = ByteArrayList.init(allocator); defer data.deinit(); switch (msg) { .ping, .pong, .poweroff, .standby, .wakeup => {}, // zero length payload - .wifi_connect => try json.stringify(msg.wifi_connect, jopt, data.writer()), - .network_report => try json.stringify(msg.network_report, jopt, data.writer()), - .get_network_report => try json.stringify(msg.get_network_report, jopt, data.writer()), - .poweroff_progress => try json.stringify(msg.poweroff_progress, jopt, data.writer()), - .bitcoind_report => try json.stringify(msg.bitcoind_report, jopt, data.writer()), + .wifi_connect => try json.stringify(msg.wifi_connect, .{}, data.writer()), + .network_report => try json.stringify(msg.network_report, .{}, data.writer()), + .get_network_report => try json.stringify(msg.get_network_report, .{}, data.writer()), + .poweroff_progress => try json.stringify(msg.poweroff_progress, .{}, data.writer()), + .bitcoind_report => try json.stringify(msg.bitcoind_report, .{}, data.writer()), } if (data.items.len > std.math.maxInt(u64)) { return Error.CommWriteTooLarge; } - try writer.writeIntLittle(u16, @enumToInt(msg)); + try writer.writeIntLittle(u16, @intFromEnum(msg)); try writer.writeIntLittle(u64, data.items.len); try writer.writeAll(data.items); } -pub fn free(allocator: mem.Allocator, m: Message) void { - switch (m) { - .ping, .pong, .poweroff, .standby, .wakeup => {}, // zero length payload - else => |v| { - json.parseFree(@TypeOf(v), v, .{ .allocator = allocator }); - }, - } -} - // TODO: use fifo // // var buf = std.fifo.LinearFifo(u8, .Dynamic).init(t.allocator); @@ -206,16 +209,16 @@ test "read" { var buf = std.ArrayList(u8).init(t.allocator); defer buf.deinit(); - try buf.writer().writeIntLittle(u16, @enumToInt(msg)); + try buf.writer().writeIntLittle(u16, @intFromEnum(msg)); try buf.writer().writeIntLittle(u64, data.items.len); try buf.writer().writeAll(data.items); var bs = std.io.fixedBufferStream(buf.items); const res = try read(t.allocator, bs.reader()); - defer free(t.allocator, res); + defer res.deinit(); - try t.expectEqualStrings(msg.wifi_connect.ssid, res.wifi_connect.ssid); - try t.expectEqualStrings(msg.wifi_connect.password, res.wifi_connect.password); + try t.expectEqualStrings(msg.wifi_connect.ssid, res.value.wifi_connect.ssid); + try t.expectEqualStrings(msg.wifi_connect.password, res.value.wifi_connect.password); } test "write" { @@ -229,7 +232,7 @@ test "write" { const payload = "{\"ssid\":\"wlan\",\"password\":\"secret\"}"; var js = std.ArrayList(u8).init(t.allocator); defer js.deinit(); - try js.writer().writeIntLittle(u16, @enumToInt(msg)); + try js.writer().writeIntLittle(u16, @intFromEnum(msg)); try js.writer().writeIntLittle(u64, payload.len); try js.appendSlice(payload); @@ -255,8 +258,8 @@ test "write/read void tags" { try write(t.allocator, buf.writer(), m); var bs = std.io.fixedBufferStream(buf.items); const res = try read(t.allocator, bs.reader()); - free(t.allocator, res); // noop - try t.expectEqual(m, res); + res.deinit(); // noop due to void type + try t.expectEqual(m, res.value); } } @@ -283,7 +286,7 @@ test "msg sequence" { var bs = std.io.fixedBufferStream(buf.items); for (msgs) |m| { const res = try read(t.allocator, bs.reader()); - defer free(t.allocator, res); - try t.expectEqual(@as(MessageTag, m), @as(MessageTag, res)); + defer res.deinit(); + try t.expectEqual(@as(MessageTag, m), @as(MessageTag, res.value)); } } diff --git a/src/nd.zig b/src/nd.zig index a1b1530..ab7f467 100644 --- a/src/nd.zig +++ b/src/nd.zig @@ -126,7 +126,7 @@ fn sighandler(sig: c_int) callconv(.C) void { pub fn main() !void { // main heap allocator used throughout the lifetime of nd var gpa_state = std.heap.GeneralPurposeAllocator(.{}){}; - defer if (gpa_state.deinit()) { + defer if (gpa_state.deinit() == .leak) { logger.err("memory leaks detected", .{}); }; const gpa = gpa_state.allocator(); diff --git a/src/nd/Daemon.zig b/src/nd/Daemon.zig index 6abdfc1..fc31daf 100644 --- a/src/nd/Daemon.zig +++ b/src/nd/Daemon.zig @@ -82,7 +82,7 @@ pub fn init(a: std.mem.Allocator, r: std.fs.File.Reader, w: std.fs.File.Writer, .uiwriter = w, .wpa_ctrl = try types.WpaControl.open(wpa), .state = .stopped, - .services = svlist.toOwnedSlice(), + .services = try svlist.toOwnedSlice(), // send a network report right at start without wifi scan to make it faster. .want_network_report = true, .want_wifi_scan = false, @@ -297,7 +297,7 @@ fn commThreadLoop(self: *Daemon) void { std.atomic.spinLoopHint(); time.sleep(100 * time.ns_per_ms); - const msg = comm.read(self.allocator, self.uireader) catch |err| { + const res = comm.read(self.allocator, self.uireader) catch |err| { self.mu.lock(); defer self.mu.unlock(); if (self.want_stop) { @@ -316,7 +316,9 @@ fn commThreadLoop(self: *Daemon) void { }, } }; + defer res.deinit(); + const msg = res.value; logger.debug("got msg: {s}", .{@tagName(msg)}); switch (msg) { .pong => { @@ -343,12 +345,12 @@ fn commThreadLoop(self: *Daemon) void { }, else => logger.warn("unhandled msg tag {s}", .{@tagName(msg)}), } - comm.free(self.allocator, msg); self.mu.lock(); quit = self.want_stop; self.mu.unlock(); } + logger.info("exiting comm thread loop", .{}); } @@ -356,8 +358,8 @@ fn commThreadLoop(self: *Daemon) void { fn sendPoweroffReport(self: *Daemon) !void { var svstat = try self.allocator.alloc(comm.Message.PoweroffProgress.Service, self.services.len); defer self.allocator.free(svstat); - for (self.services) |*sv, i| { - svstat[i] = .{ + for (self.services, svstat) |*sv, *stat| { + stat.* = .{ .name = sv.name, .stopped = sv.status() == .stopped, .err = if (sv.lastStopError()) |err| @errorName(err) else null, @@ -616,19 +618,19 @@ test "start-poweroff" { try tt.expectDeepEqual(comm.Message{ .poweroff_progress = .{ .services = &.{ .{ .name = "lnd", .stopped = false, .err = null }, .{ .name = "bitcoind", .stopped = false, .err = null }, - } } }, msg1); + } } }, msg1.value); const msg2 = try comm.read(arena, gui_reader); try tt.expectDeepEqual(comm.Message{ .poweroff_progress = .{ .services = &.{ .{ .name = "lnd", .stopped = true, .err = null }, .{ .name = "bitcoind", .stopped = false, .err = null }, - } } }, msg2); + } } }, msg2.value); const msg3 = try comm.read(arena, gui_reader); try tt.expectDeepEqual(comm.Message{ .poweroff_progress = .{ .services = &.{ .{ .name = "lnd", .stopped = true, .err = null }, .{ .name = "bitcoind", .stopped = true, .err = null }, - } } }, msg3); + } } }, msg3.value); // TODO: ensure "poweroff" was executed once custom runner is in a zig release; // need custom runner to set up a global registry for child processes. diff --git a/src/nd/bitcoindrpc.zig b/src/nd/bitcoindrpc.zig index 7f0ce5f..670a28e 100644 --- a/src/nd/bitcoindrpc.zig +++ b/src/nd/bitcoindrpc.zig @@ -55,16 +55,7 @@ pub const Client = struct { }; pub fn Result(comptime m: Method) type { - return struct { - value: ResultValue(m), - arena: *ArenaAllocator, - - pub fn deinit(self: @This()) void { - const allocator = self.arena.child_allocator; - self.arena.deinit(); - allocator.destroy(self.arena); - } - }; + return std.json.Parsed(ResultValue(m)); } pub fn ResultValue(comptime m: Method) type { @@ -142,27 +133,16 @@ pub const Client = struct { } fn parseResponse(self: Client, comptime m: Method, b: []const u8) !Result(m) { - var result = Result(m){ - .value = undefined, - .arena = try self.allocator.create(ArenaAllocator), - }; - errdefer self.allocator.destroy(result.arena); - result.arena.* = ArenaAllocator.init(self.allocator); - - var jstream = std.json.TokenStream.init(b); - const jopt = std.json.ParseOptions{ .allocator = result.arena.allocator(), .ignore_unknown_fields = true }; - const resp = try std.json.parse(RpcResponse(m), &jstream, jopt); - - errdefer result.arena.deinit(); - if (resp.@"error") |errfield| { + const jopt = std.json.ParseOptions{ .ignore_unknown_fields = true, .allocate = .alloc_always }; + const resp = try std.json.parseFromSlice(RpcResponse(m), self.allocator, b, jopt); + errdefer resp.deinit(); + if (resp.value.@"error") |errfield| { return rpcErrorFromCode(errfield.code) orelse error.UnknownError; } - if (resp.result == null) { + if (resp.value.result == null) { return error.NullResult; } - - result.value = resp.result.?; - return result; + return .{ .value = resp.value.result.?, .arena = resp.arena }; } fn formatreq(self: *Client, comptime m: Method, args: MethodArgs(m)) ![]const u8 { @@ -189,7 +169,7 @@ pub const Client = struct { try w.print("Content-Length: {d}\r\n", .{jreq.items.len}); try w.writeAll("\r\n"); try w.writeAll(jreq.items); - return bytes.toOwnedSlice(); + return try bytes.toOwnedSlice(); } fn getAuthBase64(self: Client) ![]const u8 { diff --git a/src/nd/network.zig b/src/nd/network.zig index cfe5e54..610d315 100644 --- a/src/nd/network.zig +++ b/src/nd/network.zig @@ -62,40 +62,34 @@ pub fn addWifi(gpa: mem.Allocator, wpa_ctrl: *types.WpaControl, ssid: []const u8 /// reports network status to the writer w in `comm.Message.NetworkReport` format. pub fn sendReport(gpa: mem.Allocator, wpa_ctrl: *types.WpaControl, w: anytype) !void { + var arena_state = std.heap.ArenaAllocator.init(gpa); + defer arena_state.deinit(); + const arena = arena_state.allocator(); var report = comm.Message.NetworkReport{ .ipaddrs = undefined, .wifi_ssid = null, - .wifi_scan_networks = undefined, + .wifi_scan_networks = &.{}, }; // fetch all public IP addresses using getifaddrs - const pubaddr = try nif.pubAddresses(gpa, null); - defer gpa.free(pubaddr); - //var addrs = std.ArrayList([]).init(t.allocator); - var ipaddrs = try gpa.alloc([]const u8, pubaddr.len); - for (pubaddr) |a, i| { - ipaddrs[i] = try std.fmt.allocPrint(gpa, "{s}", .{a}); + const pubaddr = try nif.pubAddresses(arena, null); + var ipaddr = try std.ArrayList([]const u8).initCapacity(arena, pubaddr.len); + for (pubaddr) |apub| { + try ipaddr.append(try std.fmt.allocPrint(arena, "{}", .{apub})); } - defer { - for (ipaddrs) |a| gpa.free(a); - gpa.free(ipaddrs); - } - report.ipaddrs = ipaddrs; + report.ipaddrs = try ipaddr.toOwnedSlice(); // get currently connected SSID, if any, from WPA ctrl - const ssid = queryWifiSSID(gpa, wpa_ctrl) catch |err| blk: { + report.wifi_ssid = queryWifiSSID(arena, wpa_ctrl) catch |err| blk: { logger.err("queryWifiSsid: {any}", .{err}); break :blk null; }; - defer if (ssid) |v| gpa.free(v); - report.wifi_ssid = ssid; // fetch available wifi networks from scan results using WPA ctrl - var wifi_networks: ?types.StringList = if (queryWifiScanResults(gpa, wpa_ctrl)) |v| v else |err| blk: { + var wifi_networks: ?types.StringList = if (queryWifiScanResults(arena, wpa_ctrl)) |v| v else |err| blk: { logger.err("queryWifiScanResults: {any}", .{err}); break :blk null; }; - defer if (wifi_networks) |*list| list.deinit(); if (wifi_networks) |list| { report.wifi_scan_networks = list.items(); } @@ -179,5 +173,5 @@ fn queryWifiNetworksList(gpa: mem.Allocator, wpa_ctrl: *types.WpaControl, filter } list.append(id) catch {}; // grab anything we can } - return list.toOwnedSlice(); + return try list.toOwnedSlice(); } diff --git a/src/ngui.zig b/src/ngui.zig index fe771da..0055b14 100644 --- a/src/ngui.zig +++ b/src/ngui.zig @@ -10,12 +10,6 @@ const lvgl = @import("ui/lvgl.zig"); const screen = @import("ui/screen.zig"); const symbol = @import("ui/symbol.zig"); -/// SIGPIPE is triggered when a process attempts to write to a broken pipe. -/// by default, SIGPIPE terminates the process without invoking a panic handler. -/// this declaration makes such writes result in EPIPE (error.BrokenPipe) to let -/// the program can handle it. -pub const keep_sigpipe = true; - const logger = std.log.scoped(.ngui); // these are auto-closed as soon as main fn terminates. @@ -46,39 +40,40 @@ var state: enum { /// while deinit and replace handle concurrency, field access requires holding mu. var last_report: struct { mu: std.Thread.Mutex = .{}, - network: ?comm.Message.NetworkReport = null, - bitcoind: ?comm.Message.BitcoindReport = null, + network: ?comm.ParsedMessage = null, // NetworkReport + bitcoind: ?comm.ParsedMessage = null, // BitcoinReport fn deinit(self: *@This()) void { self.mu.lock(); defer self.mu.unlock(); if (self.network) |v| { - comm.free(gpa, .{ .network_report = v }); + v.deinit(); self.network = null; } if (self.bitcoind) |v| { - comm.free(gpa, .{ .bitcoind_report = v }); + v.deinit(); self.bitcoind = null; } } - fn replace(self: *@This(), new: anytype) void { + fn replace(self: *@This(), new: comm.ParsedMessage) void { self.mu.lock(); defer self.mu.unlock(); - switch (@TypeOf(new)) { - comm.Message.NetworkReport => { + const tag: comm.MessageTag = new.value; + switch (tag) { + .network_report => { if (self.network) |old| { - comm.free(gpa, .{ .network_report = old }); + old.deinit(); } self.network = new; }, - comm.Message.BitcoindReport => { + .bitcoind_report => { if (self.bitcoind) |old| { - comm.free(gpa, .{ .bitcoind_report = old }); + old.deinit(); } self.bitcoind = new; }, - else => @compileError("unhandled type: " ++ @typeName(@TypeOf(new))), + else => |t| logger.err("last_report: replace: unhandled tag {}", .{t}), } } } = .{}; @@ -103,9 +98,9 @@ export fn nm_get_curr_tick() u32 { const ms = tick_timer.read() / time.ns_per_ms; const over = ms >> 32; if (over > 0) { - return @truncate(u32, over); // LVGL deals with overflow correctly + return @truncate(over); // LVGL deals with overflow correctly } - return @truncate(u32, ms); + return @truncate(ms); } export fn nm_check_idle_time(_: *lvgl.LvTimer) void { @@ -223,27 +218,27 @@ fn commThreadLoopCycle() !void { ui_mutex.lock(); // guards the state and all UI calls below defer ui_mutex.unlock(); switch (state) { - .standby => switch (msg) { + .standby => switch (msg.value) { .ping => try comm.write(gpa, stdout, comm.Message.pong), - .network_report => |v| last_report.replace(v), - .bitcoind_report => |v| last_report.replace(v), - else => logger.debug("ignoring {s}: in standby", .{@tagName(msg)}), + .network_report => last_report.replace(msg), + .bitcoind_report => last_report.replace(msg), + else => logger.debug("ignoring {s}: in standby", .{@tagName(msg.value)}), }, - .active, .alert => switch (msg) { + .active, .alert => switch (msg.value) { .ping => try comm.write(gpa, stdout, comm.Message.pong), .poweroff_progress => |rep| { ui.poweroff.updateStatus(rep) catch |err| logger.err("poweroff.updateStatus: {any}", .{err}); - comm.free(gpa, msg); + msg.deinit(); }, .network_report => |rep| { updateNetworkStatus(rep) catch |err| logger.err("updateNetworkStatus: {any}", .{err}); - last_report.replace(rep); + last_report.replace(msg); }, .bitcoind_report => |rep| { ui.bitcoin.updateTabPanel(rep) catch |err| logger.err("bitcoin.updateTabPanel: {any}", .{err}); - last_report.replace(rep); + last_report.replace(msg); }, - else => logger.warn("unhandled msg tag {s}", .{@tagName(msg)}), + else => logger.warn("unhandled msg tag {s}", .{@tagName(msg.value)}), }, } } @@ -281,11 +276,15 @@ fn uiThreadLoop() void { last_report.mu.lock(); defer last_report.mu.unlock(); - if (last_report.network) |rep| { - updateNetworkStatus(rep) catch |err| logger.err("updateNetworkStatus: {any}", .{err}); + if (last_report.network) |msg| { + updateNetworkStatus(msg.value.network_report) catch |err| { + logger.err("updateNetworkStatus: {any}", .{err}); + }; } - if (last_report.bitcoind) |rep| { - ui.bitcoin.updateTabPanel(rep) catch |err| logger.err("bitcoin.updateTabPanel: {any}", .{err}); + if (last_report.bitcoind) |msg| { + ui.bitcoin.updateTabPanel(msg.value.bitcoind_report) catch |err| { + logger.err("bitcoin.updateTabPanel: {any}", .{err}); + }; } } continue; @@ -342,7 +341,7 @@ fn sighandler(sig: c_int) callconv(.C) void { pub fn main() anyerror!void { // main heap allocator used through the lifetime of nd var gpa_state = std.heap.GeneralPurposeAllocator(.{}){}; - defer if (gpa_state.deinit()) { + defer if (gpa_state.deinit() == .leak) { logger.err("memory leaks detected", .{}); }; gpa = gpa_state.allocator(); diff --git a/src/test.zig b/src/test.zig index 0e2e995..b5744c5 100644 --- a/src/test.zig +++ b/src/test.zig @@ -55,8 +55,8 @@ pub const TestChildProcess = struct { pub fn init(argv: []const []const u8, allocator: std.mem.Allocator) TestChildProcess { var adup = allocator.alloc([]u8, argv.len) catch unreachable; - for (argv) |v, i| { - adup[i] = allocator.dupe(u8, v) catch unreachable; + for (argv, adup) |v, *dup| { + dup.* = allocator.dupe(u8, v) catch unreachable; } return .{ .allocator = allocator, @@ -167,10 +167,10 @@ pub const TestWpaControl = struct { } }; -/// similar to std.testing.expectEqual but compares slices with expectEqualSlices +/// similar to std.testing.expectEqualDeep but compares slices with expectEqualSlices /// or expectEqualStrings where slice element is a u8. +/// unhandled types are passed to std.testing.expectEqualDeep. pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void { - const t = std.testing; switch (@typeInfo(@TypeOf(actual))) { .Pointer => |p| { switch (p.size) { @@ -185,7 +185,7 @@ pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void { } break :blk null; }; - const n = std.math.min(expected.len, actual.len); + const n = @min(expected.len, actual.len); var i: usize = 0; while (i < n) : (i += 1) { expectDeepEqual(expected[i], actual[i]) catch |e| { @@ -199,14 +199,14 @@ pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void { }, else => { if (p.child == u8) { - try t.expectEqualStrings(expected, actual); + try std.testing.expectEqualStrings(expected, actual); } else { - try t.expectEqualSlices(p.child, expected, actual); + try std.testing.expectEqualSlices(p.child, expected, actual); } }, } }, - else => try t.expectEqual(expected, actual), + else => try std.testing.expectEqualDeep(expected, actual), } }, .Struct => |st| { @@ -238,17 +238,15 @@ pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void { } const Tag = std.meta.Tag(@TypeOf(expected)); const atag = @as(Tag, actual); - try t.expectEqual(@as(Tag, expected), atag); - inline for (u.fields) |f| { - if (std.mem.eql(u8, f.name, @tagName(atag))) { - try expectDeepEqual(@field(expected, f.name), @field(actual, f.name)); - return; - } + try std.testing.expectEqual(@as(Tag, expected), atag); + switch (expected) { + inline else => |x, tag| { + try expectDeepEqual(x, @field(actual, @tagName(tag))); + }, } - unreachable; }, else => { - try t.expectEqual(expected, actual); + try std.testing.expectEqualDeep(expected, actual); }, } } diff --git a/src/test/btcrpc.zig b/src/test/btcrpc.zig index 58c300f..5dff002 100644 --- a/src/test/btcrpc.zig +++ b/src/test/btcrpc.zig @@ -4,7 +4,7 @@ const bitcoinrpc = @import("bitcoindrpc"); pub fn main() !void { var gpa_state = std.heap.GeneralPurposeAllocator(.{}){}; - defer if (gpa_state.deinit()) { + defer if (gpa_state.deinit() == .leak) { std.debug.print("memory leaks detected!", .{}); }; const gpa = gpa_state.allocator(); diff --git a/src/test/guiplay.zig b/src/test/guiplay.zig index 3063b97..29e3999 100644 --- a/src/test/guiplay.zig +++ b/src/test/guiplay.zig @@ -87,9 +87,10 @@ fn commReadThread(gpa: std.mem.Allocator, r: anytype, w: anytype) void { logger.err("comm.read: {any}", .{err}); continue; }; + defer msg.deinit(); - logger.debug("got ui msg tagged {s}", .{@tagName(msg)}); - switch (msg) { + logger.debug("got msg: {s}", .{@tagName(msg.value)}); + switch (msg.value) { .pong => { logger.info("received pong from ngui", .{}); }, @@ -131,18 +132,18 @@ fn commWriteThread(gpa: std.mem.Allocator, w: anytype) !void { while (true) { time.sleep(time.ns_per_s); - if (sectimer.read() < time.ns_per_s) { + if (sectimer.read() < 3 * time.ns_per_s) { continue; } - sectimer.reset(); + block_count += 1; const now = time.timestamp(); const btcrep: comm.Message.BitcoindReport = .{ .blocks = block_count, .headers = block_count, - .timestamp = @intCast(u64, now), + .timestamp = @intCast(now), .hash = "00000000000000000002bf8029f6be4e40b4a3e0e161b6a1044ddaf9eb126504", .ibd = false, .verifyprogress = 100, @@ -155,7 +156,7 @@ fn commWriteThread(gpa: std.mem.Allocator, w: anytype) !void { .mempool = .{ .loaded = true, .txcount = 100000 + block_count, - .usage = std.math.min(200123456 + block_count * 10, 300000000), + .usage = @min(200123456 + block_count * 10, 300000000), .max = 300000000, .totalfee = 2.23049932, .minfee = 0.00004155, @@ -168,7 +169,7 @@ fn commWriteThread(gpa: std.mem.Allocator, w: anytype) !void { pub fn main() !void { var gpa_state = std.heap.GeneralPurposeAllocator(.{}){}; - defer if (gpa_state.deinit()) { + defer if (gpa_state.deinit() == .leak) { logger.err("memory leaks detected", .{}); }; const gpa = gpa_state.allocator(); diff --git a/src/ui/bitcoin.zig b/src/ui/bitcoin.zig index dab529e..3ae53d5 100644 --- a/src/ui/bitcoin.zig +++ b/src/ui/bitcoin.zig @@ -106,10 +106,10 @@ pub fn updateTabPanel(rep: comm.Message.BitcoindReport) !void { if (rep.mempool.max == 0) { break :pct 0; } - const v = @intToFloat(f64, rep.mempool.usage) / @intToFloat(f64, rep.mempool.max); - break :pct @floatCast(f32, v * 100); + const v = @as(f64, @floatFromInt(rep.mempool.usage)) / @as(f64, @floatFromInt(rep.mempool.max)); + break :pct @floatCast(v * 100); }; - tab.mempool.usage_bar.setValue(@floatToInt(i32, @round(mempool_pct))); + tab.mempool.usage_bar.setValue(@as(i32, @intFromFloat(@round(mempool_pct)))); try tab.mempool.usage_lab.setTextFmt(&buf, "{:.1} " ++ cmark ++ "out of# {:.1} ({d:.1}%)", .{ fmt.fmtIntSizeBin(rep.mempool.usage), fmt.fmtIntSizeBin(rep.mempool.max), diff --git a/src/ui/lvgl.zig b/src/ui/lvgl.zig index da441ad..0c5cadb 100644 --- a/src/ui/lvgl.zig +++ b/src/ui/lvgl.zig @@ -203,7 +203,7 @@ pub const LvStyle = opaque { /// produces an int value suitable for lv_xxx functions. fn value(self: Selector) c.lv_style_selector_t { - return @enumToInt(self.part) | @enumToInt(self.state); + return @intFromEnum(self.part) | @intFromEnum(self.state); } }; }; @@ -249,11 +249,11 @@ const RGB16 = packed struct { /// rgb produces a Color value base on the red, green and blue components. pub inline fn rgb(r: u8, g: u8, b: u8) Color { const c16 = RGB16{ - .b = @truncate(u5, b >> 3), - .g = @truncate(u6, g >> 2), - .r = @truncate(u5, r >> 3), + .b = @truncate(b >> 3), + .g = @truncate(g >> 2), + .r = @truncate(r >> 3), }; - return @bitCast(Color, c16); + return @bitCast(c16); } /// black color @@ -295,19 +295,19 @@ pub const Palette = enum(c.lv_palette_t) { /// returns main color from the predefined palette. pub inline fn main(p: Palette) Color { - return lv_palette_main(@enumToInt(p)); + return lv_palette_main(@intFromEnum(p)); } /// makes the main color from the predefined palette lighter according to the /// specified level. pub inline fn lighten(p: Palette, l: ModLevel) Color { - return lv_palette_lighten(@enumToInt(p), @enumToInt(l)); + return lv_palette_lighten(@intFromEnum(p), @intFromEnum(l)); } /// makes the main color from the predefined palette darker according to the /// specified level. pub inline fn darken(p: Palette, l: ModLevel) Color { - return lv_palette_darken(@enumToInt(p), @enumToInt(l)); + return lv_palette_darken(@intFromEnum(p), @intFromEnum(l)); } }; @@ -326,16 +326,16 @@ pub const BaseObjMethods = struct { /// sets or clears an object flag. pub fn setFlag(self: anytype, v: LvObj.Flag) void { - lv_obj_add_flag(self.lvobj, @enumToInt(v)); + lv_obj_add_flag(self.lvobj, @intFromEnum(v)); } pub fn clearFlag(self: anytype, v: LvObj.Flag) void { - lv_obj_clear_flag(self.lvobj, @enumToInt(v)); + lv_obj_clear_flag(self.lvobj, @intFromEnum(v)); } /// reports whether the object has v flag set. pub fn hasFlag(self: anytype, v: LvObj.Flag) bool { - return lv_obj_has_flag(self.lvobj, @enumToInt(v)); + return lv_obj_has_flag(self.lvobj, @intFromEnum(v)); } /// returns a user data pointer associated with the object. @@ -409,7 +409,7 @@ pub const WidgetMethods = struct { /// aligns object position. the offset is relative to the specified alignment a. pub fn posAlign(self: anytype, a: PosAlign, xoffset: Coord, yoffset: Coord) void { - lv_obj_align(self.lvobj, @enumToInt(a), xoffset, yoffset); + lv_obj_align(self.lvobj, @intFromEnum(a), xoffset, yoffset); } /// sets flex layout growth property; same meaning as in CSS flex. @@ -548,24 +548,24 @@ pub const FlexLayout = struct { } fn adopt(obj: *LvObj, flow: Flow, opt: AlignOpt) FlexLayout { - lv_obj_set_flex_flow(obj, @enumToInt(flow)); + lv_obj_set_flex_flow(obj, @intFromEnum(flow)); if (opt.all) |a| { - const v = @enumToInt(a); + const v = @intFromEnum(a); lv_obj_set_flex_align(obj, v, v, v); } else { - lv_obj_set_flex_align(obj, @enumToInt(opt.main), @enumToInt(opt.cross), @enumToInt(opt.track)); + lv_obj_set_flex_align(obj, @intFromEnum(opt.main), @intFromEnum(opt.cross), @intFromEnum(opt.track)); } return .{ .lvobj = obj }; } /// sets flex layout flow on the object. pub fn setFlow(self: FlexLayout, ff: Flow) void { - lv_obj_set_flex_flow(self.lvobj, @enumToInt(ff)); + lv_obj_set_flex_flow(self.lvobj, @intFromEnum(ff)); } /// sets flex layout alignments. pub fn setAlign(self: FlexLayout, main: Align, cross: AlignCross, track: Align) void { - lv_obj_set_flex_align(self.lvobj, @enumToInt(main), @enumToInt(cross), @enumToInt(track)); + lv_obj_set_flex_align(self.lvobj, @intFromEnum(main), @intFromEnum(cross), @intFromEnum(track)); } /// same as setPad .column but using a default constant to make flex layouts consistent. @@ -645,10 +645,10 @@ pub const Label = struct { lv_label_set_text(lv_label, text); //lv_obj_set_height(lb, sizeContent); // default if (opt.long_mode) |m| { - lv_label_set_long_mode(lv_label, @enumToInt(m)); + lv_label_set_long_mode(lv_label, @intFromEnum(m)); } if (opt.pos) |p| { - lv_obj_align(lv_label, @enumToInt(p), 0, 0); + lv_obj_align(lv_label, @intFromEnum(p), 0, 0); } if (opt.recolor) { lv_label_set_recolor(lv_label, true); diff --git a/src/ui/widget.zig b/src/ui/widget.zig index cce766d..185f466 100644 --- a/src/ui/widget.zig +++ b/src/ui/widget.zig @@ -67,12 +67,12 @@ pub fn modal(title: [*:0]const u8, text: [*:0]const u8, btns: []const [*:0]const btncont.setHeightToContent(); // leave 5% as an extra spacing. - const btnwidth = lvgl.sizePercent(try std.math.divFloor(i16, 95, @truncate(u8, btns.len))); - for (btns) |btext, i| { + const btnwidth = lvgl.sizePercent(try std.math.divFloor(i16, 95, @as(u8, @truncate(btns.len)))); + for (btns, 0..) |btext, i| { const btn = try lvgl.TextButton.new(btncont, btext); btn.setFlag(.event_bubble); btn.setFlag(.user1); // .user1 indicates actionable button in callback - btn.setUserdata(@intToPtr(?*anyopaque, i)); // button index in callback + btn.setUserdata(@ptrFromInt(i)); // button index in callback btn.setWidth(btnwidth); if (i == 0) { btn.addStyle(lvgl.nm_style_btn_red(), .{}); @@ -88,9 +88,9 @@ export fn nm_modal_callback(e: *lvgl.LvEvent) void { return; } - const btn_index = @ptrToInt(target.userdata()); - const win = lvgl.Window{ .lvobj = @ptrCast(*lvgl.LvObj, edata) }; - const cb = @ptrCast(ModalButtonCallbackFn, @alignCast(@alignOf(ModalButtonCallbackFn), win.userdata())); + const btn_index = @intFromPtr(target.userdata()); + const win = lvgl.Window{ .lvobj = @ptrCast(edata) }; + const cb: ModalButtonCallbackFn = @alignCast(@ptrCast(win.userdata())); win.destroy(); cb(btn_index); } diff --git a/src/xfmt.zig b/src/xfmt.zig index c55562f..db89cf4 100644 --- a/src/xfmt.zig +++ b/src/xfmt.zig @@ -14,7 +14,7 @@ fn formatUnix(sec: u64, comptime fmt: []const u8, opts: std.fmt.FormatOptions, w if (sec > std.math.maxInt(u47)) { // EpochSeconds.getEpochDay trucates to u47 which results in a "truncated bits" // panic for too big numbers. so, just print raw digits. - return std.fmt.format(w, "{d}", .{sec}); + return std.fmt.formatInt(sec, 10, .lower, .{}, w); } const epoch: std.time.epoch.EpochSeconds = .{ .secs = sec }; const daysec = epoch.getDaySeconds(); diff --git a/tools/ci-containerfile b/tools/ci-containerfile index 036acaf..021587d 100644 --- a/tools/ci-containerfile +++ b/tools/ci-containerfile @@ -1,12 +1,12 @@ # ci container file for compiling and testing zig projects. # requires a ZIGURL build arg. for instance: -# podman build --rm -t ci-zig0.10.1 -f ci-containerfile \ -# --build-arg ZIGURL=https://ziglang.org/download/0.10.1/zig-linux-x86_64-0.10.1.tar.xz +# podman build --rm -t ci-zig0.11.0 -f ci-containerfile \ +# --build-arg ZIGURL=https://ziglang.org/download/0.11.0/zig-linux-x86_64-0.11.0.tar.xz -FROM alpine:3.17.1 +FROM alpine:3.18.3 ARG ZIGURL -RUN apk add --no-cache git curl xz sdl2-dev clang15-extra-tools && \ +RUN apk add --no-cache git curl xz libc-dev sdl2-dev clang16-extra-tools && \ mkdir -p /tools/zig && \ cd /tools/zig && \ curl -o zig.tar.xz $ZIGURL && \