From 2dbd32d12050017cf27d258aa4f62744eb8d8a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Thu, 19 Mar 2026 12:55:10 +0900 Subject: [PATCH] build: automate version resolution in build.zig Removes manual git flags from CI and build scripts. Versioning is now automatically derived from git and build.zig.zon. With this PR, we follow https://semver.org/ Logic: 1. Read the version from build.zig.zon 2. If it doesn't have a `.pre` field (i.e. dev/alpha/beta) it will use that 3. Otherwise it will get the info from git: hash and number of commits since last `.0` version 4. Then build the version: `0.3.0-dev.1493+0896edc3` Note that, since the latest stable version is `0.2.6`. The convention is to use `0.3.0-dev`, as: - `0.2.6` < `0.3.0.dev` < `0.3.0` --- .github/workflows/e2e-integration-test.yml | 2 +- .github/workflows/e2e-test.yml | 2 +- .github/workflows/nightly.yml | 9 +- .github/workflows/wpt.yml | 2 +- Dockerfile | 3 +- Makefile | 4 +- build.zig | 107 +++++++++++++++------ build.zig.zon | 2 +- src/crash_handler.zig | 4 +- src/main.zig | 7 +- src/telemetry/lightpanda.zig | 2 +- 11 files changed, 93 insertions(+), 51 deletions(-) diff --git a/.github/workflows/e2e-integration-test.yml b/.github/workflows/e2e-integration-test.yml index da4056ca..4a639cf1 100644 --- a/.github/workflows/e2e-integration-test.yml +++ b/.github/workflows/e2e-integration-test.yml @@ -27,7 +27,7 @@ jobs: - uses: ./.github/actions/install - name: zig build release - run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }}) + run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 - name: upload artifact uses: actions/upload-artifact@v7 diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index a8e36ca5..34405913 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -52,7 +52,7 @@ jobs: - uses: ./.github/actions/install - name: zig build release - run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }}) + run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 - name: upload artifact uses: actions/upload-artifact@v7 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1c1ece06..78ccca0b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -7,7 +7,6 @@ env: AWS_REGION: ${{ vars.NIGHTLY_BUILD_AWS_REGION }} RELEASE: ${{ github.ref_type == 'tag' && github.ref_name || 'nightly' }} - GIT_VERSION_FLAG: ${{ github.ref_type == 'tag' && format('-Dgit_version={0}', github.ref_name) || '' }} on: push: @@ -45,7 +44,7 @@ jobs: run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin - name: zig build - run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }}) ${{ env.GIT_VERSION_FLAG }} + run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 - name: Rename binary run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }} @@ -85,7 +84,7 @@ jobs: run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin - name: zig build - run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=generic -Dgit_commit=$(git rev-parse --short ${{ github.sha }}) ${{ env.GIT_VERSION_FLAG }} + run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=generic - name: Rename binary run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }} @@ -127,7 +126,7 @@ jobs: run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin - name: zig build - run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dgit_commit=$(git rev-parse --short ${{ github.sha }}) ${{ env.GIT_VERSION_FLAG }} + run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast - name: Rename binary run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }} @@ -167,7 +166,7 @@ jobs: run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast snapshot_creator -- src/snapshot.bin - name: zig build - run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dgit_commit=$(git rev-parse --short ${{ github.sha }}) ${{ env.GIT_VERSION_FLAG }} + run: zig build -Dsnapshot_path=../../snapshot.bin -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast - name: Rename binary run: mv zig-out/bin/lightpanda lightpanda-${{ env.ARCH }}-${{ env.OS }} diff --git a/.github/workflows/wpt.yml b/.github/workflows/wpt.yml index 5b6f9f9f..cb8497f9 100644 --- a/.github/workflows/wpt.yml +++ b/.github/workflows/wpt.yml @@ -30,7 +30,7 @@ jobs: - uses: ./.github/actions/install - name: zig build release - run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 -Dgit_commit=$(git rev-parse --short ${{ github.sha }}) + run: zig build -Dprebuilt_v8_path=v8/libc_v8.a -Doptimize=ReleaseFast -Dcpu=x86_64 - name: upload artifact uses: actions/upload-artifact@v7 diff --git a/Dockerfile b/Dockerfile index f5cd202d..9e30f544 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,8 +53,7 @@ RUN zig build -Doptimize=ReleaseFast \ # build release RUN zig build -Doptimize=ReleaseFast \ -Dsnapshot_path=../../snapshot.bin \ - -Dprebuilt_v8_path=v8/libc_v8.a \ - -Dgit_commit=$(git rev-parse --short HEAD) + -Dprebuilt_v8_path=v8/libc_v8.a FROM debian:stable-slim diff --git a/Makefile b/Makefile index a85d4e69..462761b1 100644 --- a/Makefile +++ b/Makefile @@ -58,13 +58,13 @@ build-v8-snapshot: ## Build in release-fast mode build: build-v8-snapshot @printf "\033[36mBuilding (release fast)...\033[0m\n" - @$(ZIG) build -Doptimize=ReleaseFast -Dsnapshot_path=../../snapshot.bin -Dgit_commit=$$(git rev-parse --short HEAD) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;) + @$(ZIG) build -Doptimize=ReleaseFast -Dsnapshot_path=../../snapshot.bin || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;) @printf "\033[33mBuild OK\033[0m\n" ## Build in debug mode build-dev: @printf "\033[36mBuilding (debug)...\033[0m\n" - @$(ZIG) build -Dgit_commit=$$(git rev-parse --short HEAD) || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;) + @$(ZIG) build || (printf "\033[33mBuild ERROR\033[0m\n"; exit 1;) @printf "\033[33mBuild OK\033[0m\n" ## Run the server in release mode diff --git a/build.zig b/build.zig index 0d917b75..179705b5 100644 --- a/build.zig +++ b/build.zig @@ -17,24 +17,37 @@ // along with this program. If not, see . const std = @import("std"); +const builtin = @import("builtin"); -const Build = std.Build; +const lightpanda_version = std.SemanticVersion.parse(@import("build.zig.zon").version) catch unreachable; +const min_zig_version = std.SemanticVersion.parse(@import("build.zig.zon").minimum_zig_version) catch unreachable; + +const Build = blk: { + if (builtin.zig_version.order(min_zig_version) == .lt) { + const message = std.fmt.comptimePrint( + \\Zig version is too old: + \\ current Zig version: {f} + \\ minimum Zig version: {f} + , .{ builtin.zig_version, min_zig_version }); + @compileError(message); + } else { + break :blk std.Build; + } +}; pub fn build(b: *Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const manifest = Manifest.init(b); - - const git_commit = b.option([]const u8, "git_commit", "Current git commit"); - const git_version = b.option([]const u8, "git_version", "Current git version (from tag)"); const prebuilt_v8_path = b.option([]const u8, "prebuilt_v8_path", "Path to prebuilt libc_v8.a"); const snapshot_path = b.option([]const u8, "snapshot_path", "Path to v8 snapshot"); + const version = resolveVersion(b); + var stdout = std.fs.File.stdout().writer(&.{}); + try stdout.interface.print("Lightpanda {f}\n", .{version}); + var opts = b.addOptions(); - opts.addOption([]const u8, "version", manifest.version); - opts.addOption([]const u8, "git_commit", git_commit orelse "dev"); - opts.addOption(?[]const u8, "git_version", git_version orelse null); + opts.addOption([]const u8, "version", b.fmt("{f}", .{version})); opts.addOption(?[]const u8, "snapshot_path", snapshot_path); const enable_tsan = b.option(bool, "tsan", "Enable Thread Sanitizer") orelse false; @@ -96,6 +109,11 @@ pub fn build(b: *Build) !void { } const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); + + const version_info_step = b.step("version", "Print the resolved version information"); + const version_info_run = b.addRunArtifact(exe); + version_info_run.addArg("version"); + version_info_step.dependOn(&version_info_run.step); } { @@ -701,27 +719,56 @@ fn buildCurl( return lib; } -const Manifest = struct { - version: []const u8, - minimum_zig_version: []const u8, - - fn init(b: *std.Build) Manifest { - const input = @embedFile("build.zig.zon"); - - var diagnostics: std.zon.parse.Diagnostics = .{}; - defer diagnostics.deinit(b.allocator); - - return std.zon.parse.fromSlice(Manifest, b.allocator, input, &diagnostics, .{ - .free_on_error = true, - .ignore_unknown_fields = true, - }) catch |err| { - switch (err) { - error.OutOfMemory => @panic("OOM"), - error.ParseZon => { - std.debug.print("Parse diagnostics:\n{f}\n", .{diagnostics}); - std.process.exit(1); - }, - } +/// Returns `MAJOR.MINOR.PATCH-dev` when `git describe` fails. +fn resolveVersion(b: *std.Build) std.SemanticVersion { + const version_string = b.option([]const u8, "version_string", "Override the version of this build"); + if (version_string) |semver_string| { + return std.SemanticVersion.parse(semver_string) catch |err| { + std.debug.panic("Expected -Dversion-string={s} to be a semantic version: {}", .{ semver_string, err }); }; } -}; + + if (lightpanda_version.pre == null and lightpanda_version.build == null) return lightpanda_version; + // Check if we're exactly on a tagged release + _ = runGit(b, &.{ "describe", "--tags", "--exact-match" }) catch { + // Not on a tag, need to create a dev version + const git_hash_raw = runGit(b, &.{ "rev-parse", "--short", "HEAD" }) catch return lightpanda_version; + const commit_hash = std.mem.trim(u8, git_hash_raw, " \n\r"); + // Get the commit count - either from base tag or total + const commit_count = blk: { + // Try to find the most recent base version tag (ending with .0) + const base_tag_raw = runGit(b, &.{ "describe", "--tags", "--match=*.0", "--abbrev=0" }) catch { + // No .0 tags found, fall back to total commit count + const git_count_raw = runGit(b, &.{ "rev-list", "--count", "HEAD" }) catch return lightpanda_version; + break :blk std.mem.trim(u8, git_count_raw, " \n\r"); + }; + + const base_tag = std.mem.trim(u8, base_tag_raw, " \n\r"); + // Count commits since the base tag + const count_cmd = b.fmt("{s}..HEAD", .{base_tag}); + const git_count_raw = runGit(b, &.{ "rev-list", "--count", count_cmd }) catch return lightpanda_version; + break :blk std.mem.trim(u8, git_count_raw, " \n\r"); + }; + + return .{ + .major = lightpanda_version.major, + .minor = lightpanda_version.minor, + .patch = lightpanda_version.patch, + .pre = b.fmt("dev.{s}", .{commit_count}), + .build = commit_hash, + }; + }; + // We're exactly on a tag, return the version as-is + return lightpanda_version; +} + +/// Helper function to run git commands and return stdout +fn runGit(b: *std.Build, args: []const []const u8) ![]const u8 { + var code: u8 = undefined; + const dir = b.pathFromRoot("."); + var command: std.ArrayList([]const u8) = .empty; + defer command.deinit(b.allocator); + try command.appendSlice(b.allocator, &.{ "git", "-C", dir }); + try command.appendSlice(b.allocator, args); + return b.runAllowFail(command.items, &code, .Ignore); +} diff --git a/build.zig.zon b/build.zig.zon index cee52057..60e32ed7 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .browser, - .version = "0.0.0", + .version = "0.3.0-dev", .fingerprint = 0xda130f3af836cea0, // Changing this has security and trust implications. .minimum_zig_version = "0.15.2", .dependencies = .{ diff --git a/src/crash_handler.zig b/src/crash_handler.zig index d9c77299..b6dd6b78 100644 --- a/src/crash_handler.zig +++ b/src/crash_handler.zig @@ -47,7 +47,7 @@ pub noinline fn crash( writer.print("\nreason: {s}\n", .{reason}) catch abort(); writer.print("OS: {s}\n", .{@tagName(builtin.os.tag)}) catch abort(); writer.print("mode: {s}\n", .{@tagName(builtin.mode)}) catch abort(); - writer.print("version: {s}\n", .{lp.build_config.git_commit}) catch abort(); + writer.print("version: {s}\n", .{lp.build_config.version}) catch abort(); inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |f| { writer.writeAll(f.name ++ ": ") catch break; @import("log.zig").writeValue(.pretty, @field(args, f.name), writer) catch abort(); @@ -86,7 +86,7 @@ fn report(reason: []const u8, begin_addr: usize, args: anytype) !void { var url_buffer: [4096]u8 = undefined; const url = blk: { var writer: std.Io.Writer = .fixed(&url_buffer); - try writer.print("https://crash.lightpanda.io/c?v={s}&r=", .{lp.build_config.git_commit}); + try writer.print("https://crash.lightpanda.io/c?v={s}&r=", .{lp.build_config.version}); for (reason) |b| { switch (b) { 'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_' => try writer.writeByte(b), diff --git a/src/main.zig b/src/main.zig index 640f8231..85370109 100644 --- a/src/main.zig +++ b/src/main.zig @@ -59,11 +59,8 @@ fn run(allocator: Allocator, main_arena: Allocator) !void { return std.process.cleanExit(); }, .version => { - if (lp.build_config.git_version) |version| { - std.debug.print("{s} ({s})\n", .{ version, lp.build_config.git_commit }); - } else { - std.debug.print("{s}\n", .{lp.build_config.git_commit}); - } + var stdout = std.fs.File.stdout().writer(&.{}); + try stdout.interface.print("{s}\n", .{lp.build_config.version}); return std.process.cleanExit(); }, else => {}, diff --git a/src/telemetry/lightpanda.zig b/src/telemetry/lightpanda.zig index 96587256..95ac6ddf 100644 --- a/src/telemetry/lightpanda.zig +++ b/src/telemetry/lightpanda.zig @@ -145,7 +145,7 @@ const LightPandaEvent = struct { try writer.write(builtin.cpu.arch); try writer.objectField("version"); - try writer.write(build_config.git_version orelse build_config.git_commit); + try writer.write(build_config.version); try writer.objectField("event"); try writer.write(@tagName(std.meta.activeTag(self.event)));