neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 6082b7f850b592a9d2e3a55b00b22dc862ad1858
parent 405c6c9bb03fbb270284937f95fb9cf14f4c40dd
Author: bfredl <bjorn.linse@gmail.com>
Date:   Thu, 15 Jan 2026 11:07:40 +0100

Merge pull request #37040 from p00f/push-nwlkmnvmmlrt

feat(build.zig): add option to use system dependencies
Diffstat:
M.editorconfig | 3+++
MBUILD.md | 16++++++++++++++++
Mbuild.zig | 424++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mbuild.zig.zon | 11++++++++---
Mdeps/unibilium/build.zig | 19++++++++++---------
Mdeps/unibilium/build.zig.zon | 1+
Mdeps/utf8proc/build.zig | 15++++++++-------
Mdeps/utf8proc/build.zig.zon | 1+
Msrc/build_lua.zig | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
9 files changed, 499 insertions(+), 132 deletions(-)

diff --git a/.editorconfig b/.editorconfig @@ -7,6 +7,9 @@ tab_width = 8 end_of_line = lf insert_final_newline = true +[*.zig] +indent_size = 4 + [*.{c,h,in,lua}] max_line_length = 100 diff --git a/BUILD.md b/BUILD.md @@ -542,3 +542,19 @@ make CMAKE_BUILD_TYPE=Release MACOSX_DEPLOYMENT_TARGET=10.13 DEPS_CMAKE_FLAGS="- Note that the C++ compiler is explicitly set so that it can be found when the deployment target is set. +## Building with zig +### Prerequisites + - zig 0.15.2 +### Instructions + - Build the editor: `zig build`, run it with `./zig-out/bin/nvim` + - Complete installation with runtime: `zig build install --prefix ~/.local` + - Tests: + + `zig build functionaltest` to run all functionaltests + + `zig build functionaltest -- test/functional/autocmd/bufenter_spec.lua` to run the tests in one file + + `zig build unittest` to run all unittests +#### Using system dependencies + See "Available System Integrations" in `zig build -h` to see available system integrations. Enabling an integration, e.g. `zig build -fsys=utf8proc` will use the system's installation of utf8proc. + +`zig build --system deps_dir` will enable all integrations and turn off dependency fetching. This requires you to pre-download the dependencies which don't have a system integration into `deps_dir` (at the time of writing these are ziglua, [`lua_dev_deps`](https://github.com/neovim/deps/blob/master/opt/lua-dev-deps.tar.gz), and the built-in tree-sitter parsers). You have to create subdirectories whose names are the respective package's hash under `deps_dir` and unpack the dependencies inside that directory - ziglua should go under `deps_dir/zlua-0.1.0-hGRpC1dCBQDf-IqqUifYvyr8B9-4FlYXqY8cl7HIetrC` and so on. Hashes should be taken from `build.zig.zon`. + +See the `prepare` function of [this `PKGBUILD`](https://git.sr.ht/~chinmay/nvim_build/tree/26364a4cf9b4819f52a3e785fa5a43285fb9cea2/item/PKGBUILD#L90) for an example. diff --git a/build.zig b/build.zig @@ -1,5 +1,6 @@ const std = @import("std"); const LazyPath = std.Build.LazyPath; +const Compile = std.Build.Step.Compile; const build_lua = @import("src/build_lua.zig"); const gen = @import("src/gen/gen_steps.zig"); const runtime = @import("runtime/gen_runtime.zig"); @@ -16,6 +17,15 @@ const version = struct { const api_prerelease = true; }; +pub const SystemIntegrationOptions = packed struct { + lpeg: bool, + lua: bool, + tree_sitter: bool, + unibilium: bool, + utf8proc: bool, + uv: bool, +}; + // TODO(bfredl): this is for an upstream issue pub fn lazyArtifact(d: *std.Build.Dependency, name: []const u8) ?*std.Build.Step.Compile { var found: ?*std.Build.Step.Compile = null; @@ -54,51 +64,99 @@ pub fn build(b: *std.Build) !void { const arch = t.cpu.arch; const default_luajit = (is_linux and arch == .x86_64) or (is_darwin and arch == .aarch64); const use_luajit = b.option(bool, "luajit", "use luajit") orelse default_luajit; + const lualib_name = if (use_luajit) "luajit" else "lua5.1"; const host_use_luajit = if (cross_compiling) false else use_luajit; const E = enum { luajit, lua51 }; + const system_integration_options = SystemIntegrationOptions{ + .lpeg = b.systemIntegrationOption("lpeg", .{}), + .lua = b.systemIntegrationOption("lua", .{}), + .tree_sitter = b.systemIntegrationOption("tree-sitter", .{}), + .unibilium = b.systemIntegrationOption("unibilium", .{}), + .utf8proc = b.systemIntegrationOption("utf8proc", .{}), + .uv = b.systemIntegrationOption("uv", .{}), + }; + const ziglua = b.dependency("zlua", .{ .target = target, .optimize = optimize_lua, .lang = if (use_luajit) E.luajit else E.lua51, .shared = false, + .system_lua = system_integration_options.lua, }); - const ziglua_host = if (cross_compiling) b.dependency("zlua", .{ .target = target_host, .optimize = .ReleaseSmall, .lang = if (host_use_luajit) E.luajit else E.lua51, + .system_lua = system_integration_options.lua, .shared = false, }) else ziglua; - - const lpeg = b.dependency("lpeg", .{}); - - const iconv = if (is_windows or is_darwin) b.lazyDependency("libiconv", .{ .target = target, .optimize = optimize }) else null; - - // this is currently not necessary, as ziglua currently doesn't use lazy dependencies - // to circumvent ziglua.artifact() failing in a bad way. - const lua = lazyArtifact(ziglua, "lua") orelse return; - if (cross_compiling) { - _ = lazyArtifact(ziglua_host, "lua") orelse return; + var lua: ?*Compile = null; + var libuv: ?*Compile = null; + var libluv: ?*Compile = null; + var libluv_host: ?*Compile = null; + if (!system_integration_options.lua) { + // this is currently not necessary, as ziglua currently doesn't use lazy dependencies + // to circumvent ziglua.artifact() failing in a bad way. + lua = lazyArtifact(ziglua, "lua") orelse return; + if (cross_compiling) { + _ = lazyArtifact(ziglua_host, "lua") orelse return; + } + } + if (!system_integration_options.uv) { + if (b.lazyDependency("libuv", .{ .target = target, .optimize = optimize })) |dep| { + libuv = dep.artifact("uv"); + libluv = try build_lua.build_libluv(b, target, optimize, lua, libuv.?, use_luajit); + + libluv_host = if (cross_compiling) libluv_host: { + const libuv_dep_host = b.lazyDependency("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, + host_use_luajit, + ); + } else libluv; + } } - // const lua = ziglua.artifact("lua"); - 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 lpeg = if (system_integration_options.lpeg) null else b.lazyDependency("lpeg", .{}); - 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 iconv = if (is_windows or is_darwin) b.lazyDependency("libiconv", .{ + .target = target, + .optimize = optimize, + }) else null; - const utf8proc = b.dependency("utf8proc", .{ .target = target, .optimize = optimize }); - const unibilium = if (use_unibilium) b.lazyDependency("unibilium", .{ .target = target, .optimize = optimize }) else null; + const utf8proc = if (system_integration_options.utf8proc) null else b.lazyDependency("utf8proc", .{ + .target = target, + .optimize = optimize, + }); + const unibilium = if (use_unibilium and !system_integration_options.unibilium) b.lazyDependency("unibilium", .{ + .target = target, + .optimize = optimize, + }) else null; // TODO(bfredl): fix upstream bugs with UBSAN - const treesitter = b.dependency("treesitter", .{ .target = target, .optimize = .ReleaseFast }); + const treesitter = if (system_integration_options.tree_sitter) null else b.lazyDependency("treesitter", .{ + .target = target, + .optimize = .ReleaseFast, + }); - const nlua0 = build_lua.build_nlua0(b, target_host, optimize_host, host_use_luajit, ziglua_host, lpeg, libluv_host); + const nlua0 = try build_lua.build_nlua0( + b, + target_host, + optimize_host, + host_use_luajit, + ziglua_host, + lpeg, + libluv_host, + system_integration_options, + ); // 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 @@ -127,7 +185,14 @@ pub fn build(b: *std.Build) !void { var api_headers = try std.ArrayList(std.Build.LazyPath).initCapacity(b.allocator, 10); // TODO(bfredl): these should just become subdirs.. - const windows_only = [_][]const u8{ "pty_proc_win.c", "pty_proc_win.h", "pty_conpty_win.c", "pty_conpty_win.h", "os_win_console.c", "win_defs.h" }; + const windows_only = [_][]const u8{ + "pty_proc_win.c", + "pty_proc_win.h", + "pty_conpty_win.c", + "pty_conpty_win.h", + "os_win_console.c", + "win_defs.h", + }; const unix_only = [_][]const u8{ "unix_defs.h", "pty_proc_unix.c", "pty_proc_unix.h" }; const exclude_list = if (is_windows) &unix_only else &windows_only; @@ -149,7 +214,10 @@ pub fn build(b: *std.Build) !void { } } if (std.mem.eql(u8, ".c", entry.name[entry.name.len - 2 ..])) { - try nvim_sources.append(b.allocator, .{ .name = b.fmt("{s}{s}", .{ s, entry.name }), .api_export = api_export }); + try nvim_sources.append(b.allocator, .{ + .name = b.fmt("{s}{s}", .{ s, entry.name }), + .api_export = api_export, + }); } if (std.mem.eql(u8, ".h", entry.name[entry.name.len - 2 ..])) { try nvim_headers.append(b.allocator, b.fmt("{s}{s}", .{ s, entry.name })); @@ -168,10 +236,16 @@ pub fn build(b: *std.Build) !void { var config_str = b.fmt("zig build -Doptimize={s}", .{@tagName(optimize)}); if (cross_compiling) { - config_str = b.fmt("{s} -Dcross -Dtarget={s} (host: {s})", .{ config_str, try t.linuxTriple(b.allocator), try b.graph.host.result.linuxTriple(b.allocator) }); + config_str = b.fmt("{s} -Dcross -Dtarget={s} (host: {s})", .{ + config_str, + try t.linuxTriple(b.allocator), + try b.graph.host.result.linuxTriple(b.allocator), + }); } - const versiondef_step = b.addConfigHeader(.{ .style = .{ .cmake = b.path("cmake.config/versiondef.h.in") } }, .{ + const versiondef_step = b.addConfigHeader(.{ + .style = .{ .cmake = b.path("cmake.config/versiondef.h.in") }, + }, .{ .NVIM_VERSION_MAJOR = version.major, .NVIM_VERSION_MINOR = version.minor, .NVIM_VERSION_PATCH = version.patch, @@ -183,7 +257,9 @@ pub fn build(b: *std.Build) !void { _ = gen_config.addCopyFile(versiondef_step.getOutput(), "auto/versiondef.h"); // run_preprocessor() workaronnd const ptrwidth = t.ptrBitWidth() / 8; - const sysconfig_step = b.addConfigHeader(.{ .style = .{ .cmake = b.path("cmake.config/config.h.in") } }, .{ + const sysconfig_step = b.addConfigHeader(.{ + .style = .{ .cmake = b.path("cmake.config/config.h.in") }, + }, .{ .SIZEOF_INT = t.cTypeByteSize(.int), .SIZEOF_INTMAX_T = t.cTypeByteSize(.longlong), // TODO .SIZEOF_LONG = t.cTypeByteSize(.long), @@ -228,6 +304,9 @@ pub fn build(b: *std.Build) !void { .VTERM_TEST_FILE = "test/vterm_test_output", // TODO(bfredl): revisit when porting libvterm tests }); + const system_install_path = b.option([]const u8, "install-path", "Install path (for packagers)"); + const install_path = system_install_path orelse b.install_path; + const lib_dir = if (system_install_path) |path| b.fmt("{s}/lib", .{path}) else b.lib_dir; _ = gen_config.addCopyFile(sysconfig_step.getOutput(), "auto/config.h"); // run_preprocessor() workaronnd _ = gen_config.add("auto/pathdef.h", b.fmt( @@ -235,12 +314,20 @@ pub fn build(b: *std.Build) !void { \\char *default_vimruntime_dir = ""; \\char *default_lib_dir = "{s}/nvim"; // b.lib_dir is typically b.install_path + "/lib" but may be overridden - , .{ try replace_backslashes(b, b.install_path), try replace_backslashes(b, b.lib_dir) })); + , .{ try replace_backslashes(b, install_path), try replace_backslashes(b, lib_dir) })); - const opt_version_string = b.option([]const u8, "version-string", "Override Neovim version string. Default is to find out with git."); + const opt_version_string = b.option( + []const u8, + "version-string", + "Override Neovim version string. Default is to find out with git.", + ); const version_medium = if (opt_version_string) |version_string| version_string else v: { var code: u8 = undefined; - const version_string = b.fmt("v{d}.{d}.{d}", .{ version.major, version.minor, version.patch }); + const version_string = b.fmt("v{d}.{d}.{d}", .{ + version.major, + version.minor, + version.patch, + }); const git_describe_untrimmed = b.runAllowFail(&[_][]const u8{ "git", "-C", b.build_root.path orelse ".", // affects the --git-dir argument @@ -282,51 +369,123 @@ pub fn build(b: *std.Build) !void { // TODO(zig): using getEmittedIncludeTree() is ugly af. we want unittests // to reuse the std.build.Module include_path thing - const unittest_include_path = [_]LazyPath{ - b.path("src/"), - gen_config.getDirectory(), - lua.getEmittedIncludeTree(), - libuv.getEmittedIncludeTree(), - libluv.getEmittedIncludeTree(), - utf8proc.artifact("utf8proc").getEmittedIncludeTree(), - if (unibilium) |u| u.artifact("unibilium").getEmittedIncludeTree() else b.path("UNUSED_PATH/"), // :p - treesitter.artifact("tree-sitter").getEmittedIncludeTree(), - if (iconv) |dep| dep.artifact("iconv").getEmittedIncludeTree() else b.path("UNUSED_PATH/"), - }; + var unittest_include_path: std.ArrayList(LazyPath) = try .initCapacity(b.allocator, 2); + try unittest_include_path.append(b.allocator, b.path("src/")); + try unittest_include_path.append(b.allocator, gen_config.getDirectory()); + if (system_integration_options.lua) { + try appendSystemIncludePath(b, &unittest_include_path, lualib_name); + } else if (lua) |compile| { + try unittest_include_path.append(b.allocator, compile.getEmittedIncludeTree()); + } + if (system_integration_options.uv) { + try appendSystemIncludePath(b, &unittest_include_path, "libuv"); + try appendSystemIncludePath(b, &unittest_include_path, "libluv"); + } else { + if (libuv) |compile| try unittest_include_path.append(b.allocator, compile.getEmittedIncludeTree()); + if (libluv) |compile| try unittest_include_path.append(b.allocator, compile.getEmittedIncludeTree()); + } + if (system_integration_options.utf8proc) { + try appendSystemIncludePath(b, &unittest_include_path, "libutf8proc"); + } else if (utf8proc) |dep| { + try unittest_include_path.append(b.allocator, dep.artifact("utf8proc").getEmittedIncludeTree()); + } + if (use_unibilium) { + if (system_integration_options.unibilium) { + try appendSystemIncludePath(b, &unittest_include_path, "unibilium"); + } else if (unibilium) |dep| { + try unittest_include_path.append(b.allocator, dep.artifact("unibilium").getEmittedIncludeTree()); + } + } + if (system_integration_options.tree_sitter) { + try appendSystemIncludePath(b, &unittest_include_path, "tree-sitter"); + } else if (treesitter) |dep| { + try unittest_include_path.append(b.allocator, dep.artifact("tree-sitter").getEmittedIncludeTree()); + } + if (iconv) |dep| { + try unittest_include_path.append(b.allocator, dep.artifact("iconv").getEmittedIncludeTree()); + } - const gen_headers, const funcs_data = try gen.nvim_gen_sources(b, nlua0, &nvim_sources, &nvim_headers, &api_headers, versiondef_git, version_lua); + const gen_headers, const funcs_data = try gen.nvim_gen_sources( + b, + nlua0, + &nvim_sources, + &nvim_headers, + &api_headers, + versiondef_git, + version_lua, + ); const test_config_step = b.addWriteFiles(); _ = test_config_step.add("test/cmakeconfig/paths.lua", try test_config(b)); const test_gen_step = b.step("gen_headers", "debug: output generated headers"); - const config_install = b.addInstallDirectory(.{ .source_dir = gen_config.getDirectory(), .install_dir = .prefix, .install_subdir = "config/" }); + const config_install = b.addInstallDirectory(.{ + .source_dir = gen_config.getDirectory(), + .install_dir = .prefix, + .install_subdir = "config/", + }); test_gen_step.dependOn(&config_install.step); - test_gen_step.dependOn(&b.addInstallDirectory(.{ .source_dir = gen_headers.getDirectory(), .install_dir = .prefix, .install_subdir = "headers/" }).step); + test_gen_step.dependOn(&b.addInstallDirectory(.{ + .source_dir = gen_headers.getDirectory(), + .install_dir = .prefix, + .install_subdir = "headers/", + }).step); const nvim_exe = b.addExecutable(.{ .name = "nvim", .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), }); nvim_exe.rdynamic = true; // -E - nvim_exe.linkLibrary(lua); - nvim_exe.linkLibrary(libuv); - nvim_exe.linkLibrary(libluv); + if (system_integration_options.lua) { + nvim_exe.root_module.linkSystemLibrary(lualib_name, .{}); + } else if (lua) |compile| { + nvim_exe.root_module.linkLibrary(compile); + } + if (system_integration_options.uv) { + nvim_exe.root_module.linkSystemLibrary("libuv", .{}); + nvim_exe.root_module.linkSystemLibrary("libluv", .{}); + } else { + if (libuv) |compile| nvim_exe.root_module.linkLibrary(compile); + if (libluv) |compile| nvim_exe.root_module.linkLibrary(compile); + } if (iconv) |dep| nvim_exe.linkLibrary(dep.artifact("iconv")); - nvim_exe.linkLibrary(utf8proc.artifact("utf8proc")); - if (unibilium) |u| nvim_exe.linkLibrary(u.artifact("unibilium")); - nvim_exe.linkLibrary(treesitter.artifact("tree-sitter")); + if (system_integration_options.utf8proc) { + nvim_exe.root_module.linkSystemLibrary("utf8proc", .{}); + } else if (utf8proc) |dep| { + nvim_exe.root_module.linkLibrary(dep.artifact("utf8proc")); + } + if (use_unibilium) { + if (system_integration_options.unibilium) { + nvim_exe.root_module.linkSystemLibrary("unibilium", .{}); + } else if (unibilium) |dep| { + nvim_exe.root_module.linkLibrary(dep.artifact("unibilium")); + } + } + if (system_integration_options.tree_sitter) { + nvim_exe.root_module.linkSystemLibrary("tree-sitter", .{}); + } else if (treesitter) |dep| { + nvim_exe.root_module.linkLibrary(dep.artifact("tree-sitter")); + } if (is_windows) { nvim_exe.linkSystemLibrary("netapi32"); } nvim_exe.addIncludePath(b.path("src")); nvim_exe.addIncludePath(gen_config.getDirectory()); nvim_exe.addIncludePath(gen_headers.getDirectory()); - build_lua.add_lua_modules(nvim_exe.root_module, lpeg, use_luajit, false); + try build_lua.add_lua_modules( + b, + t, + nvim_exe.root_module, + lpeg, + use_luajit, + false, + system_integration_options, + ); var unit_test_sources = try std.ArrayList([]u8).initCapacity(b.allocator, 10); if (support_unittests) { @@ -388,7 +547,11 @@ pub fn build(b: *std.Build) !void { const test_deps = b.step("test_deps", "test prerequisites"); test_deps.dependOn(&nvim_exe_install.step); // running tests doesn't require copying the static runtime, only the generated stuff - const test_runtime_install = b.addInstallDirectory(.{ .source_dir = gen_runtime.getDirectory(), .install_dir = .prefix, .install_subdir = "runtime/" }); + const test_runtime_install = b.addInstallDirectory(.{ + .source_dir = gen_runtime.getDirectory(), + .install_dir = .prefix, + .install_subdir = "runtime/", + }); test_deps.dependOn(&test_runtime_install.step); const nvim_dev = b.step("nvim_dev", "build the editor for development"); @@ -412,15 +575,41 @@ pub fn build(b: *std.Build) !void { // installation const install = b.getInstallStep(); install.dependOn(&nvim_exe_install.step); - b.installDirectory(.{ .source_dir = b.path("runtime/"), .install_dir = .prefix, .install_subdir = "share/nvim/runtime/" }); - b.installDirectory(.{ .source_dir = gen_runtime.getDirectory(), .install_dir = .prefix, .install_subdir = "share/nvim/runtime/" }); + b.installDirectory(.{ + .source_dir = b.path("runtime/"), + .install_dir = .prefix, + .install_subdir = "share/nvim/runtime/", + }); + b.installDirectory(.{ + .source_dir = gen_runtime.getDirectory(), + .install_dir = .prefix, + .install_subdir = "share/nvim/runtime/", + }); - test_deps.dependOn(test_fixture(b, "shell-test", null, target, optimize, &flags)); - test_deps.dependOn(test_fixture(b, "tty-test", libuv, target, optimize, &flags)); - test_deps.dependOn(test_fixture(b, "pwsh-test", null, target, optimize, &flags)); - test_deps.dependOn(test_fixture(b, "printargs-test", null, target, optimize, &flags)); - test_deps.dependOn(test_fixture(b, "printenv-test", null, target, optimize, &flags)); - test_deps.dependOn(test_fixture(b, "streams-test", libuv, target, optimize, &flags)); + test_deps.dependOn(test_fixture(b, "shell-test", false, false, null, target, optimize, &flags)); + test_deps.dependOn(test_fixture( + b, + "tty-test", + true, + system_integration_options.uv, + libuv, + target, + optimize, + &flags, + )); + test_deps.dependOn(test_fixture(b, "pwsh-test", false, false, null, target, optimize, &flags)); + test_deps.dependOn(test_fixture(b, "printargs-test", false, false, null, target, optimize, &flags)); + test_deps.dependOn(test_fixture(b, "printenv-test", false, false, null, target, optimize, &flags)); + test_deps.dependOn(test_fixture( + b, + "streams-test", + true, + system_integration_options.uv, + libuv, + target, + optimize, + &flags, + )); // xxd - hex dump utility (vendored from Vim) const xxd_exe = b.addExecutable(.{ @@ -435,27 +624,51 @@ pub fn build(b: *std.Build) !void { test_deps.dependOn(&b.addInstallArtifact(xxd_exe, .{}).step); const parser_c = b.dependency("treesitter_c", .{ .target = target, .optimize = optimize }); - test_deps.dependOn(add_ts_parser(b, "c", parser_c.path("."), false, target, optimize)); + test_deps.dependOn(add_ts_parser(b, "c", parser_c.path("."), false, target, optimize, .test_)); + install.dependOn(add_ts_parser(b, "c", parser_c.path("."), false, target, optimize, .install)); + const parser_markdown = b.dependency("treesitter_markdown", .{ .target = target, .optimize = optimize }); - test_deps.dependOn(add_ts_parser(b, "markdown", parser_markdown.path("tree-sitter-markdown/"), true, target, optimize)); - test_deps.dependOn(add_ts_parser(b, "markdown_inline", parser_markdown.path("tree-sitter-markdown-inline/"), true, target, optimize)); + test_deps.dependOn(add_ts_parser(b, "markdown", parser_markdown.path("tree-sitter-markdown/"), true, target, optimize, .test_)); + install.dependOn(add_ts_parser(b, "markdown", parser_markdown.path("tree-sitter-markdown/"), true, target, optimize, .install)); + test_deps.dependOn(add_ts_parser(b, "markdown_inline", parser_markdown.path("tree-sitter-markdown-inline/"), true, target, optimize, .test_)); + install.dependOn(add_ts_parser(b, "markdown_inline", parser_markdown.path("tree-sitter-markdown-inline/"), true, target, optimize, .install)); + const parser_vim = b.dependency("treesitter_vim", .{ .target = target, .optimize = optimize }); - test_deps.dependOn(add_ts_parser(b, "vim", parser_vim.path("."), true, target, optimize)); + test_deps.dependOn(add_ts_parser(b, "vim", parser_vim.path("."), true, target, optimize, .test_)); + install.dependOn(add_ts_parser(b, "vim", parser_vim.path("."), true, target, optimize, .install)); + const parser_vimdoc = b.dependency("treesitter_vimdoc", .{ .target = target, .optimize = optimize }); - test_deps.dependOn(add_ts_parser(b, "vimdoc", parser_vimdoc.path("."), false, target, optimize)); + test_deps.dependOn(add_ts_parser(b, "vimdoc", parser_vimdoc.path("."), false, target, optimize, .test_)); + install.dependOn(add_ts_parser(b, "vimdoc", parser_vimdoc.path("."), false, target, optimize, .install)); + const parser_lua = b.dependency("treesitter_lua", .{ .target = target, .optimize = optimize }); - test_deps.dependOn(add_ts_parser(b, "lua", parser_lua.path("."), true, target, optimize)); - const parser_query = b.dependency("treesitter_query", .{ .target = target, .optimize = optimize }); - test_deps.dependOn(add_ts_parser(b, "query", parser_query.path("."), false, target, optimize)); + test_deps.dependOn(add_ts_parser(b, "lua", parser_lua.path("."), true, target, optimize, .test_)); + install.dependOn(add_ts_parser(b, "lua", parser_lua.path("."), true, target, optimize, .install)); - const unit_headers: ?[]const LazyPath = if (support_unittests) &(unittest_include_path ++ .{gen_headers.getDirectory()}) else null; + const parser_query = b.dependency("treesitter_query", .{ .target = target, .optimize = optimize }); + test_deps.dependOn(add_ts_parser(b, "query", parser_query.path("."), false, target, optimize, .test_)); + install.dependOn(add_ts_parser(b, "query", parser_query.path("."), false, target, optimize, .install)); - try tests.test_steps(b, nvim_exe, test_deps, lua_dev_deps.path("."), test_config_step.getDirectory(), unit_headers); + var unit_headers: ?[]const LazyPath = null; + if (support_unittests) { + try unittest_include_path.append(b.allocator, gen_headers.getDirectory()); + unit_headers = unittest_include_path.items; + } + try tests.test_steps( + b, + nvim_exe, + test_deps, + lua_dev_deps.path("."), + test_config_step.getDirectory(), + unit_headers, + ); } pub fn test_fixture( b: *std.Build, name: []const u8, + use_libuv: bool, + use_system_libuv: bool, libuv: ?*std.Build.Step.Compile, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, @@ -473,9 +686,18 @@ pub fn test_fixture( fixture.mingw_unicode_entry_point = true; // uses UNICODE on WINDOWS :scream: } - fixture.addCSourceFile(.{ .file = b.path(b.fmt("./test/functional/fixtures/{s}.c", .{source})), .flags = flags }); + fixture.addCSourceFile(.{ + .file = b.path(b.fmt("./test/functional/fixtures/{s}.c", .{source})), + .flags = flags, + }); fixture.linkLibC(); - if (libuv) |uv| fixture.linkLibrary(uv); + if (use_libuv) { + if (use_system_libuv) { + fixture.root_module.linkSystemLibrary("libuv", .{}); + } else if (libuv) |uv| { + fixture.linkLibrary(uv); + } + } return &b.addInstallArtifact(fixture, .{}).step; } @@ -486,6 +708,7 @@ pub fn add_ts_parser( scanner: bool, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, + path: enum { test_, install }, ) *std.Build.Step { const parser = b.addLibrary(.{ .name = name, @@ -500,8 +723,21 @@ pub fn add_ts_parser( parser.addIncludePath(parser_dir.path(b, "src")); parser.linkLibC(); - const parser_install = b.addInstallArtifact(parser, .{ .dest_sub_path = b.fmt("parser/{s}.so", .{name}) }); - return &parser_install.step; + switch (path) { + .install => { + const parser_install = b.addInstallArtifact(parser, .{ + .dest_dir = .{ .override = .{ .custom = "share/nvim/runtime/parser" } }, + .dest_sub_path = b.fmt("{s}.so", .{name}), + }); + return &parser_install.step; + }, + .test_ => { + const parser_install = b.addInstallArtifact(parser, .{ + .dest_sub_path = b.fmt("parser/{s}.so", .{name}), + }); + return &parser_install.step; + }, + } } pub fn lua_version_info(b: *std.Build) []u8 { @@ -516,7 +752,15 @@ pub fn lua_version_info(b: *std.Build) []u8 { \\ {{"api_compatible", {}}}, \\ {{"api_prerelease", {}}}, \\}} - , .{ v.major, v.minor, v.patch, v.prerelease.len > 0, v.api_level, v.api_level_compat, v.api_prerelease }); + , .{ + v.major, + v.minor, + v.patch, + v.prerelease.len > 0, + v.api_level, + v.api_level_compat, + v.api_prerelease, + }); } /// Replace all backslashes in `input` with with forward slashes when the target is Windows. @@ -551,3 +795,29 @@ pub fn test_config(b: *std.Build) ![]u8 { \\return M , .{ .bin_dir = try replace_backslashes(b, b.install_path), .src_path = try replace_backslashes(b, src_path) }); } + +fn appendSystemIncludePath( + b: *std.Build, + path: *std.ArrayList(LazyPath), + system_name: []const u8, +) !void { + var code: u8 = 0; + const stdout = try b.runAllowFail( + &[_][]const u8{ "pkg-config", system_name, "--cflags-only-I", "--keep-system-cflags" }, + &code, + .Ignore, + ); + if (code != 0) return std.Build.PkgConfigError.PkgConfigFailed; + var arg_it = std.mem.tokenizeAny(u8, stdout, " \r\n\t"); + while (arg_it.next()) |arg| { + if (std.mem.eql(u8, arg, "-I")) { + // -I /foo/bar + const dir = arg_it.next() orelse return std.Build.PkgConfigError.PkgConfigInvalidOutput; + try path.append(b.allocator, .{ .cwd_relative = dir }); + } else if (std.mem.startsWith(u8, arg, "-I")) { + // -I/foo/bar + const dir = arg[("-I".len)..]; + try path.append(b.allocator, .{ .cwd_relative = dir }); + } + } +} diff --git a/build.zig.zon b/build.zig.zon @@ -6,30 +6,35 @@ .dependencies = .{ .zlua = .{ - .url = "git+https://github.com/natecraddock/ziglua#a4d08d97795c312e63a0f09d456f7c6d280610b4", - .hash = "zlua-0.1.0-hGRpC5c9BQAfU5bkkFfLV9B4a7Prw8N7JPIFAZBbRCkq", + .url = "git+https://github.com/natecraddock/ziglua#dca1800ea46f5a19fc9abf88b2f8c1617f86ac23", + .hash = "zlua-0.1.0-hGRpC1dCBQDf-IqqUifYvyr8B9-4FlYXqY8cl7HIetrC", }, .lpeg = .{ .url = "https://github.com/neovim/deps/raw/d495ee6f79e7962a53ad79670cb92488abe0b9b4/opt/lpeg-1.1.0.tar.gz", .hash = "N-V-__8AAMnaAwCEutreuREG3QayBVEZqUTDQFY1Nsrv2OIt", + .lazy = true, }, .luv = .{ .url = "git+https://github.com/luvit/luv?ref=1.51.0-1#4c9fbc6cf6f3338bb0e0426710cf885ee557b540", .hash = "N-V-__8AAMlNDwCY07jUoMiq3iORXdZy0uFWKiHsy8MaDBJA", + .lazy = true, }, .lua_compat53 = .{ .url = "https://github.com/lunarmodules/lua-compat-5.3/archive/v0.13.tar.gz", .hash = "N-V-__8AADi-AwDnVoXwDCQvv2wcYOmN0bJLqZ44J3lwoQY2", + .lazy = true, }, .treesitter = .{ .url = "git+https://github.com/tree-sitter/tree-sitter?ref=v0.26.3#cd4b6e2ef996d4baca12caadb78dffc8b55bc869", .hash = "tree_sitter-0.26.3-Tw2sRwKWCwB6DronNIXBd7Aq5TY4WlnmS4d8PB_M7TIU", + .lazy = true, }, .libuv = .{ .url = "git+https://github.com/allyourcodebase/libuv#a2dfd385bd2a00d6d290fda85a40a55a9d6cffc5", .hash = "libuv-1.51.0-htqqv6liAADxBLIBCZT-qUh_3nRRwtNYsOFQOUmrd_sx", + .lazy = true, }, - .utf8proc = .{ .path = "./deps/utf8proc/" }, + .utf8proc = .{ .path = "./deps/utf8proc/", .lazy = true }, .unibilium = .{ .path = "./deps/unibilium/", .lazy = true }, .libiconv = .{ .url = "git+https://github.com/allyourcodebase/libiconv#9def4c8a1743380e85bcedb80f2c15b455e236f3", diff --git a/deps/unibilium/build.zig b/deps/unibilium/build.zig @@ -4,7 +4,6 @@ pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const upstream = b.dependency("unibilium", .{}); const lib = b.addLibrary(.{ .name = "unibilium", .linkage = .static, @@ -14,17 +13,19 @@ pub fn build(b: *std.Build) !void { }), }); - lib.addIncludePath(upstream.path("")); + if (b.lazyDependency("unibilium", .{})) |upstream| { + lib.addIncludePath(upstream.path("")); - lib.installHeader(upstream.path("unibilium.h"), "unibilium.h"); + lib.installHeader(upstream.path("unibilium.h"), "unibilium.h"); - lib.linkLibC(); + lib.linkLibC(); - lib.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ - "unibilium.c", - "uninames.c", - "uniutil.c", - }, .flags = &.{"-DTERMINFO_DIRS=\"/etc/terminfo:/usr/share/terminfo\""} }); + lib.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ + "unibilium.c", + "uninames.c", + "uniutil.c", + }, .flags = &.{"-DTERMINFO_DIRS=\"/etc/terminfo:/usr/share/terminfo\""} }); + } b.installArtifact(lib); } diff --git a/deps/unibilium/build.zig.zon b/deps/unibilium/build.zig.zon @@ -7,6 +7,7 @@ .unibilium = .{ .url = "git+https://github.com/neovim/unibilium?ref=v2.1.2#bfcb0350129dd76893bc90399cf37c45812268a2", .hash = "N-V-__8AADO1CgCggvx73yptnBlXbEm7TjOSO6VGIqc0CvYR", + .lazy = true, }, }, } diff --git a/deps/utf8proc/build.zig b/deps/utf8proc/build.zig @@ -4,7 +4,6 @@ pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - const upstream = b.dependency("utf8proc", .{}); const lib = b.addLibrary(.{ .name = "utf8proc", .linkage = .static, @@ -14,14 +13,16 @@ pub fn build(b: *std.Build) !void { }), }); - lib.addIncludePath(upstream.path("")); - lib.installHeader(upstream.path("utf8proc.h"), "utf8proc.h"); + if (b.lazyDependency("utf8proc", .{})) |upstream| { + lib.addIncludePath(upstream.path("")); + lib.installHeader(upstream.path("utf8proc.h"), "utf8proc.h"); - lib.linkLibC(); + lib.linkLibC(); - lib.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ - "utf8proc.c", - }, .flags = &.{"-DUTF8PROC_STATIC"} }); + lib.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ + "utf8proc.c", + }, .flags = &.{"-DUTF8PROC_STATIC"} }); + } b.installArtifact(lib); } diff --git a/deps/utf8proc/build.zig.zon b/deps/utf8proc/build.zig.zon @@ -7,6 +7,7 @@ .utf8proc = .{ .url = "git+https://github.com/juliastrings/utf8proc?ref=v2.11.3#e5e799221b45bbb90f5fdc5c69b6b8dfbf017e78", .hash = "N-V-__8AACywKABFCj0r_Y-jIWsk9ahy10zlk78hjn6S-39g", + .lazy = true, }, }, } diff --git a/src/build_lua.zig b/src/build_lua.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const build = @import("../build.zig"); const LazyPath = std.Build.LazyPath; pub fn build_nlua0( @@ -7,9 +8,10 @@ pub fn build_nlua0( optimize: std.builtin.OptimizeMode, use_luajit: bool, ziglua: *std.Build.Dependency, - lpeg: *std.Build.Dependency, - libluv: *std.Build.Step.Compile, -) *std.Build.Step.Compile { + lpeg: ?*std.Build.Dependency, + libluv: ?*std.Build.Step.Compile, + system_integration_options: build.SystemIntegrationOptions, +) !*std.Build.Step.Compile { const options = b.addOptions(); options.addOption(bool, "use_luajit", use_luajit); @@ -19,6 +21,7 @@ pub fn build_nlua0( .root_source_file = b.path("src/nlua0.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), }); const nlua0_mod = nlua0_exe.root_module; @@ -28,6 +31,7 @@ pub fn build_nlua0( .root_source_file = b.path("src/nlua0.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), }); @@ -39,14 +43,23 @@ pub fn build_nlua0( mod.addImport("ziglua", ziglua.module("zlua")); 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); + if (system_integration_options.lua) { + const system_lua_lib = if (use_luajit) "luajit" else "lua5.1"; + mod.linkSystemLibrary(system_lua_lib, .{}); + } else { + mod.linkLibrary(ziglua.artifact("lua")); + } + if (libluv) |luv| { + mod.linkLibrary(luv); + } else { + mod.linkSystemLibrary("luv", .{}); + } mod.addOptions("options", options); mod.addIncludePath(b.path("src")); mod.addIncludePath(b.path("src/includes_fixmelater")); - add_lua_modules(mod, lpeg, use_luajit, true); + try add_lua_modules(b, target.result, mod, lpeg, use_luajit, true, system_integration_options); } // for debugging the nlua0 environment @@ -68,14 +81,24 @@ pub fn build_nlua0( return nlua0_exe; } -pub fn add_lua_modules(mod: *std.Build.Module, lpeg: *std.Build.Dependency, use_luajit: bool, is_nlua0: bool) void { +pub fn add_lua_modules( + b: *std.Build, + target: std.Target, + mod: *std.Build.Module, + lpeg_dep: ?*std.Build.Dependency, + use_luajit: bool, + is_nlua0: bool, + system_integration_options: build.SystemIntegrationOptions, +) !void { const flags = [_][]const u8{ // Standard version used in Lua Makefile "-std=gnu99", if (is_nlua0) "-DNVIM_NLUA0" else "", }; - mod.addIncludePath(lpeg.path("")); + if (lpeg_dep) |lpeg| { + mod.addIncludePath(lpeg.path("")); + } mod.addCSourceFiles(.{ .files = &.{ "src/mpack/lmpack.c", @@ -86,18 +109,25 @@ pub fn add_lua_modules(mod: *std.Build.Module, lpeg: *std.Build.Dependency, use_ }, .flags = &flags, }); - mod.addCSourceFiles(.{ - .root = .{ .dependency = .{ .dependency = lpeg, .sub_path = "" } }, - .files = &.{ - "lpcap.c", - "lpcode.c", - "lpcset.c", - "lpprint.c", - "lptree.c", - "lpvm.c", - }, - .flags = &flags, - }); + if (system_integration_options.lpeg) { + if (try findLpeg(b, target)) |lpeg_lib| { + mod.addLibraryPath(.{ .cwd_relative = std.fs.path.dirname(lpeg_lib).? }); + mod.addObjectFile(.{ .cwd_relative = lpeg_lib }); + } + } else if (lpeg_dep) |lpeg| { + mod.addCSourceFiles(.{ + .root = .{ .dependency = .{ .dependency = lpeg, .sub_path = "" } }, + .files = &.{ + "lpcap.c", + "lpcode.c", + "lpcset.c", + "lpprint.c", + "lptree.c", + "lpvm.c", + }, + .flags = &flags, + }); + } if (!use_luajit) { mod.addCSourceFiles(.{ @@ -113,11 +143,12 @@ pub fn build_libluv( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, - lua: *std.Build.Step.Compile, + lua: ?*std.Build.Step.Compile, libuv: *std.Build.Step.Compile, + use_luajit: bool, ) !*std.Build.Step.Compile { - const upstream = b.dependency("luv", .{}); - const compat53 = b.dependency("lua_compat53", .{}); + const upstream = b.lazyDependency("luv", .{}); + const compat53 = b.lazyDependency("lua_compat53", .{}); const lib = b.addLibrary(.{ .name = "luv", .linkage = .static, @@ -127,21 +158,59 @@ pub fn build_libluv( }), }); - lib.linkLibrary(lua); + if (lua) |lua_lib| { + lib.root_module.linkLibrary(lua_lib); + } else { + const system_lua_lib = if (use_luajit) "luajit" else "lua5.1"; + lib.root_module.linkSystemLibrary(system_lua_lib, .{}); + } lib.linkLibrary(libuv); - lib.addIncludePath(upstream.path("src")); - lib.addIncludePath(compat53.path("c-api")); - - lib.installHeader(upstream.path("src/luv.h"), "luv/luv.h"); - - lib.addCSourceFiles(.{ .root = upstream.path("src/"), .files = &.{ - "luv.c", - } }); - - lib.addCSourceFiles(.{ .root = compat53.path("c-api"), .files = &.{ - "compat-5.3.c", - } }); + if (upstream) |dep| { + lib.addIncludePath(dep.path("src")); + lib.installHeader(dep.path("src/luv.h"), "luv/luv.h"); + lib.addCSourceFiles(.{ .root = dep.path("src/"), .files = &.{ + "luv.c", + } }); + } + if (compat53) |dep| { + lib.addIncludePath(dep.path("c-api")); + lib.addCSourceFiles(.{ .root = dep.path("c-api"), .files = &.{ + "compat-5.3.c", + } }); + } return lib; } + +fn findLpeg(b: *std.Build, target: std.Target) !?[]const u8 { + const filenames = [_][]const u8{ + "lpeg_a", + "lpeg", + "liblpeg_a", + "lpeg.so", + b.fmt("lpeg{s}", .{target.dynamicLibSuffix()}), + }; + var code: u8 = 0; + const dirs_stdout = std.mem.trimEnd(u8, try b.runAllowFail(&[_][]const u8{ + "pkg-config", + "--variable=pc_system_libdirs", + "--keep-system-cflags", + "pkg-config", + }, &code, .Ignore), "\r\n"); + var paths: std.ArrayList([]const u8) = try .initCapacity(b.allocator, 0); + var path_it = std.mem.tokenizeAny(u8, dirs_stdout, " ,"); + while (path_it.next()) |dir| { + try paths.append(b.allocator, dir); + try paths.append(b.allocator, b.fmt("{s}/lua/5.1", .{dir})); + } + for (paths.items) |path| { + var dir = std.fs.openDirAbsolute(path, .{}) catch continue; + defer dir.close(); + for (filenames) |filename| { + dir.access(filename, .{}) catch continue; + return b.fmt("{s}/{s}", .{ path, filename }); + } + } + return null; +}