build: add semantic versioning support
both nd and ngui now acquire semantic versioning recorded at the build time. they also report the version at startup and -v flag. this is useful for a release process and to avoid potential compatibility issues in the future. in a regular build flow, the version is taken from a git tag using the following command: git -C . describe --match 'v*.*.*' --tags --abbrev=8 in a non-standard scenario where git isn't available, the version can be provided on the command line during build like so: zig build -Dversion=1.2.3 if both git and command line supplied versions are available, they must match.
parent
53812cbd37
commit
1b28648d68
|
@ -1,18 +1,25 @@
|
|||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
# https://woodpecker-ci.org/plugins/Git%20Clone
|
||||
settings:
|
||||
# tags are required for aarch64 release builds for semver
|
||||
tags: true
|
||||
pipeline:
|
||||
lint:
|
||||
image: git.qcode.ch/nakamochi/ci-zig0.10.1:v2
|
||||
image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3
|
||||
commands:
|
||||
- ./tools/fmt-check.sh
|
||||
test:
|
||||
image: git.qcode.ch/nakamochi/ci-zig0.10.1:v2
|
||||
image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3
|
||||
commands:
|
||||
- zig build test
|
||||
sdl2:
|
||||
image: git.qcode.ch/nakamochi/ci-zig0.10.1:v2
|
||||
image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3
|
||||
commands:
|
||||
- zig build -Ddriver=sdl2
|
||||
aarch64:
|
||||
image: git.qcode.ch/nakamochi/ci-zig0.10.1:v2
|
||||
image: git.qcode.ch/nakamochi/ci-zig0.10.1:v3
|
||||
commands:
|
||||
- zig build -Ddriver=fbev -Dtarget=aarch64-linux-musl -Drelease-safe -Dstrip
|
||||
- sha256sum zig-out/bin/nd zig-out/bin/ngui
|
||||
|
|
76
build.zig
76
build.zig
|
@ -10,9 +10,11 @@ pub fn build(b: *std.build.Builder) void {
|
|||
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 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);
|
||||
|
||||
const common_cflags = .{
|
||||
"-Wall",
|
||||
|
@ -28,6 +30,7 @@ pub fn build(b: *std.build.Builder) void {
|
|||
ngui.setBuildMode(mode);
|
||||
ngui.pie = true;
|
||||
ngui.strip = strip;
|
||||
ngui.step.dependOn(semver_step);
|
||||
|
||||
ngui.addPackage(buildopts.getPackage("build_options"));
|
||||
ngui.addIncludePath("lib");
|
||||
|
@ -88,6 +91,7 @@ pub fn build(b: *std.build.Builder) void {
|
|||
nd.setBuildMode(mode);
|
||||
nd.pie = true;
|
||||
nd.strip = strip;
|
||||
nd.step.dependOn(semver_step);
|
||||
|
||||
nd.addPackage(buildopts.getPackage("build_options"));
|
||||
nifbuild.addPkg(b, nd, "lib/nif");
|
||||
|
@ -316,3 +320,75 @@ const LVGLLogLevel = enum {
|
|||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// VersionStep injects a release build semantic version into buildopts as "semver".
|
||||
/// the make step fails if the inver input version and the one found in a git tag mismatch.
|
||||
///
|
||||
/// while git-tagged versions are expected to be in v<semver>format, input version
|
||||
/// to match against is any format supported by std.SemanticVersion.parse.
|
||||
/// input version is optional; if unset, make fn succeeds given a correctly formatted
|
||||
/// 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
|
||||
|
||||
b: *std.build.Builder,
|
||||
step: std.build.Step,
|
||||
|
||||
fn create(b: *std.build.Builder, o: *std.build.OptionsStep, 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),
|
||||
};
|
||||
return &vstep.step;
|
||||
}
|
||||
|
||||
fn make(step: *std.build.Step) anyerror!void {
|
||||
const self = @fieldParentPtr(VersionStep, "step", step);
|
||||
const semver = try self.eval();
|
||||
std.log.info("build version: {any}", .{semver});
|
||||
self.buildopts.addOption(std.SemanticVersion, "semver", semver);
|
||||
}
|
||||
|
||||
fn eval(self: *VersionStep) !std.SemanticVersion {
|
||||
const repover = try self.gitver();
|
||||
if (self.inver) |v| {
|
||||
const insem = std.SemanticVersion.parse(v) catch |err| {
|
||||
std.log.err("invalid input semver '{s}': {any}", .{ v, err });
|
||||
return err;
|
||||
};
|
||||
if (repover != null and insem.order(repover.?) != .eq) {
|
||||
std.log.err("input and repo semver mismatch: {any} vs {any}", .{ insem, repover });
|
||||
return error.VersionMismatch;
|
||||
}
|
||||
return insem;
|
||||
}
|
||||
|
||||
if (repover == null) {
|
||||
std.log.err("must supply build semver from command line.", .{});
|
||||
return error.MissingVersion;
|
||||
}
|
||||
return repover.?;
|
||||
}
|
||||
|
||||
fn gitver(self: *VersionStep) !?std.SemanticVersion {
|
||||
if (!std.process.can_spawn) {
|
||||
return null;
|
||||
}
|
||||
const git = self.b.findProgram(&[_][]const u8{"git"}, &[_][]const u8{}) catch return null;
|
||||
|
||||
const prefix = "v"; // git tag prefix
|
||||
const matchTag = self.b.fmt("{s}*.*.*", .{prefix});
|
||||
const cmd = [_][]const u8{ git, "-C", self.b.pathFromRoot("."), "describe", "--match", matchTag, "--tags", "--abbrev=8" };
|
||||
var code: u8 = undefined;
|
||||
const git_describe = self.b.execAllowFail(&cmd, &code, .Ignore) catch return null;
|
||||
const repotag = std.mem.trim(u8, git_describe, " \n\r")[prefix.len..];
|
||||
return std.SemanticVersion.parse(repotag) catch |err| ret: {
|
||||
std.log.err("unparsable git tag semver '{s}': {any}", .{ repotag, err });
|
||||
break :ret err;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const buildopts = @import("build_options");
|
||||
const std = @import("std");
|
||||
const os = std.os;
|
||||
const sys = os.system;
|
||||
|
@ -86,6 +87,9 @@ fn parseArgs(gpa: std.mem.Allocator) !NdArgs {
|
|||
if (std.mem.eql(u8, a, "-h") or std.mem.eql(u8, a, "-help") or std.mem.eql(u8, a, "--help")) {
|
||||
usage(prog) catch {};
|
||||
std.process.exit(1);
|
||||
} else if (std.mem.eql(u8, a, "-v")) {
|
||||
try stderr.print("{any}\n", .{buildopts.semver});
|
||||
std.process.exit(0);
|
||||
} else if (std.mem.eql(u8, a, "-gui")) {
|
||||
lastarg = .gui;
|
||||
} else if (std.mem.eql(u8, a, "-gui-user")) {
|
||||
|
@ -126,6 +130,7 @@ pub fn main() !void {
|
|||
// parse program args first thing and fail fast if invalid
|
||||
const args = try parseArgs(gpa);
|
||||
defer args.deinit(gpa);
|
||||
logger.info("ndg version {any}", .{buildopts.semver});
|
||||
|
||||
// reset the screen backlight to normal power regardless
|
||||
// of its previous state.
|
||||
|
|
51
src/ngui.zig
51
src/ngui.zig
|
@ -1,3 +1,4 @@
|
|||
const buildopts = @import("build_options");
|
||||
const std = @import("std");
|
||||
const time = std.time;
|
||||
|
||||
|
@ -16,6 +17,7 @@ pub const keep_sigpipe = true;
|
|||
|
||||
const stdin = std.io.getStdIn().reader();
|
||||
const stdout = std.io.getStdOut().writer();
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
const logger = std.log.scoped(.ngui);
|
||||
|
||||
extern "c" fn ui_update_network_status(text: [*:0]const u8, wifi_list: ?[*:0]const u8) void;
|
||||
|
@ -177,18 +179,59 @@ fn commThreadLoopCycle() !void {
|
|||
}
|
||||
}
|
||||
|
||||
/// prints messages in the same way std.fmt.format does and exits the process
|
||||
/// with a non-zero code.
|
||||
fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
|
||||
stderr.print(fmt, args) catch {};
|
||||
if (fmt[fmt.len - 1] != '\n') {
|
||||
stderr.writeByte('\n') catch {};
|
||||
}
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
fn parseArgs(alloc: std.mem.Allocator) !void {
|
||||
var args = try std.process.ArgIterator.initWithAllocator(alloc);
|
||||
defer args.deinit();
|
||||
const prog = args.next() orelse return error.NoProgName;
|
||||
|
||||
while (args.next()) |a| {
|
||||
if (std.mem.eql(u8, a, "-h") or std.mem.eql(u8, a, "-help") or std.mem.eql(u8, a, "--help")) {
|
||||
usage(prog) catch {};
|
||||
std.process.exit(1);
|
||||
} else if (std.mem.eql(u8, a, "-v")) {
|
||||
try stderr.print("{any}\n", .{buildopts.semver});
|
||||
std.process.exit(0);
|
||||
} else {
|
||||
fatal("unknown arg name {s}", .{a});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// prints usage help text to stderr.
|
||||
fn usage(prog: []const u8) !void {
|
||||
try stderr.print(
|
||||
\\usage: {s} [-v]
|
||||
\\
|
||||
\\ngui is nakamochi GUI interface. it communicates with nd, nakamochi daemon,
|
||||
\\via stdio and is typically launched by the daemon as a child process.
|
||||
\\
|
||||
, .{prog});
|
||||
}
|
||||
|
||||
/// nakamochi UI program entry point.
|
||||
pub fn main() anyerror!void {
|
||||
// ensure timer is available on this platform before doing anything else;
|
||||
// the UI is unusable otherwise.
|
||||
tick_timer = try time.Timer.start();
|
||||
|
||||
// main heap allocator used through the lifetime of nd
|
||||
var gpa_state = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer if (gpa_state.deinit()) {
|
||||
logger.err("memory leaks detected", .{});
|
||||
};
|
||||
gpa = gpa_state.allocator();
|
||||
try parseArgs(gpa);
|
||||
logger.info("ndg version {any}", .{buildopts.semver});
|
||||
|
||||
// ensure timer is available on this platform before doing anything else;
|
||||
// the UI is unusable otherwise.
|
||||
tick_timer = try time.Timer.start();
|
||||
|
||||
// initalizes display, input driver and finally creates the user interface.
|
||||
ui.init() catch |err| {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
FROM alpine:3.17.1
|
||||
|
||||
ARG ZIGURL
|
||||
RUN apk add --no-cache curl xz sdl2-dev clang15-extra-tools && \
|
||||
RUN apk add --no-cache git curl xz sdl2-dev clang15-extra-tools && \
|
||||
mkdir -p /tools/zig && \
|
||||
cd /tools/zig && \
|
||||
curl -o zig.tar.xz $ZIGURL && \
|
||||
|
|
Reference in New Issue