diff --git a/build.zig b/build.zig index 67b1bca..e8a6c44 100644 --- a/build.zig +++ b/build.zig @@ -1,11 +1,16 @@ const std = @import("std"); -// TODO TODO +// Must match the version in build.zig.zon +const totp_version: std.SemanticVersion = .{ .major = 0, .minor = 1, .patch = 0, .pre = "dev" }; // Although this function looks imperative, note that its job is to // declaratively construct a build graph that will be executed by an external // runner. pub fn build(b: *std.Build) void { + const version = getVersion(b); + const options = b.addOptions(); + options.addOption(std.SemanticVersion, "version", version); + // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options @@ -37,6 +42,7 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, }); + exe.root_module.addOptions("config", options); // This declares intent for the executable to be installed into the // standard location when the user invokes the "install" step (the default @@ -91,3 +97,51 @@ pub fn build(b: *std.Build) void { test_step.dependOn(&run_lib_unit_tests.step); test_step.dependOn(&run_exe_unit_tests.step); } + +fn getVersion(b: *std.Build) std.SemanticVersion { + // if this is a release version , aka this is not a pre-release or build version, + // then use the specified version + if (totp_version.pre == null and totp_version.build == null) return totp_version; + + // for pre-release version we use the git version + const args: []const []const u8 = &.{ "git", "-C", b.pathFromRoot("."), "describe", "--match", "*.*.*", "--tags" }; + var out_code: u8 = undefined; + const output_untrimmed = b.runAllowFail(args, &out_code, .Ignore) catch |err| { + std.log.warn( + \\ failed to run git describe: {} + , .{err}); + return totp_version; + }; + + const output_trimmed = std.mem.trim(u8, output_untrimmed, " \r\n"); + switch (std.mem.count(u8, output_trimmed, "-")) { + 0 => { + // release version, e.g. 1.0.0 + if (!std.mem.eql(u8, output_trimmed, b.fmt("{}", .{totp_version}))) { + std.debug.panic("the version in build.zig and build.zig.zon must match the tag in git", .{}); + } + return totp_version; + }, + 2 => { + // development version, e.g. 1.0.0-7-64es356 + var iter = std.mem.splitScalar(u8, output_trimmed, '-'); + const tag = iter.first(); + const commits_since_tag = iter.next().?; + const commit_hash = iter.next().?; + + const v: std.SemanticVersion = std.SemanticVersion.parse(tag) catch unreachable; + + return .{ + .major = v.major, + .minor = v.minor, + .patch = v.patch, + .pre = b.fmt("dev.{s}", .{commits_since_tag}), + .build = commit_hash[1..], + }; + }, + else => { + std.debug.print("unexpected output of git describe: '{s}'\n", .{output_untrimmed}); + std.process.exit(1); + }, + } +} diff --git a/build.zig.zon b/build.zig.zon index da98033..c16b3c3 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,7 +2,7 @@ .name = "zig-totp", // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. - .version = "0.0.0", + .version = "0.1.0-dev", // This field is optional. // This is currently advisory only; Zig does not yet do anything diff --git a/src/main.zig b/src/main.zig index 0130e20..3f093c0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -10,6 +10,7 @@ const expect = testing.expect; const ArrayList = std.ArrayList; const Base32 = @import("base32.zig").Base32; const Base32Error = @import("base32.zig").Base32Error; +const options = @import("config"); // otpauth://totp/AWS+Dev?secret=47STA47VFCMMLLWOLHWO3KY7MYNC36MLCDTHOLIYKJCTTSSAMKVM7YA3VWT2AJEP&digits=6&icon=Amazon // otpauth://totp/Gitea%20%28git.lucares.de%29:andi?algorithm=SHA1&digits=6&issuer=Gitea&period=30&secret=MSP53Q672UJMSCLQVRCJKMMZKK7MWSMYFL77OQ24JBM65RZWY7F2Y45FMTNLYM36 @@ -49,7 +50,9 @@ fn mainInternal() !void { const arg: Args = try parseArgs(allocator, args); //print("parsed Args: {?any}\n", arg); - if (arg.list) { + if (arg.print_version) { + try std.io.getStdOut().writer().print("{}", .{options.version}); + } else if (arg.list) { const names = try executeGetList(allocator, arg.config_location); for (0..names.items.len) |i| { @@ -85,12 +88,13 @@ fn printHelp() void { \\ eval "$(zig-totp --bash)" \\ to your ~/.bashrc \\ --config [path] The path to a config file (default is $XDG_CONFIG_HOME/zig-totp or $HOME/.zig-totp). - \\ + \\ --version Print the version number + \\ ; std.debug.print("{s}", .{msg}); } -const Args = struct { list: bool, show: ?[]const u8, config_location: []const u8, validateAuthenticator: ?[]const u8, validateCode: ?[]const u8 }; +const Args = struct { list: bool, show: ?[]const u8, config_location: []const u8, validateAuthenticator: ?[]const u8, validateCode: ?[]const u8, print_version: bool }; const OtpAuthUrl = struct { name: []const u8, secretEncoded: []const u8, url: []const u8, period: u32, digits: u4, algorithm: []const u8 }; @@ -186,7 +190,7 @@ fn zeroPad(allocator: Allocator, digits: u4, x: anytype) ![]u8 { const ArgumentError = error{ InvalidOtpAuthUrl, UnknownParameter, MissingConfigLocation, AuthenticatorNotFound, MissingAuthenticatorParam, FailedToOpenConfigFile, TooManyParsinErrors }; fn parseArgs(allocator: Allocator, args: []const []const u8) !Args { - var result = Args{ .list = false, .show = null, .config_location = try configLocation(allocator), .validateAuthenticator = null, .validateCode = null }; + var result = Args{ .list = false, .show = null, .config_location = try configLocation(allocator), .validateAuthenticator = null, .validateCode = null, .print_version = false }; var i: u17 = 1; while (i < args.len) : (i += 1) { const arg = args[i]; @@ -220,6 +224,8 @@ fn parseArgs(allocator: Allocator, args: []const []const u8) !Args { return error.MissingConfigLocation; } result.config_location = args[i]; + } else if (eql(u8, "--version", arg)) { + result.print_version = true; } else if (eql(u8, "list", arg)) { result.list = true; } else if (eql(u8, "show", arg)) {