build.zig (32732B)
1 const std = @import("std"); 2 const LazyPath = std.Build.LazyPath; 3 const Compile = std.Build.Step.Compile; 4 const build_lua = @import("src/build_lua.zig"); 5 const gen = @import("src/gen/gen_steps.zig"); 6 const runtime = @import("runtime/gen_runtime.zig"); 7 const tests = @import("test/run_tests.zig"); 8 9 const version = struct { 10 const major = 0; 11 const minor = 12; 12 const patch = 0; 13 const prerelease = "-dev"; 14 15 const api_level = 14; 16 const api_level_compat = 0; 17 const api_prerelease = true; 18 }; 19 20 pub const SystemIntegrationOptions = packed struct { 21 lpeg: bool, 22 lua: bool, 23 tree_sitter: bool, 24 unibilium: bool, 25 utf8proc: bool, 26 uv: bool, 27 }; 28 29 // TODO(bfredl): this is for an upstream issue 30 pub fn lazyArtifact(d: *std.Build.Dependency, name: []const u8) ?*std.Build.Step.Compile { 31 var found: ?*std.Build.Step.Compile = null; 32 for (d.builder.install_tls.step.dependencies.items) |dep_step| { 33 const inst = dep_step.cast(std.Build.Step.InstallArtifact) orelse continue; 34 if (std.mem.eql(u8, inst.artifact.name, name)) { 35 if (found != null) std.debug.panic("artifact name '{s}' is ambiguous", .{name}); 36 found = inst.artifact; 37 } 38 } 39 return found; 40 } 41 42 pub fn build(b: *std.Build) !void { 43 const target = b.standardTargetOptions(.{}); 44 const optimize = b.standardOptimizeOption(.{}); 45 46 const t = target.result; 47 const os_tag = t.os.tag; 48 const is_windows = (os_tag == .windows); 49 const is_linux = (os_tag == .linux); 50 const is_darwin = os_tag.isDarwin(); 51 const modern_unix = is_darwin or os_tag.isBSD() or is_linux; 52 53 const cross_compiling = b.option(bool, "cross", "cross compile") orelse false; 54 // TODO(bfredl): option to set nlua0 target explicitly when cross compiling? 55 const target_host = if (cross_compiling) b.graph.host else target; 56 // without cross_compiling we like to reuse libluv etc at the same optimize level 57 const optimize_host = if (cross_compiling) .ReleaseSafe else optimize; 58 59 const use_unibilium = b.option(bool, "unibilium", "use unibilium") orelse true; 60 61 // puc lua 5.1 is not ReleaseSafe "safe" 62 const optimize_lua = if (optimize == .Debug or optimize == .ReleaseSafe) .ReleaseSmall else optimize; 63 64 const use_luajit = b.option(bool, "luajit", "use luajit") orelse true; 65 const lualib_name = if (use_luajit) "luajit" else "lua5.1"; 66 const host_use_luajit = if (cross_compiling) false else use_luajit; 67 const E = enum { luajit, lua51 }; 68 69 const system_integration_options = SystemIntegrationOptions{ 70 .lpeg = b.systemIntegrationOption("lpeg", .{}), 71 .lua = b.systemIntegrationOption("lua", .{}), 72 .tree_sitter = b.systemIntegrationOption("tree-sitter", .{}), 73 .unibilium = b.systemIntegrationOption("unibilium", .{}), 74 .utf8proc = b.systemIntegrationOption("utf8proc", .{}), 75 .uv = b.systemIntegrationOption("uv", .{}), 76 }; 77 78 const ziglua = b.dependency("zlua", .{ 79 .target = target, 80 .optimize = optimize_lua, 81 .lang = if (use_luajit) E.luajit else E.lua51, 82 .shared = false, 83 .system_lua = system_integration_options.lua, 84 }); 85 const ziglua_host = if (cross_compiling) b.dependency("zlua", .{ 86 .target = target_host, 87 .optimize = .ReleaseSmall, 88 .lang = if (host_use_luajit) E.luajit else E.lua51, 89 .system_lua = system_integration_options.lua, 90 .shared = false, 91 }) else ziglua; 92 var lua: ?*Compile = null; 93 var libuv: ?*Compile = null; 94 var libluv: ?*Compile = null; 95 var libluv_host: ?*Compile = null; 96 if (!system_integration_options.lua) { 97 // this is currently not necessary, as ziglua currently doesn't use lazy dependencies 98 // to circumvent ziglua.artifact() failing in a bad way. 99 lua = lazyArtifact(ziglua, "lua") orelse return; 100 if (cross_compiling) { 101 _ = lazyArtifact(ziglua_host, "lua") orelse return; 102 } 103 } 104 if (!system_integration_options.uv) { 105 if (b.lazyDependency("libuv", .{ .target = target, .optimize = optimize })) |dep| { 106 libuv = dep.artifact("uv"); 107 libluv = try build_lua.build_libluv(b, target, optimize, lua, libuv.?, use_luajit); 108 109 libluv_host = if (cross_compiling) libluv_host: { 110 const libuv_dep_host = b.lazyDependency("libuv", .{ 111 .target = target_host, 112 .optimize = optimize_host, 113 }); 114 const libuv_host = libuv_dep_host.?.artifact("uv"); 115 break :libluv_host try build_lua.build_libluv( 116 b, 117 target_host, 118 optimize_host, 119 ziglua_host.artifact("lua"), 120 libuv_host, 121 host_use_luajit, 122 ); 123 } else libluv; 124 } 125 } 126 127 const lpeg = if (system_integration_options.lpeg) null else b.lazyDependency("lpeg", .{}); 128 129 const iconv = if (is_windows or is_darwin) b.lazyDependency("libiconv", .{ 130 .target = target, 131 .optimize = optimize, 132 }) else null; 133 134 const utf8proc = if (system_integration_options.utf8proc) null else b.lazyDependency("utf8proc", .{ 135 .target = target, 136 .optimize = optimize, 137 }); 138 const unibilium = if (use_unibilium and !system_integration_options.unibilium) b.lazyDependency("unibilium", .{ 139 .target = target, 140 .optimize = optimize, 141 }) else null; 142 143 // TODO(bfredl): fix upstream bugs with UBSAN 144 const optimize_ts = .ReleaseFast; 145 const treesitter = if (system_integration_options.tree_sitter) null else b.lazyDependency("treesitter", .{ 146 .target = target, 147 .optimize = optimize_ts, 148 }); 149 150 const nlua0 = try build_lua.build_nlua0( 151 b, 152 target_host, 153 optimize_host, 154 host_use_luajit, 155 ziglua_host, 156 lpeg, 157 libluv_host, 158 system_integration_options, 159 ); 160 161 // usual caveat emptor: might need to force a rebuild if the only change is 162 // addition of new .c files, as those are not seen by any hash 163 const subdirs = [_][]const u8{ 164 "", // src/nvim itself 165 "os/", 166 "api/", 167 "api/private/", 168 "msgpack_rpc/", 169 "tui/", 170 "tui/termkey/", 171 "event/", 172 "eval/", 173 "lib/", 174 "lua/", 175 "viml/", 176 "viml/parser/", 177 "vterm/", 178 }; 179 180 // source names _relative_ src/nvim/, not including other src/ subdircs 181 var nvim_sources = try std.ArrayList(gen.SourceItem).initCapacity(b.allocator, 100); 182 var nvim_headers = try std.ArrayList([]u8).initCapacity(b.allocator, 100); 183 184 // both source headers and the {module}.h.generated.h files 185 var api_headers = try std.ArrayList(std.Build.LazyPath).initCapacity(b.allocator, 10); 186 187 // TODO(bfredl): these should just become subdirs.. 188 const windows_only = [_][]const u8{ 189 "pty_proc_win.c", 190 "pty_proc_win.h", 191 "pty_conpty_win.c", 192 "pty_conpty_win.h", 193 "os_win_console.c", 194 "win_defs.h", 195 }; 196 const unix_only = [_][]const u8{ "unix_defs.h", "pty_proc_unix.c", "pty_proc_unix.h" }; 197 const exclude_list = if (is_windows) &unix_only else &windows_only; 198 199 const src_dir = b.build_root.handle; 200 for (subdirs) |s| { 201 var dir = try src_dir.openDir(b.fmt("src/nvim/{s}", .{s}), .{ .iterate = true }); 202 defer dir.close(); 203 var it = dir.iterateAssumeFirstIteration(); 204 const api_export = std.mem.eql(u8, s, "api/"); 205 const os_check = std.mem.eql(u8, s, "os/"); 206 entries: while (try it.next()) |entry| { 207 if (entry.name.len < 3) continue; 208 if (entry.name[0] < 'a' or entry.name[0] > 'z') continue; 209 if (os_check) { 210 for (exclude_list) |name| { 211 if (std.mem.eql(u8, name, entry.name)) { 212 continue :entries; 213 } 214 } 215 } 216 if (std.mem.eql(u8, ".c", entry.name[entry.name.len - 2 ..])) { 217 try nvim_sources.append(b.allocator, .{ 218 .name = b.fmt("{s}{s}", .{ s, entry.name }), 219 .api_export = api_export, 220 }); 221 } 222 if (std.mem.eql(u8, ".h", entry.name[entry.name.len - 2 ..])) { 223 try nvim_headers.append(b.allocator, b.fmt("{s}{s}", .{ s, entry.name })); 224 if (api_export and !std.mem.eql(u8, "ui_events.in.h", entry.name)) { 225 try api_headers.append(b.allocator, b.path(b.fmt("src/nvim/{s}{s}", .{ s, entry.name }))); 226 } 227 } 228 } 229 } 230 231 const support_unittests = use_luajit; 232 233 const gen_config = b.addWriteFiles(); 234 235 const version_lua = gen_config.add("nvim_version.lua", lua_version_info(b)); 236 237 var config_str = b.fmt("zig build -Doptimize={s}", .{@tagName(optimize)}); 238 if (cross_compiling) { 239 config_str = b.fmt("{s} -Dcross -Dtarget={s} (host: {s})", .{ 240 config_str, 241 try t.linuxTriple(b.allocator), 242 try b.graph.host.result.linuxTriple(b.allocator), 243 }); 244 } 245 246 const versiondef_step = b.addConfigHeader(.{ 247 .style = .{ .cmake = b.path("cmake.config/versiondef.h.in") }, 248 }, .{ 249 .NVIM_VERSION_MAJOR = version.major, 250 .NVIM_VERSION_MINOR = version.minor, 251 .NVIM_VERSION_PATCH = version.patch, 252 .NVIM_VERSION_PRERELEASE = version.prerelease, 253 .NVIM_VERSION_MEDIUM = "", 254 .VERSION_STRING = "TODO", // TODO(bfredl): not sure what to put here. summary already in "config_str" 255 .CONFIG = config_str, 256 }); 257 _ = gen_config.addCopyFile(versiondef_step.getOutput(), "auto/versiondef.h"); // run_preprocessor() workaronnd 258 259 const ptrwidth = t.ptrBitWidth() / 8; 260 const sysconfig_step = b.addConfigHeader(.{ 261 .style = .{ .cmake = b.path("cmake.config/config.h.in") }, 262 }, .{ 263 .SIZEOF_INT = t.cTypeByteSize(.int), 264 .SIZEOF_INTMAX_T = t.cTypeByteSize(.longlong), // TODO 265 .SIZEOF_LONG = t.cTypeByteSize(.long), 266 .SIZEOF_SIZE_T = ptrwidth, 267 .SIZEOF_VOID_PTR = ptrwidth, 268 269 .PROJECT_NAME = "nvim", 270 271 .HAVE__NSGETENVIRON = is_darwin, 272 .HAVE_FD_CLOEXEC = modern_unix, 273 .HAVE_FSEEKO = modern_unix, 274 .HAVE_LANGINFO_H = modern_unix, 275 .HAVE_NL_LANGINFO_CODESET = modern_unix, 276 .HAVE_NL_MSG_CAT_CNTR = t.isGnuLibC(), 277 .HAVE_PWD_FUNCS = modern_unix, 278 .HAVE_READLINK = modern_unix, 279 .HAVE_STRNLEN = modern_unix, 280 .HAVE_STRCASECMP = modern_unix, 281 .HAVE_STRINGS_H = modern_unix, 282 .HAVE_STRNCASECMP = modern_unix, 283 .HAVE_STRPTIME = modern_unix, 284 .HAVE_XATTR = is_linux, 285 .HAVE_SYS_SDT_H = false, 286 .HAVE_SYS_UTSNAME_H = modern_unix, 287 .HAVE_SYS_WAIT_H = false, // unused 288 .HAVE_TERMIOS_H = modern_unix, 289 .HAVE_WORKING_LIBINTL = t.isGnuLibC(), 290 .UNIX = modern_unix, 291 .CASE_INSENSITIVE_FILENAME = is_darwin or is_windows, 292 .HAVE_SYS_UIO_H = modern_unix, 293 .HAVE_READV = modern_unix, 294 .HAVE_DIRFD_AND_FLOCK = modern_unix, 295 .HAVE_FORKPTY = modern_unix and !is_darwin, // also on Darwin but we lack the headers :( 296 .HAVE_BE64TOH = modern_unix and !is_darwin, 297 .ORDER_BIG_ENDIAN = t.cpu.arch.endian() == .big, 298 .ENDIAN_INCLUDE_FILE = "endian.h", 299 .HAVE_EXECINFO_BACKTRACE = modern_unix and !t.isMuslLibC(), 300 .HAVE_BUILTIN_ADD_OVERFLOW = true, 301 .HAVE_WIMPLICIT_FALLTHROUGH_FLAG = true, 302 .HAVE_BITSCANFORWARD64 = null, 303 304 .VTERM_TEST_FILE = "test/vterm_test_output", // TODO(bfredl): revisit when porting libvterm tests 305 }); 306 307 const system_install_path = b.option([]const u8, "install-path", "Install path (for packagers)"); 308 const install_path = system_install_path orelse b.install_path; 309 const lib_dir = if (system_install_path) |path| b.fmt("{s}/lib", .{path}) else b.lib_dir; 310 _ = gen_config.addCopyFile(sysconfig_step.getOutput(), "auto/config.h"); // run_preprocessor() workaronnd 311 312 _ = gen_config.add("auto/pathdef.h", b.fmt( 313 \\char *default_vim_dir = "{s}/share/nvim"; 314 \\char *default_vimruntime_dir = ""; 315 \\char *default_lib_dir = "{s}/nvim"; 316 // b.lib_dir is typically b.install_path + "/lib" but may be overridden 317 , .{ try replace_backslashes(b, install_path), try replace_backslashes(b, lib_dir) })); 318 319 const opt_version_string = b.option( 320 []const u8, 321 "version-string", 322 "Override Neovim version string. Default is to find out with git.", 323 ); 324 const version_medium = if (opt_version_string) |version_string| version_string else v: { 325 var code: u8 = undefined; 326 const version_string = b.fmt("v{d}.{d}.{d}", .{ 327 version.major, 328 version.minor, 329 version.patch, 330 }); 331 const git_describe_untrimmed = b.runAllowFail(&[_][]const u8{ 332 "git", 333 "-C", b.build_root.path orelse ".", // affects the --git-dir argument 334 "--git-dir", ".git", // affected by the -C argument 335 "describe", "--dirty", "--match", "v*.*.*", // 336 }, &code, .Ignore) catch { 337 break :v version_string; 338 }; 339 const git_describe = std.mem.trim(u8, git_describe_untrimmed, " \n\r"); 340 341 const num_parts = std.mem.count(u8, git_describe, "-") + 1; 342 if (num_parts < 3) { 343 break :v version_string; // achtung: unrecognized format 344 } 345 346 var it = std.mem.splitScalar(u8, git_describe, '-'); 347 const tagged_ancestor = it.first(); 348 _ = tagged_ancestor; 349 const commit_height = it.next().?; 350 const commit_id = it.next().?; 351 const maybe_dirty = it.next(); 352 353 // Check that the commit hash is prefixed with a 'g' (a Git convention). 354 if (commit_id.len < 1 or commit_id[0] != 'g') { 355 std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe}); 356 break :v version_string; 357 } 358 359 const dirty_tag = if (maybe_dirty) |dirty| b.fmt("-{s}", .{dirty}) else ""; 360 361 break :v b.fmt("{s}-dev-{s}+{s}{s}", .{ version_string, commit_height, commit_id, dirty_tag }); 362 }; 363 364 const versiondef_git = gen_config.add("auto/versiondef_git.h", b.fmt( 365 \\#define NVIM_VERSION_MEDIUM "{s}" 366 \\#define NVIM_VERSION_BUILD "zig" 367 \\ 368 , .{version_medium})); 369 370 // TODO(zig): using getEmittedIncludeTree() is ugly af. we want unittests 371 // to reuse the std.build.Module include_path thing 372 var unittest_include_path: std.ArrayList(LazyPath) = try .initCapacity(b.allocator, 2); 373 try unittest_include_path.append(b.allocator, b.path("src/")); 374 try unittest_include_path.append(b.allocator, gen_config.getDirectory()); 375 if (system_integration_options.lua) { 376 try appendSystemIncludePath(b, &unittest_include_path, lualib_name); 377 } else if (lua) |compile| { 378 try unittest_include_path.append(b.allocator, compile.getEmittedIncludeTree()); 379 } 380 if (system_integration_options.uv) { 381 try appendSystemIncludePath(b, &unittest_include_path, "libuv"); 382 try appendSystemIncludePath(b, &unittest_include_path, "libluv"); 383 } else { 384 if (libuv) |compile| try unittest_include_path.append(b.allocator, compile.getEmittedIncludeTree()); 385 if (libluv) |compile| try unittest_include_path.append(b.allocator, compile.getEmittedIncludeTree()); 386 } 387 if (system_integration_options.utf8proc) { 388 try appendSystemIncludePath(b, &unittest_include_path, "libutf8proc"); 389 } else if (utf8proc) |dep| { 390 try unittest_include_path.append(b.allocator, dep.artifact("utf8proc").getEmittedIncludeTree()); 391 } 392 if (use_unibilium) { 393 if (system_integration_options.unibilium) { 394 try appendSystemIncludePath(b, &unittest_include_path, "unibilium"); 395 } else if (unibilium) |dep| { 396 try unittest_include_path.append(b.allocator, dep.artifact("unibilium").getEmittedIncludeTree()); 397 } 398 } 399 if (system_integration_options.tree_sitter) { 400 try appendSystemIncludePath(b, &unittest_include_path, "tree-sitter"); 401 } else if (treesitter) |dep| { 402 try unittest_include_path.append(b.allocator, dep.artifact("tree-sitter").getEmittedIncludeTree()); 403 } 404 if (iconv) |dep| { 405 try unittest_include_path.append(b.allocator, dep.artifact("iconv").getEmittedIncludeTree()); 406 } 407 408 const gen_headers, const funcs_data = try gen.nvim_gen_sources( 409 b, 410 nlua0, 411 &nvim_sources, 412 &nvim_headers, 413 &api_headers, 414 versiondef_git, 415 version_lua, 416 ); 417 418 const test_config_step = b.addWriteFiles(); 419 _ = test_config_step.add("test/cmakeconfig/paths.lua", try test_config(b)); 420 421 const test_gen_step = b.step("gen_headers", "debug: output generated headers"); 422 const config_install = b.addInstallDirectory(.{ 423 .source_dir = gen_config.getDirectory(), 424 .install_dir = .prefix, 425 .install_subdir = "config/", 426 }); 427 test_gen_step.dependOn(&config_install.step); 428 test_gen_step.dependOn(&b.addInstallDirectory(.{ 429 .source_dir = gen_headers.getDirectory(), 430 .install_dir = .prefix, 431 .install_subdir = "headers/", 432 }).step); 433 434 const nvim_exe = b.addExecutable(.{ 435 .name = "nvim", 436 .root_module = b.createModule(.{ 437 .target = target, 438 .optimize = optimize, 439 .link_libc = true, 440 }), 441 }); 442 nvim_exe.rdynamic = true; // -E 443 444 if (system_integration_options.lua) { 445 nvim_exe.root_module.linkSystemLibrary(lualib_name, .{}); 446 } else if (lua) |compile| { 447 nvim_exe.root_module.linkLibrary(compile); 448 } 449 if (system_integration_options.uv) { 450 nvim_exe.root_module.linkSystemLibrary("libuv", .{}); 451 nvim_exe.root_module.linkSystemLibrary("libluv", .{}); 452 } else { 453 if (libuv) |compile| nvim_exe.root_module.linkLibrary(compile); 454 if (libluv) |compile| nvim_exe.root_module.linkLibrary(compile); 455 } 456 if (iconv) |dep| nvim_exe.linkLibrary(dep.artifact("iconv")); 457 if (system_integration_options.utf8proc) { 458 nvim_exe.root_module.linkSystemLibrary("utf8proc", .{}); 459 } else if (utf8proc) |dep| { 460 nvim_exe.root_module.linkLibrary(dep.artifact("utf8proc")); 461 } 462 if (use_unibilium) { 463 if (system_integration_options.unibilium) { 464 nvim_exe.root_module.linkSystemLibrary("unibilium", .{}); 465 } else if (unibilium) |dep| { 466 nvim_exe.root_module.linkLibrary(dep.artifact("unibilium")); 467 } 468 } 469 if (system_integration_options.tree_sitter) { 470 nvim_exe.root_module.linkSystemLibrary("tree-sitter", .{}); 471 } else if (treesitter) |dep| { 472 nvim_exe.root_module.linkLibrary(dep.artifact("tree-sitter")); 473 } 474 if (is_windows) { 475 nvim_exe.linkSystemLibrary("netapi32"); 476 } 477 nvim_exe.addIncludePath(b.path("src")); 478 nvim_exe.addIncludePath(gen_config.getDirectory()); 479 nvim_exe.addIncludePath(gen_headers.getDirectory()); 480 try build_lua.add_lua_modules( 481 b, 482 t, 483 nvim_exe.root_module, 484 lpeg, 485 use_luajit, 486 false, 487 system_integration_options, 488 ); 489 490 var unit_test_sources = try std.ArrayList([]u8).initCapacity(b.allocator, 10); 491 if (support_unittests) { 492 var unit_test_fixtures = try src_dir.openDir("test/unit/fixtures/", .{ .iterate = true }); 493 defer unit_test_fixtures.close(); 494 var it = unit_test_fixtures.iterateAssumeFirstIteration(); 495 while (try it.next()) |entry| { 496 if (entry.name.len < 3) continue; 497 if (std.mem.eql(u8, ".c", entry.name[entry.name.len - 2 ..])) { 498 try unit_test_sources.append(b.allocator, b.fmt("test/unit/fixtures/{s}", .{entry.name})); 499 } 500 } 501 } 502 503 const src_paths = try b.allocator.alloc([]u8, nvim_sources.items.len + unit_test_sources.items.len); 504 for (nvim_sources.items, 0..) |s, i| { 505 src_paths[i] = b.fmt("src/nvim/{s}", .{s.name}); 506 } 507 @memcpy(src_paths[nvim_sources.items.len..], unit_test_sources.items); 508 509 const flags = [_][]const u8{ 510 "-std=gnu99", 511 "-DZIG_BUILD", 512 "-D_GNU_SOURCE", 513 if (support_unittests) "-DUNIT_TESTING" else "", 514 if (use_luajit) "" else "-DNVIM_VENDOR_BIT", 515 if (is_windows) "-DMSWIN" else "", 516 if (is_windows) "-DWIN32_LEAN_AND_MEAN" else "", 517 if (is_windows) "-DUTF8PROC_STATIC" else "", 518 if (use_unibilium) "-DHAVE_UNIBILIUM" else "", 519 }; 520 nvim_exe.addCSourceFiles(.{ .files = src_paths, .flags = &flags }); 521 522 nvim_exe.addCSourceFiles(.{ .files = &.{ 523 "src/xdiff/xdiffi.c", 524 "src/xdiff/xemit.c", 525 "src/xdiff/xhistogram.c", 526 "src/xdiff/xpatience.c", 527 "src/xdiff/xprepare.c", 528 "src/xdiff/xutils.c", 529 "src/cjson/lua_cjson.c", 530 "src/cjson/fpconv.c", 531 "src/cjson/strbuf.c", 532 }, .flags = &flags }); 533 534 if (is_windows) { 535 nvim_exe.addWin32ResourceFile(.{ .file = b.path("src/nvim/os/nvim.rc") }); 536 } 537 538 const nvim_exe_step = b.step("nvim_bin", "only the binary (not a fully working install!)"); 539 const nvim_exe_install = b.addInstallArtifact(nvim_exe, .{}); 540 541 nvim_exe_step.dependOn(&nvim_exe_install.step); 542 543 const gen_runtime = try runtime.nvim_gen_runtime(b, nlua0, funcs_data); 544 545 const lua_dev_deps = b.dependency("lua_dev_deps", .{}); 546 547 const test_deps = b.step("test_deps", "test prerequisites"); 548 test_deps.dependOn(&nvim_exe_install.step); 549 // running tests doesn't require copying the static runtime, only the generated stuff 550 const test_runtime_install = b.addInstallDirectory(.{ 551 .source_dir = gen_runtime.getDirectory(), 552 .install_dir = .prefix, 553 .install_subdir = "runtime/", 554 }); 555 test_deps.dependOn(&test_runtime_install.step); 556 557 const nvim_dev = b.step("nvim_dev", "build the editor for development"); 558 b.default_step = nvim_dev; 559 560 nvim_dev.dependOn(&nvim_exe_install.step); 561 nvim_dev.dependOn(&test_runtime_install.step); 562 563 // run from dev environment 564 const run_cmd = b.addRunArtifact(nvim_exe); 565 run_cmd.setEnvironmentVariable("VIMRUNTIME", try b.build_root.join(b.graph.arena, &.{"runtime"})); 566 run_cmd.setEnvironmentVariable("NVIM_ZIG_INSTALL_DIR", b.getInstallPath(.prefix, "runtime")); 567 run_cmd.step.dependOn(nvim_dev); 568 run_cmd.addArgs(&.{ "--cmd", "let &rtp = &rtp.','.$NVIM_ZIG_INSTALL_DIR" }); 569 if (b.args) |args| { 570 run_cmd.addArgs(args); 571 } 572 const run_step = b.step("run_dev", "run the editor (for development)"); 573 run_step.dependOn(&run_cmd.step); 574 575 // installation 576 const install = b.getInstallStep(); 577 install.dependOn(&nvim_exe_install.step); 578 b.installDirectory(.{ 579 .source_dir = b.path("runtime/"), 580 .install_dir = .prefix, 581 .install_subdir = "share/nvim/runtime/", 582 }); 583 b.installDirectory(.{ 584 .source_dir = gen_runtime.getDirectory(), 585 .install_dir = .prefix, 586 .install_subdir = "share/nvim/runtime/", 587 }); 588 589 test_deps.dependOn(test_fixture(b, "shell-test", false, false, null, target, optimize, &flags)); 590 test_deps.dependOn(test_fixture( 591 b, 592 "tty-test", 593 true, 594 system_integration_options.uv, 595 libuv, 596 target, 597 optimize, 598 &flags, 599 )); 600 test_deps.dependOn(test_fixture(b, "pwsh-test", false, false, null, target, optimize, &flags)); 601 test_deps.dependOn(test_fixture(b, "printargs-test", false, false, null, target, optimize, &flags)); 602 test_deps.dependOn(test_fixture(b, "printenv-test", false, false, null, target, optimize, &flags)); 603 test_deps.dependOn(test_fixture( 604 b, 605 "streams-test", 606 true, 607 system_integration_options.uv, 608 libuv, 609 target, 610 optimize, 611 &flags, 612 )); 613 614 // tee: vendored in src/tee/ 615 const tee_exe = b.addExecutable(.{ 616 .name = "tee", 617 .root_module = b.createModule(.{ 618 .target = target, 619 .optimize = optimize, 620 }), 621 }); 622 tee_exe.addCSourceFile(.{ .file = b.path("src/tee/tee.c") }); 623 tee_exe.linkLibC(); 624 test_deps.dependOn(&b.addInstallArtifact(tee_exe, .{}).step); 625 626 // xxd - hex dump utility (vendored from Vim) 627 const xxd_exe = b.addExecutable(.{ 628 .name = "xxd", 629 .root_module = b.createModule(.{ 630 .target = target, 631 .optimize = optimize, 632 }), 633 }); 634 xxd_exe.addCSourceFile(.{ .file = b.path("src/xxd/xxd.c") }); 635 xxd_exe.linkLibC(); 636 test_deps.dependOn(&b.addInstallArtifact(xxd_exe, .{}).step); 637 638 const parser_c = b.dependency("treesitter_c", .{ .target = target, .optimize = optimize_ts }); 639 test_deps.dependOn(add_ts_parser(b, "c", parser_c.path("."), false, target, optimize_ts, .test_)); 640 install.dependOn(add_ts_parser(b, "c", parser_c.path("."), false, target, optimize_ts, .install)); 641 642 const parser_markdown = b.dependency("treesitter_markdown", .{ .target = target, .optimize = optimize_ts }); 643 test_deps.dependOn(add_ts_parser(b, "markdown", parser_markdown.path("tree-sitter-markdown/"), true, target, optimize_ts, .test_)); 644 install.dependOn(add_ts_parser(b, "markdown", parser_markdown.path("tree-sitter-markdown/"), true, target, optimize_ts, .install)); 645 test_deps.dependOn(add_ts_parser(b, "markdown_inline", parser_markdown.path("tree-sitter-markdown-inline/"), true, target, optimize_ts, .test_)); 646 install.dependOn(add_ts_parser(b, "markdown_inline", parser_markdown.path("tree-sitter-markdown-inline/"), true, target, optimize_ts, .install)); 647 648 const parser_vim = b.dependency("treesitter_vim", .{ .target = target, .optimize = optimize_ts }); 649 test_deps.dependOn(add_ts_parser(b, "vim", parser_vim.path("."), true, target, optimize_ts, .test_)); 650 install.dependOn(add_ts_parser(b, "vim", parser_vim.path("."), true, target, optimize_ts, .install)); 651 652 const parser_vimdoc = b.dependency("treesitter_vimdoc", .{ .target = target, .optimize = optimize_ts }); 653 test_deps.dependOn(add_ts_parser(b, "vimdoc", parser_vimdoc.path("."), false, target, optimize_ts, .test_)); 654 install.dependOn(add_ts_parser(b, "vimdoc", parser_vimdoc.path("."), false, target, optimize_ts, .install)); 655 656 const parser_lua = b.dependency("treesitter_lua", .{ .target = target, .optimize = optimize_ts }); 657 test_deps.dependOn(add_ts_parser(b, "lua", parser_lua.path("."), true, target, optimize_ts, .test_)); 658 install.dependOn(add_ts_parser(b, "lua", parser_lua.path("."), true, target, optimize_ts, .install)); 659 660 const parser_query = b.dependency("treesitter_query", .{ .target = target, .optimize = optimize_ts }); 661 test_deps.dependOn(add_ts_parser(b, "query", parser_query.path("."), false, target, optimize_ts, .test_)); 662 install.dependOn(add_ts_parser(b, "query", parser_query.path("."), false, target, optimize_ts, .install)); 663 664 var unit_headers: ?[]const LazyPath = null; 665 if (support_unittests) { 666 try unittest_include_path.append(b.allocator, gen_headers.getDirectory()); 667 unit_headers = unittest_include_path.items; 668 } 669 try tests.test_steps( 670 b, 671 nvim_exe, 672 test_deps, 673 lua_dev_deps.path("."), 674 test_config_step.getDirectory(), 675 unit_headers, 676 ); 677 } 678 679 pub fn test_fixture( 680 b: *std.Build, 681 name: []const u8, 682 use_libuv: bool, 683 use_system_libuv: bool, 684 libuv: ?*std.Build.Step.Compile, 685 target: std.Build.ResolvedTarget, 686 optimize: std.builtin.OptimizeMode, 687 flags: []const []const u8, 688 ) *std.Build.Step { 689 const fixture = b.addExecutable(.{ 690 .name = name, 691 .root_module = b.createModule(.{ 692 .target = target, 693 .optimize = optimize, 694 }), 695 }); 696 const source = if (std.mem.eql(u8, name, "pwsh-test")) "shell-test" else name; 697 if (std.mem.eql(u8, name, "printenv-test")) { 698 fixture.mingw_unicode_entry_point = true; // uses UNICODE on WINDOWS :scream: 699 } 700 701 fixture.addCSourceFile(.{ 702 .file = b.path(b.fmt("./test/functional/fixtures/{s}.c", .{source})), 703 .flags = flags, 704 }); 705 fixture.linkLibC(); 706 if (use_libuv) { 707 if (use_system_libuv) { 708 fixture.root_module.linkSystemLibrary("libuv", .{}); 709 } else if (libuv) |uv| { 710 fixture.linkLibrary(uv); 711 } 712 } 713 return &b.addInstallArtifact(fixture, .{}).step; 714 } 715 716 pub fn add_ts_parser( 717 b: *std.Build, 718 name: []const u8, 719 parser_dir: LazyPath, 720 scanner: bool, 721 target: std.Build.ResolvedTarget, 722 optimize: std.builtin.OptimizeMode, 723 path: enum { test_, install }, 724 ) *std.Build.Step { 725 const parser = b.addLibrary(.{ 726 .name = name, 727 .root_module = b.createModule(.{ 728 .target = target, 729 .optimize = optimize, 730 }), 731 .linkage = .dynamic, 732 }); 733 parser.addCSourceFile(.{ .file = parser_dir.path(b, "src/parser.c") }); 734 if (scanner) parser.addCSourceFile(.{ .file = parser_dir.path(b, "src/scanner.c") }); 735 parser.addIncludePath(parser_dir.path(b, "src")); 736 parser.linkLibC(); 737 738 switch (path) { 739 .install => { 740 const parser_install = b.addInstallArtifact(parser, .{ 741 .dest_dir = .{ .override = .{ .custom = "share/nvim/runtime/parser" } }, 742 .dest_sub_path = b.fmt("{s}.so", .{name}), 743 }); 744 return &parser_install.step; 745 }, 746 .test_ => { 747 const parser_install = b.addInstallArtifact(parser, .{ 748 .dest_sub_path = b.fmt("parser/{s}.so", .{name}), 749 }); 750 return &parser_install.step; 751 }, 752 } 753 } 754 755 pub fn lua_version_info(b: *std.Build) []u8 { 756 const v = version; 757 return b.fmt( 758 \\return {{ 759 \\ {{"major", {}}}, 760 \\ {{"minor", {}}}, 761 \\ {{"patch", {}}}, 762 \\ {{"prerelease", {}}}, 763 \\ {{"api_level", {}}}, 764 \\ {{"api_compatible", {}}}, 765 \\ {{"api_prerelease", {}}}, 766 \\}} 767 , .{ 768 v.major, 769 v.minor, 770 v.patch, 771 v.prerelease.len > 0, 772 v.api_level, 773 v.api_level_compat, 774 v.api_prerelease, 775 }); 776 } 777 778 /// Replace all backslashes in `input` with with forward slashes when the target is Windows. 779 /// Returned memory is stored in `b.graph.arena`. 780 fn replace_backslashes(b: *std.Build, input: []const u8) ![]const u8 { 781 return if (b.graph.host.result.os.tag == .windows) 782 std.mem.replaceOwned(u8, b.graph.arena, input, "\\", "/") 783 else 784 input; 785 } 786 787 pub fn test_config(b: *std.Build) ![]u8 { 788 var buf: [std.fs.max_path_bytes]u8 = undefined; 789 const src_path = try b.build_root.handle.realpath(".", &buf); 790 791 // we don't use test/cmakeconfig/paths.lua.in because it contains cmake specific logic 792 return b.fmt( 793 \\local M = {{}} 794 \\ 795 \\M.apple_sysroot = "" 796 \\M.translations_enabled = "$ENABLE_TRANSLATIONS" == "ON" 797 \\M.is_asan = "$ENABLE_ASAN_UBSAN" == "ON" 798 \\M.is_zig_build = true 799 \\M.vterm_test_file = "test/vterm_test_output" 800 \\M.test_build_dir = "{[bin_dir]s}" -- bull 801 \\M.test_source_path = "{[src_path]s}" 802 \\M.test_lua_prg = "" 803 \\M.test_luajit_prg = "" 804 \\ -- include path passed on the cmdline, see test/lua_runner.lua 805 \\M.include_paths = _G.c_include_path or {{}} 806 \\ 807 \\return M 808 , .{ .bin_dir = try replace_backslashes(b, b.install_path), .src_path = try replace_backslashes(b, src_path) }); 809 } 810 811 fn appendSystemIncludePath( 812 b: *std.Build, 813 path: *std.ArrayList(LazyPath), 814 system_name: []const u8, 815 ) !void { 816 var code: u8 = 0; 817 const stdout = try b.runAllowFail( 818 &[_][]const u8{ "pkg-config", system_name, "--cflags-only-I", "--keep-system-cflags" }, 819 &code, 820 .Ignore, 821 ); 822 if (code != 0) return std.Build.PkgConfigError.PkgConfigFailed; 823 var arg_it = std.mem.tokenizeAny(u8, stdout, " \r\n\t"); 824 while (arg_it.next()) |arg| { 825 if (std.mem.eql(u8, arg, "-I")) { 826 // -I /foo/bar 827 const dir = arg_it.next() orelse return std.Build.PkgConfigError.PkgConfigInvalidOutput; 828 try path.append(b.allocator, .{ .cwd_relative = dir }); 829 } else if (std.mem.startsWith(u8, arg, "-I")) { 830 // -I/foo/bar 831 const dir = arg[("-I".len)..]; 832 try path.append(b.allocator, .{ .cwd_relative = dir }); 833 } 834 } 835 }