From 8caaf7ad96e1b34ab2d740af7b5fc01554d21807 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 14 Aug 2026 10:43:34 +0800 Subject: [PATCH] dev: On linux -dev_fast is on by default Assuming the conditions are met for -dev_fast to work (linux, debug, x86-64 without sanitizers), the -dev_fast now defaults to true. download-v8 make target always downloads the .a (as before) and on linux x86-64 it also downloads the .so. --- CONTRIBUTING.md | 11 ++--- Makefile | 26 ++++------- build.zig | 121 ++++++++++++++++++++++++++++++------------------ 3 files changed, 88 insertions(+), 70 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 055946755..f8678ed4f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,13 +12,12 @@ See [AGENTS.md](AGENTS.md) for the full set of test, formatting, and code conven ### Skip the V8 source build By default the build compiles V8 from source, which takes several minutes. Run -`make download-v8` once to fetch the matching prebuilt archive from the +`make download-v8` once to fetch the matching prebuilt libraries from the [`zig-v8-fork`](https://github.com/lightpanda-io/zig-v8-fork/releases) releases -instead; later `make build` / `make test` pick it up automatically. - -On Linux x86_64, `make download-v8-shared` additionally fetches the shared -flavor (`libc_v8.so`) that `zig build -Ddev_fast=true` picks up automatically -for much faster debug rebuilds. +instead; later `make build` / `make test` pick them up automatically. On Linux +x86_64 this includes the shared flavor (`libc_v8.so`) used by `-Ddev_fast` +builds (defaults to true). Pass `-Ddev_fast=false` to get a static-V8 debug +build instead. ## Before opening a PR diff --git a/Makefile b/Makefile index 9085ae0a6..dadfa59fa 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,8 @@ BC := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) # option test filter make test F="server" F= -# Extra flags forwarded to every `$(ZIG) build` invocation. Most commonly used -# to point at a prebuilt V8 archive and skip the multi-minute source rebuild: -# ZIGFLAGS=-Dprebuilt_v8_path=/path/to/libc_v8.a make test +# Extra flags forwarded to every `$(ZIG) build` invocation, e.g.: +# ZIGFLAGS=-Ddev_fast=false make test ZIGFLAGS ?= # OS and ARCH @@ -36,7 +35,8 @@ endif # Prebuilt V8 # ----------- # Building V8 from source takes 10+ minutes. `make download-v8` fetches the -# matching prebuilt archive from the zig-v8-fork releases instead. The versions +# matching prebuilt archive from the zig-v8-fork releases instead; build.zig +# discovers the cached files itself, so the target only fetches. The versions # are read from the install action so they can't drift from CI. # # The cache path is keyed on ZIG_V8_TAG as well as the archive name: a @@ -57,14 +57,6 @@ V8_CACHE := .lp-cache/prebuilt-v8/$(ZIG_V8_TAG)/$(V8_ARCHIVE) V8_SO_ASSET := libc_v8_$(V8_VERSION)_$(OS)_$(ARCH).so V8_SO_CACHE := .lp-cache/prebuilt-v8/$(ZIG_V8_TAG)/libc_v8.so -# If the prebuilt archive is in place and the caller hasn't set ZIGFLAGS, point -# the build at it rather than building V8 from source. -ifeq ($(strip $(ZIGFLAGS)),) - ifneq ($(wildcard $(V8_CACHE)),) - ZIGFLAGS := -Dprebuilt_v8_path=$(V8_CACHE) - endif -endif - # Infos # ----- @@ -85,9 +77,9 @@ help: # $(ZIG) commands # ------------ -.PHONY: build build-v8-snapshot build-dev download-v8 download-v8-shared run run-release test bench data end2end clean +.PHONY: build build-v8-snapshot build-dev download-v8 run run-release test bench data end2end clean -## Download the prebuilt V8 archive (skips the 10+ min source build) +## Download the prebuilt V8 libraries (skips the 10+ min source build) download-v8: @mkdir -p $(dir $(V8_CACHE)) @test -f $(V8_CACHE) || ( \ @@ -96,16 +88,14 @@ download-v8: https://github.com/lightpanda-io/zig-v8-fork/releases/download/$(ZIG_V8_TAG)/$(V8_ARCHIVE) \ || (rm -f $(V8_CACHE); printf "\033[33mDownload ERROR\033[0m\n"; exit 1) ) @printf "\033[33mV8 ready: %s\033[0m\n" "$(V8_CACHE)" - -## Download the prebuilt shared V8 used by -Ddev_fast builds (Linux x86_64) -download-v8-shared: - @mkdir -p $(dir $(V8_SO_CACHE)) +ifeq ($(OS)_$(ARCH),linux_x86_64) @test -f $(V8_SO_CACHE) || ( \ printf "\033[36mDownloading prebuilt shared V8 $(V8_VERSION) ($(ZIG_V8_TAG))...\033[0m\n"; \ curl -fL --progress-bar -o $(V8_SO_CACHE) \ https://github.com/lightpanda-io/zig-v8-fork/releases/download/$(ZIG_V8_TAG)/$(V8_SO_ASSET) \ || (rm -f $(V8_SO_CACHE); printf "\033[33mDownload ERROR\033[0m\n"; exit 1) ) @printf "\033[33mShared V8 ready: %s\033[0m\n" "$(V8_SO_CACHE)" +endif ## Build v8 snapshot build-v8-snapshot: diff --git a/build.zig b/build.zig index 13716620a..8705ae596 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire @@ -37,21 +37,19 @@ const Build = blk: { pub fn build(b: *Build) !void { const optimize = b.standardOptimizeOption(.{}); + const requested_target = b.standardTargetOptions(.{}); - // The three settings below only work as a set, so they get one knob rather - // than three defaults: the self-hosted backend needs the shared V8 (it - // cannot apply the CREL relocations in the archive) and needs zig's own CRT - // (a system crt1.o with SFrame unwind data has relocations its linker does - // not handle). Debug-only, opt-in, and a good deal faster to rebuild. - const dev_fast = b.option(bool, "dev_fast", "Linux debug builds: shared V8 + self-hosted backend. Implies -Dshared_v8, -Duse_llvm=false and a bundled-CRT target") orelse false; + const enable_tsan = b.option(bool, "tsan", "Enable Thread Sanitizer") orelse false; + const enable_asan = b.option(bool, "asan", "Enable Address Sanitizer") orelse false; + const enable_csan = b.option(std.zig.SanitizeC, "csan", "Enable C Sanitizers"); - const target = if (dev_fast) b.resolveTargetQuery(.{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .gnu, - // https://codeberg.org/ziglang/zig/issues/31272 - .glibc_version = .{ .major = 2, .minor = 43, .patch = 0 }, - }) else b.standardTargetOptions(.{}); + const prebuilt_v8_path_option = b.option([]const u8, "prebuilt_v8_path", "Path to a prebuilt libc_v8.a or libc_v8.so"); + + const dev_fast = b.option(bool, "dev_fast", "Linux debug builds: shared V8 + self-hosted backend. Implies -Dshared_v8, -Duse_llvm=false and a bundled-CRT target") orelse + (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64 and + optimize == .Debug and requested_target.query.isNative() and + !enable_tsan and !enable_asan and + (prebuilt_v8_path_option == null or std.mem.endsWith(u8, prebuilt_v8_path_option.?, ".so"))); if (dev_fast) { if (builtin.os.tag != .linux) { @@ -62,16 +60,27 @@ pub fn build(b: *Build) !void { std.debug.print("-Ddev_fast is Debug-only (optimize is {s})\n", .{@tagName(optimize)}); return error.DevFastRequiresDebug; } + if (!requested_target.query.isNative()) { + std.debug.print("-Ddev_fast builds for the host; drop -Dtarget/-Dcpu\n", .{}); + return error.DevFastRequiresNativeTarget; + } } - // dev_fast links V8 shared, so it defaults to the libc_v8.so that - // `make download-v8-shared` caches (when present) rather than a - // multi-minute from-source build. - const prebuilt_v8_path = b.option([]const u8, "prebuilt_v8_path", "Path to a prebuilt libc_v8.a or libc_v8.so") orelse - if (dev_fast) findSharedV8Cache(b) else null; + const target = if (dev_fast) b.resolveTargetQuery(.{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .gnu, + // https://codeberg.org/ziglang/zig/issues/31272 + .glibc_version = .{ .major = 2, .minor = 43, .patch = 0 }, + }) else requested_target; + + // Without an explicit -Dprebuilt_v8_path, pick up whatever `make + // download-v8` cached rather than building V8 from source. + const prebuilt_v8_path = prebuilt_v8_path_option orelse if (enable_tsan or enable_asan) null else findPrebuiltV8(b, target, dev_fast); const snapshot_path = b.option([]const u8, "snapshot_path", "Path to v8 snapshot"); const wpt_extensions = b.option(bool, "wpt_extensions", "Extend WebAPI with WPT driver behavior") orelse false; - const shared_v8 = b.option(bool, "shared_v8", "Link V8 as a shared library") orelse dev_fast; + const shared_v8 = b.option(bool, "shared_v8", "Link V8 as a shared library") orelse + (dev_fast or (prebuilt_v8_path != null and std.mem.endsWith(u8, prebuilt_v8_path.?, ".so"))); const use_llvm = b.option(bool, "use_llvm", "Use the LLVM backend") orelse !dev_fast; const version = resolveVersion(b); @@ -86,10 +95,6 @@ pub fn build(b: *Build) !void { opts.addOption(?[]const u8, "snapshot_path", snapshot_path); opts.addOption(bool, "wpt_extensions", wpt_extensions); - const enable_tsan = b.option(bool, "tsan", "Enable Thread Sanitizer") orelse false; - const enable_asan = b.option(bool, "asan", "Enable Address Sanitizer") orelse false; - const enable_csan = b.option(std.zig.SanitizeC, "csan", "Enable C Sanitizers"); - const lightpanda_module = blk: { const mod = b.addModule("lightpanda", .{ .root_source_file = b.path("src/lightpanda.zig"), @@ -251,10 +256,10 @@ pub fn build(b: *Build) !void { } } -/// Looks for the shared V8 that `make download-v8-shared` caches. The cache +/// Looks for the prebuilt V8 that `make download-v8` caches. The cache /// path is keyed on the zig-v8 release tag, read from the install action so /// it cannot drift from CI (the Makefile reads the same source of truth). -fn findSharedV8Cache(b: *Build) ?[]const u8 { +fn findPrebuiltV8(b: *Build, target: Build.ResolvedTarget, dev_fast: bool) ?[]const u8 { const io = b.graph.io; const action = std.Io.Dir.cwd().readFileAlloc( io, @@ -263,32 +268,56 @@ fn findSharedV8Cache(b: *Build) ?[]const u8 { .limited(64 * 1024), ) catch return null; - const tag = blk: { - var in_zig_v8 = false; - var lines = std.mem.splitScalar(u8, action, '\n'); - while (lines.next()) |line| { - if (std.mem.startsWith(u8, line, " ") and !std.mem.startsWith(u8, line, " ")) { - in_zig_v8 = std.mem.eql(u8, std.mem.trimEnd(u8, line, " \r"), " zig-v8:"); - continue; - } - if (!in_zig_v8) continue; - const trimmed = std.mem.trim(u8, line, " \r"); - if (std.mem.startsWith(u8, trimmed, "default:")) { - var it = std.mem.splitScalar(u8, trimmed, '\''); - _ = it.next(); - break :blk it.next() orelse return null; - } + const tag = actionDefault(action, "zig-v8:") orelse return null; + if (tag.len == 0) { + return null; + } + + // The .so must keep the name the exe's DT_NEEDED records; the archive + // name encodes V8 version, os and arch. + const path = if (dev_fast) + b.fmt("{s}/prebuilt-v8/{s}/libc_v8.so", .{ b.pathFromRoot(".lp-cache"), tag }) + else blk: { + const version = actionDefault(action, "v8:") orelse return null; + break :blk b.fmt("{s}/prebuilt-v8/{s}/libc_v8_{s}_{s}_{s}.a", .{ + b.pathFromRoot(".lp-cache"), + tag, + version, + @tagName(target.result.os.tag), + @tagName(target.result.cpu.arch), + }); + }; + std.Io.Dir.cwd().access(io, path, .{}) catch { + if (dev_fast) { + std.debug.print("No cached libc_v8.so; building V8 from source. `make download-v8` skips this.\n", .{}); } return null; }; - if (tag.len == 0) return null; - - const path = b.fmt("{s}/prebuilt-v8/{s}/libc_v8.so", .{ b.pathFromRoot(".lp-cache"), tag }); - std.Io.Dir.cwd().access(io, path, .{}) catch return null; - std.debug.print("Using prebuilt shared V8: {s}\n", .{path}); + std.debug.print("Using prebuilt V8: {s}\n", .{path}); return path; } +// Returns the quoted `default:` value of a top-level `key` in the install +// action's yaml. +fn actionDefault(action: []const u8, key: []const u8) ?[]const u8 { + var in_key = false; + var lines = std.mem.splitScalar(u8, action, '\n'); + while (lines.next()) |line| { + if (std.mem.startsWith(u8, line, " ") and !std.mem.startsWith(u8, line, " ")) { + in_key = std.mem.eql(u8, std.mem.trimEnd(u8, line[2..], " \r"), key); + continue; + } + if (!in_key) continue; + const trimmed = std.mem.trim(u8, line, " \r"); + if (std.mem.startsWith(u8, trimmed, "default:")) { + var it = std.mem.splitScalar(u8, trimmed, '\''); + _ = it.next(); + return it.next(); + } + } + return null; +} + fn linkV8( b: *Build, mod: *Build.Module,