commit 4a1295c6264864336a656bfbc69d25e620b8b814
parent 27bc7dcb019571b2e2ce1005122f9f4406b7dc98
Author: bfredl <bjorn.linse@gmail.com>
Date: Wed, 20 Aug 2025 09:10:02 +0200
Merge pull request #34186 from bfredl/neotags
build: generate helptags without running "nvim" binary
Diffstat:
6 files changed, 104 insertions(+), 12 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) {