aws-sdk-for-zig/VersionStep.zig
Emil Lerch 2793e1e17b
Some checks failed
continuous-integration/drone/push Build is failing
drone support for versionstep
2022-01-12 17:42:54 -08:00

229 lines
6.9 KiB
Zig

//! Publish Date: 2022-01-12
//! This file is hosted at ??? and is meant to be copied
//! to projects that use it. Sample usage:
//!
//! const version = VersionStep.create(b, null);
//! exe.step.dependOn(&version.step);
const std = @import("std");
const Step = @This();
step: std.build.Step,
builder: *std.build.Builder,
version_path: []const u8,
// Creates a step that will add the git version info in a file in src/
// so it can be consumed by additional code. If version_path is not specified,
// it will default to "git_version.zig". This should be part of .gitignore
pub fn create(b: *std.build.Builder, version_path: ?[]const u8) *Step {
var result = b.allocator.create(Step) catch @panic("memory");
result.* = Step{
.step = std.build.Step.init(.custom, "create version file", b.allocator, make),
.builder = b,
.version_path = std.fs.path.resolve(b.allocator, &[_][]const u8{
b.build_root,
"src",
version_path orelse "git_version.zig",
}) catch @panic("memory"),
};
return result;
}
fn make(step: *std.build.Step) !void {
const self = @fieldParentPtr(Step, "step", step);
const file = try std.fs.createFileAbsolute(self.version_path, .{});
defer file.close();
const version = try getGitVersion(
self.builder.allocator,
self.builder.build_root,
self.builder.env_map,
);
defer version.deinit();
try file.writer().print(
\\pub const hash = "{s}";
\\pub const abbreviated_hash = "{s}";
\\pub const commit_date = "{s}";
\\pub const branch = "{s}";
\\pub const dirty = {b};
\\pub const pretty_version = "{s}";
, .{
version.hash,
version.abbreviated_hash,
version.commit_date,
version.branch,
version.dirty,
version.pretty_version,
});
}
const GitVersion = struct {
hash: []const u8,
abbreviated_hash: []const u8,
commit_date: []const u8,
branch: []const u8,
dirty: bool,
pretty_version: []const u8,
allocator: std.mem.Allocator,
const Self = @This();
pub fn deinit(self: Self) void {
self.allocator.free(self.hash);
self.allocator.free(self.abbreviated_hash);
self.allocator.free(self.commit_date);
self.allocator.free(self.branch);
self.allocator.free(self.pretty_version);
}
};
fn getGitVersion(allocator: std.mem.Allocator, git_working_root: ?[]const u8, env: anytype) !GitVersion {
// git log -1 --pretty="%H%n%h%n%ci%n%D"
// 3bf6adc13e4aa653a7b75b1b5e9c9db5215df8e1
// 3bf6adc
// 2022-01-12 12:21:28 -0800
// HEAD -> zig-native
if (std.os.getenv("DRONE_COMMIT_SHA") != null)
return getGitVersionFromDrone(allocator);
const log_output = try run(
allocator,
&[_][]const u8{
"git",
"log",
"-1",
"--pretty=%H%n%h%n%ci%n%D",
},
git_working_root,
env,
);
defer allocator.free(log_output);
const line_data = try getLines(allocator, 4, log_output);
const hash = line_data[0];
const abbrev_hash = line_data[1];
const date = line_data[2];
const branch = line_data[3];
// git status --porcelain
const status_output = try run(
allocator,
&[_][]const u8{
"git",
"status",
"--porcelain",
},
git_working_root,
env,
);
const dirty = blk: {
if (status_output.len > 0) {
allocator.free(status_output);
break :blk true;
}
break :blk false;
};
const dirty_str = blk: {
if (dirty) {
break :blk " (dirty)";
}
break :blk "";
};
return GitVersion{
.hash = hash,
.abbreviated_hash = abbrev_hash,
.commit_date = date,
.branch = branch,
.allocator = allocator,
.dirty = dirty,
.pretty_version = try prettyVersion(allocator, abbrev_hash, date, dirty_str),
};
}
fn prettyVersion(allocator: std.mem.Allocator, abbrev_hash: []const u8, date: []const u8, dirty_str: []const u8) ![]const u8 {
const pretty_version: []const u8 = try std.fmt.allocPrint(
allocator,
"version {s}, committed at {s}{s}",
.{
abbrev_hash,
date,
dirty_str,
},
);
return pretty_version;
}
fn getGitVersionFromDrone(allocator: std.mem.Allocator) !GitVersion {
const abbrev_hash = std.os.getenv("DRONE_COMMIT_SHA").?[0..7]; // This isn't quite how git works, but ok
const date = std.os.getenv("DRONE_BUILD_STARTED").?; // this is a timestamp :(
return GitVersion{
.hash = std.os.getenv("DRONE_COMMIT_SHA").?,
.abbreviated_hash = abbrev_hash,
.commit_date = date,
.branch = std.os.getenv("DRONE_COMMIT_BRANCH").?,
.allocator = allocator,
.dirty = false,
.pretty_version = try prettyVersion(allocator, abbrev_hash, date, ""),
};
}
fn getLines(allocator: std.mem.Allocator, comptime line_count: u32, data: []const u8) ![line_count][]u8 {
var line: u32 = 0;
var start: u32 = 0;
var current: u32 = 0;
var line_data: [line_count][]u8 = undefined;
errdefer {
while (line > 0) {
allocator.free(line_data[line]);
line -= 1;
}
}
for (data) |c| {
// try std.io.getStdErr().writer().print("line: {d}, c: {c}, cur: {d}, strt: {d}\n", .{ line, c, current, start });
if (c == '\n') {
line_data[line] = try allocator.dupe(u8, data[start..current]);
// try std.io.getStdErr().writer().print("c: {d}, s: {d}, data: '{s}'\n", .{ current, start, line_data[line] });
start = current + 1;
line += 1;
}
current += 1;
}
return line_data;
}
// env is a std.process.BufMap, but that's private, which is a little weird tbh
fn run(allocator: std.mem.Allocator, argv: []const []const u8, cwd: ?[]const u8, env: anytype) ![]const u8 {
{
var msg = std.ArrayList(u8).init(allocator);
defer msg.deinit();
const writer = msg.writer();
var prefix: []const u8 = "";
for (argv) |arg| {
try writer.print("{s}\"{s}\"", .{ prefix, arg });
prefix = " ";
}
std.log.info("[RUN] {s}", .{msg.items});
}
const result = try std.ChildProcess.exec(.{
.allocator = allocator,
.argv = argv,
.cwd = cwd,
.env_map = env,
});
defer if (result.stderr.len > 0) allocator.free(result.stderr);
try std.io.getStdErr().writer().writeAll(result.stderr);
switch (result.term) {
.Exited => |code| if (code != 0) {
std.log.err("process failed with exit code: {}", .{code});
std.os.exit(0xff);
},
else => {
std.log.err("process failed due to exception: {}", .{result});
std.os.exit(0xff);
},
}
return result.stdout;
}