use git version in metno identifier
This commit is contained in:
parent
ad93634100
commit
b2cb5d537f
3 changed files with 171 additions and 2 deletions
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const GitVersion = @import("build/GitVersion.zig");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
|
@ -53,6 +54,10 @@ pub fn build(b: *std.Build) void {
|
|||
|
||||
maxminddb.installHeadersDirectory(maxminddb_upstream.path("include"), "", .{});
|
||||
|
||||
const version = GitVersion.getVersion(b, .{});
|
||||
const build_options = b.addOptions();
|
||||
build_options.addOption([]const u8, "version", version);
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "wttr",
|
||||
.root_module = b.createModule(.{
|
||||
|
|
@ -67,6 +72,7 @@ pub fn build(b: *std.Build) void {
|
|||
exe.root_module.addAnonymousImport("airports.dat", .{
|
||||
.root_source_file = openflights.path("data/airports.dat"),
|
||||
});
|
||||
exe.root_module.addOptions("build_options", build_options);
|
||||
exe.root_module.addIncludePath(maxminddb_upstream.path("include"));
|
||||
exe.root_module.addConfigHeader(maxminddb_config);
|
||||
exe.linkLibrary(maxminddb);
|
||||
|
|
@ -87,6 +93,7 @@ pub fn build(b: *std.Build) void {
|
|||
|
||||
const test_options = b.addOptions();
|
||||
test_options.addOption(bool, "download_geoip", download_geoip);
|
||||
test_options.addOption([]const u8, "version", version);
|
||||
|
||||
const tests = b.addTest(.{
|
||||
.root_module = b.createModule(.{
|
||||
|
|
|
|||
161
build/GitVersion.zig
Normal file
161
build/GitVersion.zig
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
const std = @import("std");
|
||||
const Build = std.Build;
|
||||
|
||||
pub const Options = struct {
|
||||
/// Length of the short hash (default: 7, git's default)
|
||||
hash_length: u6 = 7,
|
||||
/// String to append when working tree is dirty
|
||||
dirty_flag: []const u8 = "*",
|
||||
};
|
||||
|
||||
/// Get git version information by reading .git files directly
|
||||
pub fn getVersion(b: *Build, options: Options) []const u8 {
|
||||
const allocator = b.allocator;
|
||||
|
||||
// Find build root by looking for build.zig
|
||||
const build_root = findBuildRoot(allocator) catch return "unknown";
|
||||
defer allocator.free(build_root);
|
||||
|
||||
// Read .git/HEAD relative to build root
|
||||
const head_path = std.fmt.allocPrint(allocator, "{s}/.git/HEAD", .{build_root}) catch return "unknown";
|
||||
defer allocator.free(head_path);
|
||||
|
||||
const head_data = std.fs.cwd().readFileAlloc(allocator, head_path, 1024) catch {
|
||||
return "not under version control";
|
||||
};
|
||||
defer allocator.free(head_data);
|
||||
|
||||
const head_trimmed = std.mem.trim(u8, head_data, &std.ascii.whitespace);
|
||||
|
||||
// Parse HEAD - either "ref: refs/heads/branch" or direct hash
|
||||
const hash_owned = if (std.mem.startsWith(u8, head_trimmed, "ref: ")) blk: {
|
||||
const ref_path_rel = std.mem.trimLeft(u8, head_trimmed[5..], &std.ascii.whitespace);
|
||||
const ref_file = std.fmt.allocPrint(allocator, "{s}/.git/{s}", .{ build_root, ref_path_rel }) catch return "unknown";
|
||||
defer allocator.free(ref_file);
|
||||
|
||||
const ref_fd = std.fs.openFileAbsolute(ref_file, .{}) catch return "unknown";
|
||||
defer ref_fd.close();
|
||||
|
||||
var ref_buf: [1024]u8 = undefined;
|
||||
const bytes_read = ref_fd.readAll(&ref_buf) catch return "unknown";
|
||||
const ref_data = ref_buf[0..bytes_read];
|
||||
|
||||
const ref_trimmed = std.mem.trim(u8, ref_data, &std.ascii.whitespace);
|
||||
break :blk allocator.dupe(u8, ref_trimmed) catch return "unknown";
|
||||
} else allocator.dupe(u8, head_trimmed) catch return "unknown";
|
||||
defer allocator.free(hash_owned);
|
||||
|
||||
// Truncate to short hash
|
||||
const short_hash = if (hash_owned.len > options.hash_length)
|
||||
hash_owned[0..options.hash_length]
|
||||
else
|
||||
hash_owned;
|
||||
|
||||
// Check if dirty using simple heuristic:
|
||||
// If any .zig files are newer than .git/index, mark as dirty
|
||||
const is_dirty = isDirty(allocator, build_root) catch return "unknown";
|
||||
|
||||
if (is_dirty) {
|
||||
return std.fmt.allocPrint(allocator, "{s}{s}", .{ short_hash, options.dirty_flag }) catch return "unknown";
|
||||
}
|
||||
|
||||
return allocator.dupe(u8, short_hash) catch return "unknown";
|
||||
}
|
||||
|
||||
fn findBuildRoot(allocator: std.mem.Allocator) ![]const u8 {
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
const start_cwd = try std.fs.cwd().realpath(".", &buf);
|
||||
var cwd: []const u8 = start_cwd;
|
||||
|
||||
while (true) {
|
||||
// Check if build.zig exists in current directory
|
||||
var dir = std.fs.openDirAbsolute(cwd, .{}) catch break;
|
||||
defer dir.close();
|
||||
|
||||
dir.access("build.zig", .{}) catch {
|
||||
// build.zig not found, try parent
|
||||
const parent = std.fs.path.dirname(cwd) orelse break;
|
||||
if (std.mem.eql(u8, parent, cwd)) break; // Reached root
|
||||
cwd = parent;
|
||||
continue;
|
||||
};
|
||||
|
||||
return allocator.dupe(u8, cwd);
|
||||
}
|
||||
|
||||
return error.BuildRootNotFound;
|
||||
}
|
||||
|
||||
fn isDirty(allocator: std.mem.Allocator, build_root: []const u8) !bool {
|
||||
// Get .git/index mtime
|
||||
const index_path = try std.fs.path.join(allocator, &[_][]const u8{ build_root, ".git", "index" });
|
||||
defer allocator.free(index_path);
|
||||
|
||||
const index_stat = std.fs.cwd().statFile(index_path) catch return error.CannotDetermineDirty;
|
||||
const index_mtime = index_stat.mtime;
|
||||
|
||||
// Read .gitignore
|
||||
const ignore_path = try std.fs.path.join(allocator, &[_][]const u8{ build_root, ".gitignore" });
|
||||
defer allocator.free(ignore_path);
|
||||
const ignore_data = std.fs.cwd().readFileAlloc(allocator, ignore_path, 1024 * 1024) catch
|
||||
try allocator.dupe(u8, "");
|
||||
defer allocator.free(ignore_data);
|
||||
|
||||
// Walk source files in build root and check if any are newer
|
||||
var dir = std.fs.openDirAbsolute(build_root, .{ .iterate = true }) catch return error.CannotDetermineDirty;
|
||||
defer dir.close();
|
||||
|
||||
var walker = dir.walk(allocator) catch return error.CannotDetermineDirty;
|
||||
defer walker.deinit();
|
||||
|
||||
while (walker.next() catch return error.CannotDetermineDirty) |entry| {
|
||||
if (entry.kind != .file) continue;
|
||||
|
||||
// Always ignore .git/
|
||||
if (std.mem.startsWith(u8, entry.path, ".git/")) continue;
|
||||
|
||||
// Check if path matches any ignore pattern
|
||||
var lines = std.mem.splitScalar(u8, ignore_data, '\n');
|
||||
var ignored = false;
|
||||
while (lines.next()) |line| {
|
||||
const trimmed = std.mem.trim(u8, line, &std.ascii.whitespace);
|
||||
if (trimmed.len == 0) continue;
|
||||
if (trimmed[0] == '#') continue;
|
||||
|
||||
if (isIgnored(entry.path, trimmed)) {
|
||||
ignored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ignored) continue;
|
||||
|
||||
const stat = entry.dir.statFile(entry.basename) catch continue;
|
||||
if (stat.mtime > index_mtime) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Handles checking a gitignore style pattern to determine if a path should
|
||||
/// be ignored. Mostly, globs are not handled, but an extension-like pattern
|
||||
/// is common and recognized by this function, so things like '*.swp' will work
|
||||
/// as expected
|
||||
fn isIgnored(path: []const u8, pattern: []const u8) bool {
|
||||
// TODO: Handle other glob patterns (?, [], *prefix, suffix*)
|
||||
// Handle *.extension pattern
|
||||
if (std.mem.startsWith(u8, pattern, "*."))
|
||||
return std.mem.endsWith(u8, path, pattern[1..]);
|
||||
|
||||
// Pattern ending with / matches directory prefix
|
||||
if (std.mem.endsWith(u8, pattern, "/"))
|
||||
return std.mem.startsWith(u8, path, pattern);
|
||||
|
||||
// Pattern starting with / matches from root
|
||||
if (std.mem.startsWith(u8, pattern, "/"))
|
||||
return std.mem.eql(u8, path, pattern[1..]);
|
||||
|
||||
// Otherwise match as substring (file/dir name anywhere in path)
|
||||
return std.mem.indexOf(u8, path, pattern) != null;
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
const WeatherProvider = @import("Provider.zig");
|
||||
const Coordinates = @import("../Coordinates.zig");
|
||||
const types = @import("types.zig");
|
||||
|
|
@ -119,8 +120,8 @@ fn fetchRaw(ptr: *anyopaque, allocator: std.mem.Allocator, coords: Coordinates)
|
|||
|
||||
const user_agent = try std.fmt.allocPrint(
|
||||
self.allocator,
|
||||
"wttr/1.0 git.lerch.org/lobo/wttr {s}",
|
||||
.{self.identifying_email},
|
||||
"wttr/{s} git.lerch.org/lobo/wttr {s}",
|
||||
.{ build_options.version, self.identifying_email },
|
||||
);
|
||||
defer self.allocator.free(user_agent);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue