From 6b936002cc1ace8b59c40167d26c85d98c7c8a21 Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 25 May 2025 14:14:28 +0200 Subject: [PATCH] build: make build.zig generate helptags without running "nvim" binary This is matters for cross-compiling where we might not be able to run the "nvim" binary on the host. Instead reimplement the helptags extractor as a small lua script, which we can run on the host using the nlua0 helper already used for other generator scripts. --- .github/workflows/test.yml | 6 ++- build.zig | 11 ++++-- runtime/gen_runtime.zig | 14 +++---- src/build_lua.zig | 2 + src/gen/gen_helptags.lua | 78 ++++++++++++++++++++++++++++++++++++++ src/nlua0.zig | 5 +++ 6 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 src/gen/gen_helptags.lua diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 481f75efd6..296a115512 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -217,9 +217,13 @@ jobs: version: 0.14.1 - run: sudo apt-get install -y inotify-tools - run: zig build test_nlua0 - - run: zig build nvim_bin && ./zig-out/bin/nvim --version + - run: zig build nvim && ./zig-out/bin/nvim --version - run: zig build unittest - run: zig build functionaltest + # `zig build nvim` uses a lua script for doctags in order to support cross-compiling + # compare with the builtin generator that they match + - run: cd runtime; ../zig-out/bin/nvim -u NONE -i NONE -e --headless -c "helptags ++t doc" -c quit + - run: diff -u runtime/doc/tags zig-out/runtime/doc/tags windows: uses: ./.github/workflows/test_windows.yml diff --git a/build.zig b/build.zig index 4a4c652680..ea1d45077b 100644 --- a/build.zig +++ b/build.zig @@ -82,15 +82,20 @@ pub fn build(b: *std.Build) !void { const libuv_dep = b.dependency("libuv", .{ .target = target, .optimize = optimize }); const libuv = libuv_dep.artifact("uv"); - const libluv = try build_lua.build_libluv(b, target, optimize, lua, libuv); + const libluv_host = if (cross_compiling) libluv_host: { + const libuv_dep_host = b.dependency("libuv", .{ .target = target_host, .optimize = optimize_host }); + const libuv_host = libuv_dep_host.artifact("uv"); + break :libluv_host try build_lua.build_libluv(b, target_host, optimize_host, ziglua_host.artifact("lua"), libuv_host); + } else libluv; + const utf8proc = b.dependency("utf8proc", .{ .target = target, .optimize = optimize }); const unibilium = b.dependency("unibilium", .{ .target = target, .optimize = optimize }); // TODO(bfredl): fix upstream bugs with UBSAN const treesitter = b.dependency("treesitter", .{ .target = target, .optimize = .ReleaseFast }); - const nlua0 = build_lua.build_nlua0(b, target_host, optimize_host, host_use_luajit, ziglua_host, lpeg); + const nlua0 = build_lua.build_nlua0(b, target_host, optimize_host, host_use_luajit, ziglua_host, lpeg, libluv_host); // usual caveat emptor: might need to force a rebuild if the only change is // addition of new .c files, as those are not seen by any hash @@ -331,7 +336,7 @@ pub fn build(b: *std.Build) !void { nvim_exe_step.dependOn(&nvim_exe_install.step); - const gen_runtime = try runtime.nvim_gen_runtime(b, nlua0, nvim_exe, funcs_data); + const gen_runtime = try runtime.nvim_gen_runtime(b, nlua0, funcs_data); const runtime_install = b.addInstallDirectory(.{ .source_dir = gen_runtime.getDirectory(), .install_dir = .prefix, .install_subdir = "runtime/" }); const nvim = b.step("nvim", "build the editor"); diff --git a/runtime/gen_runtime.zig b/runtime/gen_runtime.zig index 1bf7bfde38..76033b6d1c 100644 --- a/runtime/gen_runtime.zig +++ b/runtime/gen_runtime.zig @@ -6,7 +6,6 @@ pub const SourceItem = struct { name: []u8, api_export: bool }; pub fn nvim_gen_runtime( b: *std.Build, nlua0: *std.Build.Step.Compile, - nvim_bin: *std.Build.Step.Compile, funcs_data: LazyPath, ) !*std.Build.Step.WriteFile { const gen_runtime = b.addWriteFiles(); @@ -25,14 +24,13 @@ pub fn nvim_gen_runtime( { const install_doc_files = b.addInstallDirectory(.{ .source_dir = b.path("runtime/doc"), .install_dir = .prefix, .install_subdir = "runtime/doc" }); - const gen_step = b.addRunArtifact(nvim_bin); - gen_step.step.dependOn(&install_doc_files.step); - gen_step.addArgs(&.{ "-u", "NONE", "-i", "NONE", "-e", "--headless", "-c", "helptags ++t doc", "-c", "quit" }); - // TODO(bfredl): ugly on purpose. nvim should be able to generate "tags" at a specificed destination - const install_path: std.Build.LazyPath = .{ .cwd_relative = b.install_path }; - gen_step.setCwd(install_path.path(b, "runtime/")); + gen_runtime.step.dependOn(&install_doc_files.step); - gen_runtime.step.dependOn(&gen_step.step); + const gen_step = b.addRunArtifact(nlua0); + gen_step.addFileArg(b.path("src/gen/gen_helptags.lua")); + const file = gen_step.addOutputFileArg("tags"); + _ = gen_runtime.addCopyFile(file, "doc/tags"); + gen_step.addDirectoryArg(b.path("runtime/doc")); } return gen_runtime; diff --git a/src/build_lua.zig b/src/build_lua.zig index 7bef6049bd..5a2059fc00 100644 --- a/src/build_lua.zig +++ b/src/build_lua.zig @@ -8,6 +8,7 @@ pub fn build_nlua0( use_luajit: bool, ziglua: *std.Build.Dependency, lpeg: *std.Build.Dependency, + libluv: *std.Build.Step.Compile, ) *std.Build.Step.Compile { const options = b.addOptions(); options.addOption(bool, "use_luajit", use_luajit); @@ -39,6 +40,7 @@ pub fn build_nlua0( mod.addImport("embedded_data", embedded_data); // addImport already links by itself. but we need headers as well.. mod.linkLibrary(ziglua.artifact("lua")); + mod.linkLibrary(libluv); mod.addOptions("options", options); diff --git a/src/gen/gen_helptags.lua b/src/gen/gen_helptags.lua new file mode 100644 index 0000000000..eda21e5425 --- /dev/null +++ b/src/gen/gen_helptags.lua @@ -0,0 +1,78 @@ +-- Does the same as `nvim -c "helptags ++t doc" -c quit` +-- without needing to run a "nvim" binary, which is needed for cross-compiling. +local out = arg[1] +local dir = arg[2] + +local dirfd = vim.uv.fs_opendir(dir, nil, 1) +local files = {} +while true do + local file = dirfd:readdir() + if file == nil then + break + end + if file[1].type == 'file' and vim.endswith(file[1].name, '.txt') then + table.insert(files, file[1].name) + end +end + +local tags = {} +for _, fn in ipairs(files) do + local in_example = false + for line in io.lines(dir .. '/' .. fn) do + if in_example then + local first = string.sub(line, 1, 1) + if first ~= ' ' and first ~= '\t' and first ~= '' then + in_example = false + end + end + local chunks = vim.split(line, '*', { plain = true }) + local next_valid = false + local n_chunks = #chunks + for i, chunk in ipairs(chunks) do + if next_valid and not in_example then + if #chunk > 0 and string.find(chunk, '[ \t|]') == nil then + local next = string.sub(chunks[i + 1], 1, 1) + if next == ' ' or next == '\t' or (i == n_chunks - 1 and next == '') then + table.insert(tags, { chunk, fn }) + end + end + end + + if i == n_chunks - 1 then + break + end + next_valid = false + local lastend = string.sub(chunk, -1) -- "" for empty string + if lastend == ' ' or lastend == '\t' or (i == 1 and lastend == '') then + next_valid = true + end + end + + if line == '>' or vim.endswith(line, ' >') then + in_example = true + end + end +end + +table.insert(tags, { 'help-tags', 'tags' }) +table.sort(tags, function(a, b) + return a[1] < b[1] +end) + +local f = io.open(out, 'w') +local lasttagname, lastfn = nil +for _, tag in ipairs(tags) do + local tagname, fn = unpack(tag) + if tagname == lasttagname then + error('duplicate tags in ' .. fn .. (lastfn ~= fn and (' and ' .. lastfn) or '')) + end + lasttagname, lastfn = tagname, fn + + if tagname == 'help-tags' then + f:write(tagname .. '\t' .. fn .. '\t1\n') + else + local escaped = string.gsub(tagname, '[\\/]', '\\%0') + f:write(tagname .. '\t' .. fn .. '\t/*' .. escaped .. '*\n') + end +end +f:close() diff --git a/src/nlua0.zig b/src/nlua0.zig index 042ff01609..cdd30506b5 100644 --- a/src/nlua0.zig +++ b/src/nlua0.zig @@ -20,6 +20,7 @@ const Lua = ziglua.Lua; extern "c" fn luaopen_mpack(ptr: *anyopaque) c_int; extern "c" fn luaopen_lpeg(ptr: *anyopaque) c_int; extern "c" fn luaopen_bit(ptr: *anyopaque) c_int; +extern "c" fn luaopen_luv(ptr: *anyopaque) c_int; fn init() !*Lua { // Initialize the Lua vm @@ -56,6 +57,10 @@ fn init() !*Lua { if (retval2 != 1) return error.LoadError; lua.setField(-3, "lpeg"); + const retval3 = luaopen_luv(lua); + if (retval3 != 1) return error.LoadError; + lua.setField(-3, "uv"); + lua.pop(2); if (!options.use_luajit) {