From 142471fcc46070326526e3976f0150fe734df0b6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 2 Dec 2023 21:51:34 -0700 Subject: zig build system: change target, compilation, and module APIs Introduce the concept of "target query" and "resolved target". A target query is what the user specifies, with some things left to default. A resolved target has the default things discovered and populated. In the future, std.zig.CrossTarget will be rename to std.Target.Query. Introduces `std.Build.resolveTargetQuery` to get from one to the other. The concept of `main_mod_path` is gone, no longer supported. You have to put the root source file at the module root now. * remove deprecated API * update build.zig for the breaking API changes in this branch * move std.Build.Step.Compile.BuildId to std.zig.BuildId * add more options to std.Build.ExecutableOptions, std.Build.ObjectOptions, std.Build.SharedLibraryOptions, std.Build.StaticLibraryOptions, and std.Build.TestOptions. * remove `std.Build.constructCMacro`. There is no use for this API. * deprecate `std.Build.Step.Compile.defineCMacro`. Instead, `std.Build.Module.addCMacro` is provided. - remove `std.Build.Step.Compile.defineCMacroRaw`. * deprecate `std.Build.Step.Compile.linkFrameworkNeeded` - use `std.Build.Module.linkFramework` * deprecate `std.Build.Step.Compile.linkFrameworkWeak` - use `std.Build.Module.linkFramework` * move more logic into `std.Build.Module` * allow `target` and `optimize` to be `null` when creating a Module. Along with other fields, those unspecified options will be inherited from parent `Module` when inserted into an import table. * the `target` field of `addExecutable` is now required. pass `b.host` to get the host target. --- test/standalone/stack_iterator/build.zig | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'test/standalone/stack_iterator') diff --git a/test/standalone/stack_iterator/build.zig b/test/standalone/stack_iterator/build.zig index 628210452c..6a98011e41 100644 --- a/test/standalone/stack_iterator/build.zig +++ b/test/standalone/stack_iterator/build.zig @@ -22,11 +22,10 @@ pub fn build(b: *std.Build) void { .root_source_file = .{ .path = "unwind.zig" }, .target = target, .optimize = optimize, + .unwind_tables = target.target.isDarwin(), + .omit_frame_pointer = false, }); - if (target.isDarwin()) exe.unwind_tables = true; - exe.omit_frame_pointer = false; - const run_cmd = b.addRunArtifact(exe); test_step.dependOn(&run_cmd.step); } @@ -46,11 +45,10 @@ pub fn build(b: *std.Build) void { .root_source_file = .{ .path = "unwind.zig" }, .target = target, .optimize = optimize, + .unwind_tables = true, + .omit_frame_pointer = true, }); - exe.omit_frame_pointer = true; - exe.unwind_tables = true; - const run_cmd = b.addRunArtifact(exe); test_step.dependOn(&run_cmd.step); } @@ -69,11 +67,12 @@ pub fn build(b: *std.Build) void { .name = "c_shared_lib", .target = target, .optimize = optimize, + .strip = false, }); - if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)"); + if (target.target.os.tag == .windows) + c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)"); - c_shared_lib.strip = false; c_shared_lib.addCSourceFile(.{ .file = .{ .path = "shared_lib.c" }, .flags = &.{"-fomit-frame-pointer"}, @@ -85,10 +84,10 @@ pub fn build(b: *std.Build) void { .root_source_file = .{ .path = "shared_lib_unwind.zig" }, .target = target, .optimize = optimize, + .unwind_tables = target.target.isDarwin(), + .omit_frame_pointer = true, }); - if (target.isDarwin()) exe.unwind_tables = true; - exe.omit_frame_pointer = true; exe.linkLibrary(c_shared_lib); const run_cmd = b.addRunArtifact(exe); -- cgit v1.2.3 From b92e30ff0bd2b77a486451b21d17666a311407f3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Dec 2023 16:09:07 -0700 Subject: std.Build.ResolvedTarget: rename target field to result This change is seemingly insignificant but I actually agonized over this for three days. Some other things I considered: * (status quo in master branch) make Compile step creation functions accept a Target.Query and delete the ResolvedTarget struct. - downside: redundantly resolve target queries many times * same as before but additionally add a hash map to cache target query resolutions. - downside: now there is a hash map that doesn't actually need to exist, just to make the API more ergonomic. * add is_native_os and is_native_abi fields to std.Target and use it directly as the result of resolving a target query. - downside: they really don't belong there. They would be available as comptime booleans via `@import("builtin")` but they should not be exposed that way. With this change the downsides are: * the option name of addExecutable and friends is `target` instead of `resolved_target` matching the type name. - upside: this does not break compatibility with existing build scripts * you likely end up seeing `target.result.cpu.arch` rather than `target.cpu.arch`. - upside: this is an improvement over `target.target.cpu.arch` which it was before this commit. - downside: `b.host.target` is now `b.host.result`. --- build.zig | 8 ++++---- lib/build_runner.zig | 2 +- lib/std/Build.zig | 4 ++-- lib/std/Build/Module.zig | 10 +++++----- lib/std/Build/Step/Compile.zig | 6 +++--- lib/std/Build/Step/Run.zig | 14 ++++++++------ test/link/elf.zig | 2 +- test/link/link.zig | 2 +- test/link/macho/bugs/13056/build.zig | 2 +- test/src/Cases.zig | 10 +++++----- test/standalone/c_compiler/build.zig | 2 +- test/standalone/compiler_rt_panic/build.zig | 8 ++++---- test/standalone/ios/build.zig | 2 +- test/standalone/self_exe_symlink/build.zig | 2 +- test/standalone/stack_iterator/build.zig | 6 +++--- test/tests.zig | 4 ++-- 16 files changed, 43 insertions(+), 41 deletions(-) (limited to 'test/standalone/stack_iterator') diff --git a/build.zig b/build.zig index bc4c32035c..d24b51741a 100644 --- a/build.zig +++ b/build.zig @@ -221,7 +221,7 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(&exe.step); - if (target.target.os.tag == .windows and target.target.abi == .gnu) { + if (target.result.os.tag == .windows and target.result.abi == .gnu) { // LTO is currently broken on mingw, this can be removed when it's fixed. exe.want_lto = false; check_case_exe.want_lto = false; @@ -347,7 +347,7 @@ pub fn build(b: *std.Build) !void { try addStaticLlvmOptionsToExe(exe); try addStaticLlvmOptionsToExe(check_case_exe); } - if (target.target.os.tag == .windows) { + if (target.result.os.tag == .windows) { inline for (.{ exe, check_case_exe }) |artifact| { artifact.linkSystemLibrary("version"); artifact.linkSystemLibrary("uuid"); @@ -371,7 +371,7 @@ pub fn build(b: *std.Build) !void { ); // On mingw, we need to opt into windows 7+ to get some features required by tracy. - const tracy_c_flags: []const []const u8 = if (target.target.os.tag == .windows and target.target.abi == .gnu) + const tracy_c_flags: []const []const u8 = if (target.result.os.tag == .windows and target.result.abi == .gnu) &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined", "-D_WIN32_WINNT=0x601" } else &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" }; @@ -383,7 +383,7 @@ pub fn build(b: *std.Build) !void { } exe.linkLibC(); - if (target.target.os.tag == .windows) { + if (target.result.os.tag == .windows) { exe.linkSystemLibrary("dbghelp"); exe.linkSystemLibrary("ws2_32"); } diff --git a/lib/build_runner.zig b/lib/build_runner.zig index b485469a38..687a1eb151 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -48,7 +48,7 @@ pub fn main() !void { const host: std.Build.ResolvedTarget = .{ .query = .{}, - .target = try std.zig.system.resolveTargetQuery(.{}), + .result = try std.zig.system.resolveTargetQuery(.{}), }; const build_root_directory: std.Build.Cache.Directory = .{ diff --git a/lib/std/Build.zig b/lib/std/Build.zig index da2bb49397..2074269c9b 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -2044,7 +2044,7 @@ pub fn hex64(x: u64) [16]u8 { /// of the target are "native". This can apply to the CPU, the OS, or even the ABI. pub const ResolvedTarget = struct { query: Target.Query, - target: Target, + result: Target, }; /// Converts a target query into a fully resolved target that can be passed to @@ -2056,7 +2056,7 @@ pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget { return .{ .query = query, - .target = std.zig.system.resolveTargetQuery(query) catch + .result = std.zig.system.resolveTargetQuery(query) catch @panic("unable to resolve target query"), }; } diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index 9c98dd0a46..81fab2be5e 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -10,7 +10,7 @@ root_source_file: ?LazyPath, /// maintain step dependency edges. import_table: std.StringArrayHashMapUnmanaged(*Module), -target: ?std.Build.ResolvedTarget = null, +resolved_target: ?std.Build.ResolvedTarget = null, optimize: ?std.builtin.OptimizeMode = null, dwarf_format: ?std.dwarf.Format, @@ -192,7 +192,7 @@ pub fn init(m: *Module, owner: *std.Build, options: CreateOptions, compile: ?*St .depending_steps = .{}, .root_source_file = if (options.root_source_file) |lp| lp.dupe(owner) else null, .import_table = .{}, - .target = options.target, + .resolved_target = options.target, .optimize = options.optimize, .link_libc = options.link_libc, .link_libcpp = options.link_libcpp, @@ -627,7 +627,7 @@ pub fn appendZigProcessFlags( try zig_args.append(@tagName(m.code_model)); } - if (m.target) |*target| { + if (m.resolved_target) |*target| { // Communicate the query via CLI since it's more compact. if (!target.query.isNative()) { try zig_args.appendSlice(&.{ @@ -737,9 +737,9 @@ fn linkLibraryOrObject(m: *Module, other: *Step.Compile) void { } fn requireKnownTarget(m: *Module) std.Target { - const resolved_target = m.target orelse + const resolved_target = m.resolved_target orelse @panic("this API requires the Module to be created with a known 'target' field"); - return resolved_target.target; + return resolved_target.result; } const Module = @This(); diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index b51304c910..a984b16189 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -251,7 +251,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile { else owner.fmt("{s} ", .{name}); - const target = options.root_module.target.?.target; + const target = options.root_module.target.?.result; const step_name = owner.fmt("{s} {s}{s} {s}", .{ switch (options.kind) { @@ -954,7 +954,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try addFlag(&zig_args, "llvm", self.use_llvm); try addFlag(&zig_args, "lld", self.use_lld); - if (self.root_module.target.?.query.ofmt) |ofmt| { + if (self.root_module.resolved_target.?.query.ofmt) |ofmt| { try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)})); } @@ -1845,5 +1845,5 @@ fn matchCompileError(actual: []const u8, expected: []const u8) bool { pub fn rootModuleTarget(c: *Compile) std.Target { // The root module is always given a target, so we know this to be non-null. - return c.root_module.target.?.target; + return c.root_module.resolved_target.?.result; } diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index ad68dce648..d48fe2a2f1 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -678,8 +678,8 @@ fn runCommand( const need_cross_glibc = exe.rootModuleTarget().isGnuLibC() and exe.is_linking_libc; - const other_target = exe.root_module.target.?.target; - switch (std.zig.system.getExternalExecutor(b.host.target, &other_target, .{ + const other_target = exe.root_module.resolved_target.?.result; + switch (std.zig.system.getExternalExecutor(b.host.result, &other_target, .{ .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, .link_libc = exe.is_linking_libc, })) { @@ -752,7 +752,7 @@ fn runCommand( .bad_dl => |foreign_dl| { if (allow_skip) return error.MakeSkipped; - const host_dl = b.host.target.dynamic_linker.get() orelse "(none)"; + const host_dl = b.host.result.dynamic_linker.get() orelse "(none)"; return step.fail( \\the host system is unable to execute binaries from the target @@ -764,7 +764,7 @@ fn runCommand( .bad_os_or_cpu => { if (allow_skip) return error.MakeSkipped; - const host_name = try b.host.target.zigTriple(b.allocator); + const host_name = try b.host.result.zigTriple(b.allocator); const foreign_name = try exe.rootModuleTarget().zigTriple(b.allocator); return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{ @@ -1295,7 +1295,9 @@ fn addPathForDynLibs(self: *Run, artifact: *Step.Compile) void { while (it.next()) |item| { const other = item.compile.?; if (item.module == &other.root_module) { - if (item.module.target.?.target.os.tag == .windows and other.isDynamicLibrary()) { + if (item.module.resolved_target.?.result.os.tag == .windows and + other.isDynamicLibrary()) + { addPathDir(self, fs.path.dirname(other.getEmittedBin().getPath(b)).?); } } @@ -1314,7 +1316,7 @@ fn failForeign( return error.MakeSkipped; const b = self.step.owner; - const host_name = try b.host.target.zigTriple(b.allocator); + const host_name = try b.host.result.zigTriple(b.allocator); const foreign_name = try exe.rootModuleTarget().zigTriple(b.allocator); return self.step.fail( diff --git a/test/link/elf.zig b/test/link/elf.zig index 459244db29..dafbf867ed 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -1763,7 +1763,7 @@ fn testInitArrayOrder(b: *Build, opts: Options) *Step { exe.addObject(g_o); exe.addObject(h_o); - if (opts.target.target.isGnuLibC()) { + if (opts.target.result.isGnuLibC()) { // TODO I think we need to clarify our use of `-fPIC -fPIE` flags for different targets exe.pie = true; } diff --git a/test/link/link.zig b/test/link/link.zig index 4d31d2d49a..ccce03bb0b 100644 --- a/test/link/link.zig +++ b/test/link/link.zig @@ -14,7 +14,7 @@ pub const Options = struct { }; pub fn addTestStep(b: *Build, prefix: []const u8, opts: Options) *Step { - const target = opts.target.target.zigTriple(b.allocator) catch @panic("OOM"); + const target = opts.target.result.zigTriple(b.allocator) catch @panic("OOM"); const optimize = @tagName(opts.optimize); const use_llvm = if (opts.use_llvm) "llvm" else "no-llvm"; const name = std.fmt.allocPrint(b.allocator, "test-{s}-{s}-{s}-{s}", .{ diff --git a/test/link/macho/bugs/13056/build.zig b/test/link/macho/bugs/13056/build.zig index c7136bbd7f..53bcefb930 100644 --- a/test/link/macho/bugs/13056/build.zig +++ b/test/link/macho/bugs/13056/build.zig @@ -15,7 +15,7 @@ pub fn build(b: *std.Build) void { fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void { const target = b.resolveTargetQuery(.{ .os_tag = .macos }); - const sdk = std.zig.system.darwin.getSdk(b.allocator, target.target) orelse + const sdk = std.zig.system.darwin.getSdk(b.allocator, target.result) orelse @panic("macOS SDK is required to run the test"); const exe = b.addExecutable(.{ diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 0f2645f0e0..6fedf43bf7 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -467,7 +467,7 @@ fn addFromDirInner( // Cross-product to get all possible test combinations for (targets) |target_query| { const resolved_target = b.resolveTargetQuery(target_query); - const target = resolved_target.target; + const target = resolved_target.result; for (backends) |backend| { if (backend == .stage2 and target.cpu.arch != .wasm32 and target.cpu.arch != .x86_64) @@ -647,8 +647,8 @@ pub fn lowerToBuildSteps( parent_step.dependOn(&artifact.step); }, .Execution => |expected_stdout| no_exec: { - const run = if (case.target.target.ofmt == .c) run_step: { - if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) { + const run = if (case.target.result.ofmt == .c) run_step: { + if (getExternalExecutor(host, &case.target.result, .{ .link_libc = true }) != .native) { // We wouldn't be able to run the compiled C code. break :no_exec; } @@ -667,7 +667,7 @@ pub fn lowerToBuildSteps( "--", "-lc", "-target", - case.target.target.zigTriple(b.allocator) catch @panic("OOM"), + case.target.result.zigTriple(b.allocator) catch @panic("OOM"), }); run_c.addArtifactArg(artifact); break :run_step run_c; @@ -693,7 +693,7 @@ pub fn lowerToBuildSteps( continue; // Pass test. } - if (getExternalExecutor(host, &case.target.target, .{ .link_libc = true }) != .native) { + if (getExternalExecutor(host, &case.target.result, .{ .link_libc = true }) != .native) { // We wouldn't be able to run the compiled C code. continue; // Pass test. } diff --git a/test/standalone/c_compiler/build.zig b/test/standalone/c_compiler/build.zig index 236042d487..0550ad8cee 100644 --- a/test/standalone/c_compiler/build.zig +++ b/test/standalone/c_compiler/build.zig @@ -42,7 +42,7 @@ fn add( exe_cpp.addCSourceFile(.{ .file = .{ .path = "test.cpp" }, .flags = &[0][]const u8{} }); exe_cpp.linkLibCpp(); - switch (target.target.os.tag) { + switch (target.result.os.tag) { .windows => { // https://github.com/ziglang/zig/issues/8531 exe_cpp.want_lto = false; diff --git a/test/standalone/compiler_rt_panic/build.zig b/test/standalone/compiler_rt_panic/build.zig index cb275e08df..8ad7732a93 100644 --- a/test/standalone/compiler_rt_panic/build.zig +++ b/test/standalone/compiler_rt_panic/build.zig @@ -4,16 +4,16 @@ pub fn build(b: *std.Build) void { const test_step = b.step("test", "Test it"); b.default_step = test_step; - const resolved_target = b.standardTargetOptions(.{}); - const target = resolved_target.target; + const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - if (target.ofmt != .elf or !(target.abi.isMusl() or target.abi.isGnu())) return; + if (target.result.ofmt != .elf or !(target.result.abi.isMusl() or target.result.abi.isGnu())) + return; const exe = b.addExecutable(.{ .name = "main", .optimize = optimize, - .target = resolved_target, + .target = target, }); exe.linkLibC(); exe.addCSourceFile(.{ diff --git a/test/standalone/ios/build.zig b/test/standalone/ios/build.zig index 7642f3a3e8..356f12a3d9 100644 --- a/test/standalone/ios/build.zig +++ b/test/standalone/ios/build.zig @@ -12,7 +12,7 @@ pub fn build(b: *std.Build) void { .cpu_arch = .aarch64, .os_tag = .ios, }); - const sdk = std.zig.system.darwin.getSdk(b.allocator, target.target) orelse + const sdk = std.zig.system.darwin.getSdk(b.allocator, target.result) orelse @panic("no iOS SDK found"); b.sysroot = sdk; diff --git a/test/standalone/self_exe_symlink/build.zig b/test/standalone/self_exe_symlink/build.zig index c4720ce967..d61d502574 100644 --- a/test/standalone/self_exe_symlink/build.zig +++ b/test/standalone/self_exe_symlink/build.zig @@ -11,7 +11,7 @@ pub fn build(b: *std.Build) void { // The test requires getFdPath in order to to get the path of the // File returned by openSelfExe - if (!std.os.isGetFdPathSupportedOnTarget(target.target.os)) return; + if (!std.os.isGetFdPathSupportedOnTarget(target.result.os)) return; const main = b.addExecutable(.{ .name = "main", diff --git a/test/standalone/stack_iterator/build.zig b/test/standalone/stack_iterator/build.zig index 6a98011e41..d40a377d39 100644 --- a/test/standalone/stack_iterator/build.zig +++ b/test/standalone/stack_iterator/build.zig @@ -22,7 +22,7 @@ pub fn build(b: *std.Build) void { .root_source_file = .{ .path = "unwind.zig" }, .target = target, .optimize = optimize, - .unwind_tables = target.target.isDarwin(), + .unwind_tables = target.result.isDarwin(), .omit_frame_pointer = false, }); @@ -70,7 +70,7 @@ pub fn build(b: *std.Build) void { .strip = false, }); - if (target.target.os.tag == .windows) + if (target.result.os.tag == .windows) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)"); c_shared_lib.addCSourceFile(.{ @@ -84,7 +84,7 @@ pub fn build(b: *std.Build) void { .root_source_file = .{ .path = "shared_lib_unwind.zig" }, .target = target, .optimize = optimize, - .unwind_tables = target.target.isDarwin(), + .unwind_tables = target.result.isDarwin(), .omit_frame_pointer = true, }); diff --git a/test/tests.zig b/test/tests.zig index 64c0f3a42e..b2fb1e4bca 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1043,7 +1043,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { continue; const resolved_target = b.resolveTargetQuery(test_target.target); - const target = resolved_target.target; + const target = resolved_target.result; if (options.skip_cross_glibc and !test_target.target.isNative() and target.isGnuLibC() and test_target.link_libc == true) @@ -1229,7 +1229,7 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S if (skip_non_native and !c_abi_target.target.isNative()) continue; const resolved_target = b.resolveTargetQuery(c_abi_target.target); - const target = resolved_target.target; + const target = resolved_target.result; if (target.os.tag == .windows and target.cpu.arch == .aarch64) { // https://github.com/ziglang/zig/issues/14908 -- cgit v1.2.3 From 751ff043d777c31655757df3ca93b2acaa8204a4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 28 Dec 2023 18:05:30 -0700 Subject: fix stack_iterator test build script When I updated this build script to the new API, I incorrectly translated the logic for setting unwind_tables. This commit fixes it. --- test/standalone/stack_iterator/build.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'test/standalone/stack_iterator') diff --git a/test/standalone/stack_iterator/build.zig b/test/standalone/stack_iterator/build.zig index d40a377d39..463a533ac4 100644 --- a/test/standalone/stack_iterator/build.zig +++ b/test/standalone/stack_iterator/build.zig @@ -22,7 +22,7 @@ pub fn build(b: *std.Build) void { .root_source_file = .{ .path = "unwind.zig" }, .target = target, .optimize = optimize, - .unwind_tables = target.result.isDarwin(), + .unwind_tables = if (target.result.isDarwin()) true else null, .omit_frame_pointer = false, }); @@ -84,7 +84,7 @@ pub fn build(b: *std.Build) void { .root_source_file = .{ .path = "shared_lib_unwind.zig" }, .target = target, .optimize = optimize, - .unwind_tables = target.result.isDarwin(), + .unwind_tables = if (target.result.isDarwin()) true else null, .omit_frame_pointer = true, }); -- cgit v1.2.3