initial sync branch implementation

This commit is contained in:
Jonathan Marler
2026-02-08 05:33:32 -07:00
parent d03bf26fc8
commit cf8547ae4a
6 changed files with 418 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
.zig-cache/
zig-out/
/generate-branch/
+164
View File
@@ -0,0 +1,164 @@
const std = @import("std");
const Build = std.Build;
const version = "25.0.28-preview";
pub fn build(b: *Build) !void {
const sync_step = b.step("sync", "Sync the generate/main branches");
b.default_step = sync_step;
const nofetch = b.option(bool, "nofetch", "disable fetching the generate branch on every build") orelse false;
const desc_line_prefix = [_]u8{' '} ** 31;
const sha_file = blk: {
const exe = b.addExecutable(.{
.name = "fetchgenerate",
.root_module = b.createModule(.{
.root_source_file = b.path("src/fetchgenerate.zig"),
.target = b.graph.host,
}),
});
const run = b.addRunArtifact(exe);
run.has_side_effects = if (nofetch) false else true;
run.addDirectoryArg(b.path("generate-branch"));
const sha_file = run.addOutputFileArg("generate-sha");
b.step("fetchgenerate", ("Fetches the generate branch in 'generate-branch'\n" ++ desc_line_prefix ++
"and stores its sha in a file for other\n" ++ desc_line_prefix ++
"steps to use as an input.")).dependOn(&run.step);
break :blk sha_file;
};
{
const exe = b.addExecutable(.{
.name = "sync",
.root_module = b.createModule(.{
.root_source_file = b.path("src/sync.zig"),
.target = b.graph.host,
}),
});
const run = b.addRunArtifact(exe);
run.addFileArg(sha_file);
sync_step.dependOn(&run.step);
}
// TODO: default step clones/updates a copy of the "generate" branch
// a copy of the repo
// into a subdirectory (work/
// const metadata_nupkg_file = blk: {
// const download_winmd_nupkg = b.addSystemCommand(&.{
// "curl",
// "https://www.nuget.org/api/v2/package/Microsoft.Windows.SDK.Win32Metadata/" ++ version,
// "--location",
// "--output",
// });
// break :blk download_winmd_nupkg.addOutputFileArg("win32metadata.nupkg");
// };
// const metadata_nupkg_out = blk: {
// const zipcmdline = b.dependency("zipcmdline", .{
// .target = b.graph.host,
// });
// const unzip_exe = zipcmdline.artifact("unzip");
// const unzip_metadata = b.addRunArtifact(unzip_exe);
// unzip_metadata.addFileArg(metadata_nupkg_file);
// unzip_metadata.addArg("-d");
// break :blk unzip_metadata.addOutputDirectoryArg("nupkg");
// };
// const winmd = metadata_nupkg_out.path(b, "Windows.Win32.winmd");
// b.step(
// "show-winmd",
// "Print the winmd file path in the cache",
// ).dependOn(
// &PrintLazyPath.create(b, winmd).step,
// );
// const gen_step = b.step("gen", "Generate JSON files (in .zig-cache)");
// const winmd_dep = b.dependency("winmd", .{});
// const gen_out_dir = blk: {
// const exe = b.addExecutable(.{
// .name = "genjson",
// .root_module = b.createModule(.{
// .root_source_file = b.path("src/genjson.zig"),
// .target = b.graph.host,
// }),
// });
// exe.root_module.addImport("winmd", winmd_dep.module("winmd"));
// const run = b.addRunArtifact(exe);
// run.addFileArg(winmd);
// const out_dir = run.addOutputDirectoryArg(".");
// gen_step.dependOn(&run.step);
// break :blk out_dir;
// };
// {
// // we can't use b.installDirectory because we need to make sure we delete
// // all existing files before installing the new ones in case JSON files are removed
// // b.installDirectory(.{
// // .source_dir = gen_out_dir,
// // .install_dir = .prefix,
// // .install_subdir = ".",
// // });
// const install_exe = b.addExecutable(.{
// .name = "install",
// .root_module = b.createModule(.{
// .root_source_file = b.path("src/install.zig"),
// .target = b.graph.host,
// }),
// });
// const install = b.addRunArtifact(install_exe);
// install.addDirectoryArg(gen_out_dir);
// install.addArg(b.pathResolve(&.{ b.build_root.path.?, "win32json", "api" }));
// b.getInstallStep().dependOn(&install.step);
// }
// const test_step = b.step("test", "Run all the tests");
// {
// const validate_exe = b.addExecutable(.{
// .name = "validate",
// .root_module = b.createModule(.{
// .root_source_file = b.path("src/validate.zig"),
// .target = b.graph.host,
// .optimize = .Debug,
// }),
// });
// const validate = b.addRunArtifact(validate_exe);
// validate.addDirectoryArg(gen_out_dir);
// validate.expectExitCode(0);
// test_step.dependOn(&validate.step);
// }
}
// const PrintLazyPath = struct {
// step: std.Build.Step,
// lazy_path: Build.LazyPath,
// pub fn create(
// b: *Build,
// lazy_path: Build.LazyPath,
// ) *PrintLazyPath {
// const print = b.allocator.create(PrintLazyPath) catch unreachable;
// print.* = .{
// .step = std.Build.Step.init(.{
// .id = .custom,
// .name = "print the given lazy path",
// .owner = b,
// .makeFn = make,
// }),
// .lazy_path = lazy_path,
// };
// lazy_path.addStepDependencies(&print.step);
// return print;
// }
// fn make(step: *std.Build.Step, opt: std.Build.Step.MakeOptions) !void {
// _ = opt;
// const print: *PrintLazyPath = @fieldParentPtr("step", step);
// var write_buf: [1024]u8 = undefined;
// var stdout = std.fs.File.stdout().writer(&write_buf);
// try stdout.interface.print(
// "{s}\n",
// .{print.lazy_path.getPath(step.owner)},
// );
// try stdout.interface.flush();
// }
// };
+22
View File
@@ -0,0 +1,22 @@
.{
.name = .generatewin32json,
.fingerprint = 0xfd7a77ded32ab056,
.version = "0.0.0",
.zig_version = "0.15.2",
.minimum_zig_version = "0.15.0",
.dependencies = .{
.zipcmdline = .{
.url = "git+https://github.com/marler8997/zipcmdline#a6de65875023c52d2f56f21a31c6487266f8105f",
.hash = "zipcmdline-0.0.0-DFii07hmAwCFNhtMpml64Pj_hr8kN4WesClygZErQHsk",
},
.winmd = .{
.url = "git+https://github.com/marler8997/winmd-zig#d0d8daff01b4fb1a6324c79a855c34ced5bbc8d2",
.hash = "winmd-0.0.0-2QIrG4MpAQCCh0ReC0jZKAps9Frn3jbbR--W7szyGAOS",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
+98
View File
@@ -0,0 +1,98 @@
pub fn run(
allocator: std.mem.Allocator,
name: []const u8,
argv: []const []const u8,
) !void {
var child = std.process.Child.init(argv, allocator);
std.log.info("{f}", .{fmtArgv(child.argv)});
try child.spawn();
const term = try child.wait();
if (childProcFailed(term)) {
errExit("{s} {f}", .{ name, fmtTerm(term) });
}
}
pub fn childProcFailed(term: std.process.Child.Term) bool {
return switch (term) {
.Exited => |code| code != 0,
.Signal => true,
.Stopped => true,
.Unknown => true,
};
}
pub fn fmtArgv(argv: []const []const u8) FormatArgv {
return .{ .argv = argv };
}
const FormatArgv = struct {
argv: []const []const u8,
pub fn format(self: @This(), writer: *std.Io.Writer) error{WriteFailed}!void {
var prefix: []const u8 = "";
for (self.argv) |arg| {
try writer.print("{s}{s}", .{ prefix, arg });
prefix = " ";
}
}
};
pub fn fmtTerm(term: std.process.Child.Term) FormatTerm {
return .{ .term = term };
}
const FormatTerm = struct {
term: std.process.Child.Term,
pub fn format(self: @This(), writer: *std.Io.Writer) error{WriteFailed}!void {
switch (self.term) {
.Exited => |code| try writer.print("exited with code {}", .{code}),
.Signal => |sig| try writer.print("exited with signal {}", .{sig}),
.Stopped => |sig| try writer.print("stopped with signal {}", .{sig}),
.Unknown => |sig| try writer.print("terminated abnormally with signal {}", .{sig}),
}
}
};
pub fn makeRepo(path: []const u8) !void {
std.fs.cwd().access(path, .{}) catch |err| switch (err) {
error.FileNotFound => try gitInit(path),
else => |e| return e,
};
}
fn gitInit(repo: []const u8) !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const tmp_repo = try std.mem.concat(allocator, u8, &.{ repo, ".initializing" });
defer allocator.free(tmp_repo);
try std.fs.cwd().deleteTree(tmp_repo);
try std.fs.cwd().makeDir(tmp_repo);
try run(allocator, "git init", &.{
"git",
"-C",
tmp_repo,
"init",
});
try std.fs.cwd().rename(tmp_repo, repo);
}
pub fn readSha(sha_file: []const u8) !union(enum) {
invalid: []const u8,
good: [40]u8,
} {
var buffer: [41]u8 = undefined;
var out_file = std.fs.cwd().openFile(sha_file, .{}) catch |err| switch (err) {
error.FileNotFound => return .{ .invalid = "file not found" },
else => |e| return e,
};
defer out_file.close();
const len = try out_file.readAll(&buffer);
if (len < 40) return .{ .invalid = "file too small" };
if (len > 40) return .{ .invalid = "file too big" };
return .{ .good = buffer[0..40].* };
}
pub fn errExit(comptime fmt: []const u8, args: anytype) noreturn {
std.log.err(fmt, args);
std.process.exit(0xff);
}
const std = @import("std");
+93
View File
@@ -0,0 +1,93 @@
pub fn main() !u8 {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const allocator = arena.allocator();
const all_args = try std.process.argsAlloc(allocator);
// don't care about freeing args
const cmd_args = all_args[1..];
if (cmd_args.len != 2) {
std.log.err("expected 2 cmdline arguments but got {}", .{cmd_args.len});
return 1;
}
const gen_repo = cmd_args[0];
const out_file_path = cmd_args[1];
try common.makeRepo(gen_repo);
const repo_url = "https://github.com/marlersoft/win32json";
try common.run(allocator, "git fetch", &.{
"git",
"-C",
gen_repo,
"fetch",
repo_url,
"generate",
});
const latest_sha = blk: {
const argv = [_][]const u8{
"git",
"-C",
gen_repo,
"rev-parse",
"FETCH_HEAD",
};
std.log.info("{f}", .{common.fmtArgv(&argv)});
const result = try std.process.Child.run(.{
.allocator = allocator,
.argv = &argv,
});
// don't free stdout, we're using it
//defer allocator.free(result.stdout);
defer allocator.free(result.stderr);
if (result.stderr.len > 0) {
var fw = std.fs.File.stderr().writer(&.{});
try fw.interface.writeAll(result.stderr);
}
if (common.childProcFailed(result.term))
errExit("git rev-parse {f}", .{common.fmtTerm(result.term)});
break :blk std.mem.trimRight(u8, result.stdout, "\r\n");
};
if (latest_sha.len != 40) errExit(
"expected sha from git rev-parse to be 40 characters but got {}: '{s}'",
.{ latest_sha.len, latest_sha },
);
std.log.info("latest generate sha: {s}", .{latest_sha});
switch (try common.readSha(out_file_path)) {
.invalid => |reason| {
std.log.info("existing generate sha: invalid: {s}", .{reason});
},
.good => |existing_sha| {
std.log.info("existing generate sha: {s}", .{existing_sha});
if (std.mem.eql(u8, &existing_sha, latest_sha)) {
std.log.info("status: already up-to-date", .{});
return 0;
}
std.log.info("status: needs update", .{});
},
}
{
var file = try std.fs.cwd().createFile(out_file_path, .{});
defer file.close();
var fw = file.writer(&.{});
try fw.interface.writeAll(latest_sha);
}
// sanity check
switch (try common.readSha(out_file_path)) {
.invalid => |reason| errExit("can't read sha after writing it: {s}", .{reason}),
.good => |sha_from_file| if (!std.mem.eql(u8, &sha_from_file, latest_sha)) errExit(
"sha changed to '{s}'",
.{sha_from_file},
),
}
std.log.info("{s}", .{out_file_path});
return 0;
}
const errExit = common.errExit;
const std = @import("std");
const common = @import("common.zig");
+38
View File
@@ -0,0 +1,38 @@
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
const allocator = arena.allocator();
const all_args = try std.process.argsAlloc(allocator);
// don't care about freeing args
const cmd_args = all_args[1..];
if (cmd_args.len != 1) {
std.log.err("expected 1 cmdline argument but got {}", .{cmd_args.len});
std.process.exit(0xff);
}
const generate_sha_file_path = cmd_args[0];
// const gen_repo = cmd_args[0];
// const main_sha_file_path = cmd_args[1];
// const releases_file_path = cmd_args[2];
// const zigwin32_repo = cmd_args[3];
// const clean_arg = cmd_args[4];
// const do_clean = if (std.mem.eql(u8, clean_arg, "noclean"))
// false
// else if (std.mem.eql(u8, clean_arg, "clean"))
// true
// else
// fatal("unexpected clean cmdline argument '{s}'", .{clean_arg});
const generate_sha = switch (try common.readSha(generate_sha_file_path)) {
.invalid => |reason| errExit("read sha from '{s}' failed: {s}", .{ generate_sha_file_path, reason }),
.good => |sha| sha,
};
std.log.info("generate branch sha: {s}", .{&generate_sha});
@panic("todo");
}
const errExit = common.errExit;
const std = @import("std");
const common = @import("common.zig");