build.zig 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. const std = @import("std");
  2. const LazyPath = std.Build.LazyPath;
  3. const build_lua = @import("src/build_lua.zig");
  4. const gen = @import("src/gen/gen_steps.zig");
  5. const runtime = @import("runtime/gen_runtime.zig");
  6. const tests = @import("test/run_tests.zig");
  7. const version = struct {
  8. const major = 0;
  9. const minor = 12;
  10. const patch = 0;
  11. const prerelease = "-dev";
  12. const api_level = 14;
  13. const api_level_compat = 0;
  14. const api_prerelease = true;
  15. };
  16. // TODO(bfredl): this is for an upstream issue
  17. pub fn lazyArtifact(d: *std.Build.Dependency, name: []const u8) ?*std.Build.Step.Compile {
  18. var found: ?*std.Build.Step.Compile = null;
  19. for (d.builder.install_tls.step.dependencies.items) |dep_step| {
  20. const inst = dep_step.cast(std.Build.Step.InstallArtifact) orelse continue;
  21. if (std.mem.eql(u8, inst.artifact.name, name)) {
  22. if (found != null) std.debug.panic("artifact name '{s}' is ambiguous", .{name});
  23. found = inst.artifact;
  24. }
  25. }
  26. return found;
  27. }
  28. pub fn build(b: *std.Build) !void {
  29. const target = b.standardTargetOptions(.{});
  30. const optimize = b.standardOptimizeOption(.{});
  31. const t = target.result;
  32. const os_tag = t.os.tag;
  33. const is_windows = (os_tag == .windows);
  34. const is_linux = (os_tag == .linux);
  35. const is_darwin = os_tag.isDarwin();
  36. const modern_unix = is_darwin or os_tag.isBSD() or is_linux;
  37. const cross_compiling = b.option(bool, "cross", "cross compile") orelse false;
  38. // TODO(bfredl): option to set nlua0 target explicitly when cross compiling?
  39. const target_host = if (cross_compiling) b.graph.host else target;
  40. const optimize_host = .ReleaseSafe;
  41. // puc lua 5.1 is not ReleaseSafe "safe"
  42. const optimize_lua = if (optimize == .Debug or optimize == .ReleaseSafe) .ReleaseSmall else optimize;
  43. const arch = t.cpu.arch;
  44. const default_luajit = (is_linux and arch == .x86_64) or (is_darwin and arch == .aarch64);
  45. const use_luajit = b.option(bool, "luajit", "use luajit") orelse default_luajit;
  46. const host_use_luajit = if (cross_compiling) false else use_luajit;
  47. const E = enum { luajit, lua51 };
  48. const ziglua = b.dependency("lua_wrapper", .{
  49. .target = target,
  50. .optimize = optimize_lua,
  51. .lang = if (use_luajit) E.luajit else E.lua51,
  52. .shared = false,
  53. });
  54. const ziglua_host = if (cross_compiling) b.dependency("lua_wrapper", .{
  55. .target = target_host,
  56. .optimize = optimize_lua,
  57. .lang = if (host_use_luajit) E.luajit else E.lua51,
  58. .shared = false,
  59. }) else ziglua;
  60. const lpeg = b.dependency("lpeg", .{});
  61. const iconv_apple = if (cross_compiling and is_darwin) b.lazyDependency("iconv_apple", .{ .target = target, .optimize = optimize }) else null;
  62. // this is currently not necessary, as ziglua currently doesn't use lazy dependencies
  63. // to circumvent ziglua.artifact() failing in a bad way.
  64. // const lua = lazyArtifact(ziglua, "lua") orelse return;
  65. const lua = ziglua.artifact("lua");
  66. const libuv_dep = b.dependency("libuv", .{ .target = target, .optimize = optimize });
  67. const libuv = libuv_dep.artifact("uv");
  68. const libluv = try build_lua.build_libluv(b, target, optimize, lua, libuv);
  69. const utf8proc = b.dependency("utf8proc", .{ .target = target, .optimize = optimize });
  70. const unibilium = b.dependency("unibilium", .{ .target = target, .optimize = optimize });
  71. // TODO(bfredl): fix upstream bugs with UBSAN
  72. const treesitter = b.dependency("treesitter", .{ .target = target, .optimize = .ReleaseFast });
  73. const nlua0 = build_lua.build_nlua0(b, target_host, optimize_host, host_use_luajit, ziglua_host, lpeg);
  74. // usual caveat emptor: might need to force a rebuild if the only change is
  75. // addition of new .c files, as those are not seen by any hash
  76. const subdirs = [_][]const u8{
  77. "", // src/nvim itself
  78. "os/",
  79. "api/",
  80. "api/private/",
  81. "msgpack_rpc/",
  82. "tui/",
  83. "tui/termkey/",
  84. "event/",
  85. "eval/",
  86. "lib/",
  87. "lua/",
  88. "viml/",
  89. "viml/parser/",
  90. "vterm/",
  91. };
  92. // source names _relative_ src/nvim/, not including other src/ subdircs
  93. var nvim_sources = try std.ArrayList(gen.SourceItem).initCapacity(b.allocator, 100);
  94. var nvim_headers = try std.ArrayList([]u8).initCapacity(b.allocator, 100);
  95. // both source headers and the {module}.h.generated.h files
  96. var api_headers = try std.ArrayList(std.Build.LazyPath).initCapacity(b.allocator, 10);
  97. // TODO(bfredl): these should just become subdirs..
  98. 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" };
  99. const unix_only = [_][]const u8{ "unix_defs.h", "pty_proc_unix.c", "pty_proc_unix.h" };
  100. const exclude_list = if (is_windows) &unix_only else &windows_only;
  101. const src_dir = b.build_root.handle;
  102. for (subdirs) |s| {
  103. var dir = try src_dir.openDir(b.fmt("src/nvim/{s}", .{s}), .{ .iterate = true });
  104. defer dir.close();
  105. var it = dir.iterateAssumeFirstIteration();
  106. const api_export = std.mem.eql(u8, s, "api/");
  107. const os_check = std.mem.eql(u8, s, "os/");
  108. entries: while (try it.next()) |entry| {
  109. if (entry.name.len < 3) continue;
  110. if (entry.name[0] < 'a' or entry.name[0] > 'z') continue;
  111. if (os_check) {
  112. for (exclude_list) |name| {
  113. if (std.mem.eql(u8, name, entry.name)) {
  114. continue :entries;
  115. }
  116. }
  117. }
  118. if (std.mem.eql(u8, ".c", entry.name[entry.name.len - 2 ..])) {
  119. try nvim_sources.append(.{ .name = b.fmt("{s}{s}", .{ s, entry.name }), .api_export = api_export });
  120. }
  121. if (std.mem.eql(u8, ".h", entry.name[entry.name.len - 2 ..])) {
  122. try nvim_headers.append(b.fmt("{s}{s}", .{ s, entry.name }));
  123. if (api_export and !std.mem.eql(u8, "ui_events.in.h", entry.name)) {
  124. try api_headers.append(b.path(b.fmt("src/nvim/{s}{s}", .{ s, entry.name })));
  125. }
  126. }
  127. }
  128. }
  129. const support_unittests = use_luajit;
  130. const gen_config = b.addWriteFiles();
  131. const version_lua = gen_config.add("nvim_version.lua", lua_version_info(b));
  132. var config_str = b.fmt("zig build -Doptimize={s}", .{@tagName(optimize)});
  133. if (cross_compiling) {
  134. 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) });
  135. }
  136. const versiondef_step = b.addConfigHeader(.{ .style = .{ .cmake = b.path("cmake.config/versiondef.h.in") } }, .{
  137. .NVIM_VERSION_MAJOR = version.major,
  138. .NVIM_VERSION_MINOR = version.minor,
  139. .NVIM_VERSION_PATCH = version.patch,
  140. .NVIM_VERSION_PRERELEASE = version.prerelease,
  141. .NVIM_VERSION_MEDIUM = "",
  142. .VERSION_STRING = "TODO", // TODO(bfredl): not sure what to put here. summary already in "config_str"
  143. .CONFIG = config_str,
  144. });
  145. _ = gen_config.addCopyFile(versiondef_step.getOutput(), "auto/versiondef.h"); // run_preprocessor() workaronnd
  146. const ptrwidth = t.ptrBitWidth() / 8;
  147. const sysconfig_step = b.addConfigHeader(.{ .style = .{ .cmake = b.path("cmake.config/config.h.in") } }, .{
  148. .SIZEOF_INT = t.cTypeByteSize(.int),
  149. .SIZEOF_INTMAX_T = t.cTypeByteSize(.longlong), // TODO
  150. .SIZEOF_LONG = t.cTypeByteSize(.long),
  151. .SIZEOF_SIZE_T = ptrwidth,
  152. .SIZEOF_VOID_PTR = ptrwidth,
  153. .PROJECT_NAME = "nvim",
  154. .HAVE__NSGETENVIRON = is_darwin,
  155. .HAVE_FD_CLOEXEC = modern_unix,
  156. .HAVE_FSEEKO = modern_unix,
  157. .HAVE_LANGINFO_H = modern_unix,
  158. .HAVE_NL_LANGINFO_CODESET = modern_unix,
  159. .HAVE_NL_MSG_CAT_CNTR = t.isGnuLibC(),
  160. .HAVE_PWD_FUNCS = modern_unix,
  161. .HAVE_READLINK = modern_unix,
  162. .HAVE_STRNLEN = modern_unix,
  163. .HAVE_STRCASECMP = modern_unix,
  164. .HAVE_STRINGS_H = modern_unix,
  165. .HAVE_STRNCASECMP = modern_unix,
  166. .HAVE_STRPTIME = modern_unix,
  167. .HAVE_XATTR = is_linux,
  168. .HAVE_SYS_SDT_H = false,
  169. .HAVE_SYS_UTSNAME_H = modern_unix,
  170. .HAVE_SYS_WAIT_H = false, // unused
  171. .HAVE_TERMIOS_H = modern_unix,
  172. .HAVE_WORKING_LIBINTL = t.isGnuLibC(),
  173. .UNIX = modern_unix,
  174. .CASE_INSENSITIVE_FILENAME = is_darwin or is_windows,
  175. .HAVE_SYS_UIO_H = modern_unix,
  176. .HAVE_READV = modern_unix,
  177. .HAVE_DIRFD_AND_FLOCK = modern_unix,
  178. .HAVE_FORKPTY = modern_unix and !is_darwin, // also on Darwin but we lack the headers :(
  179. .HAVE_BE64TOH = modern_unix and !is_darwin,
  180. .ORDER_BIG_ENDIAN = t.cpu.arch.endian() == .big,
  181. .ENDIAN_INCLUDE_FILE = "endian.h",
  182. .HAVE_EXECINFO_BACKTRACE = modern_unix and !t.isMuslLibC(),
  183. .HAVE_BUILTIN_ADD_OVERFLOW = true,
  184. .HAVE_WIMPLICIT_FALLTHROUGH_FLAG = true,
  185. .HAVE_BITSCANFORWARD64 = null,
  186. .VTERM_TEST_FILE = "test/vterm_test_output", // TODO(bfredl): revisit when porting libvterm tests
  187. });
  188. _ = gen_config.addCopyFile(sysconfig_step.getOutput(), "auto/config.h"); // run_preprocessor() workaronnd
  189. _ = gen_config.add("auto/pathdef.h", b.fmt(
  190. \\char *default_vim_dir = "/usr/local/share/nvim";
  191. \\char *default_vimruntime_dir = "";
  192. \\char *default_lib_dir = "/usr/local/lib/nvim";
  193. , .{}));
  194. // TODO(bfredl): include git version when available
  195. const medium = b.fmt("v{}.{}.{}{s}+zig", .{ version.major, version.minor, version.patch, version.prerelease });
  196. const versiondef_git = gen_config.add("auto/versiondef_git.h", b.fmt(
  197. \\#define NVIM_VERSION_MEDIUM "{s}"
  198. \\#define NVIM_VERSION_BUILD "???"
  199. \\
  200. , .{medium}));
  201. // TODO(zig): using getEmittedIncludeTree() is ugly af. we want unittests
  202. // to reuse the std.build.Module include_path thing
  203. const include_path = [_]LazyPath{
  204. b.path("src/"),
  205. gen_config.getDirectory(),
  206. lua.getEmittedIncludeTree(),
  207. libuv.getEmittedIncludeTree(),
  208. libluv.getEmittedIncludeTree(),
  209. utf8proc.artifact("utf8proc").getEmittedIncludeTree(),
  210. unibilium.artifact("unibilium").getEmittedIncludeTree(),
  211. treesitter.artifact("tree-sitter").getEmittedIncludeTree(),
  212. };
  213. const gen_headers, const funcs_data = try gen.nvim_gen_sources(b, nlua0, &nvim_sources, &nvim_headers, &api_headers, versiondef_git, version_lua);
  214. const test_config_step = b.addWriteFiles();
  215. _ = test_config_step.add("test/cmakeconfig/paths.lua", try test_config(b));
  216. const test_gen_step = b.step("gen_headers", "debug: output generated headers");
  217. const config_install = b.addInstallDirectory(.{ .source_dir = gen_config.getDirectory(), .install_dir = .prefix, .install_subdir = "config/" });
  218. test_gen_step.dependOn(&config_install.step);
  219. test_gen_step.dependOn(&b.addInstallDirectory(.{ .source_dir = gen_headers.getDirectory(), .install_dir = .prefix, .install_subdir = "headers/" }).step);
  220. const nvim_exe = b.addExecutable(.{
  221. .name = "nvim",
  222. .target = target,
  223. .optimize = optimize,
  224. });
  225. nvim_exe.rdynamic = true; // -E
  226. nvim_exe.linkLibrary(lua);
  227. nvim_exe.linkLibrary(libuv);
  228. nvim_exe.linkLibrary(libluv);
  229. if (iconv_apple) |iconv| {
  230. nvim_exe.linkLibrary(iconv.artifact("iconv"));
  231. }
  232. nvim_exe.linkLibrary(utf8proc.artifact("utf8proc"));
  233. nvim_exe.linkLibrary(unibilium.artifact("unibilium"));
  234. nvim_exe.linkLibrary(treesitter.artifact("tree-sitter"));
  235. nvim_exe.addIncludePath(b.path("src"));
  236. nvim_exe.addIncludePath(gen_config.getDirectory());
  237. nvim_exe.addIncludePath(gen_headers.getDirectory());
  238. build_lua.add_lua_modules(nvim_exe.root_module, lpeg, use_luajit, false);
  239. var unit_test_sources = try std.ArrayList([]u8).initCapacity(b.allocator, 10);
  240. if (support_unittests) {
  241. var unit_test_fixtures = try src_dir.openDir("test/unit/fixtures/", .{ .iterate = true });
  242. defer unit_test_fixtures.close();
  243. var it = unit_test_fixtures.iterateAssumeFirstIteration();
  244. while (try it.next()) |entry| {
  245. if (entry.name.len < 3) continue;
  246. if (std.mem.eql(u8, ".c", entry.name[entry.name.len - 2 ..])) {
  247. try unit_test_sources.append(b.fmt("test/unit/fixtures/{s}", .{entry.name}));
  248. }
  249. }
  250. }
  251. const src_paths = try b.allocator.alloc([]u8, nvim_sources.items.len + unit_test_sources.items.len);
  252. for (nvim_sources.items, 0..) |s, i| {
  253. src_paths[i] = b.fmt("src/nvim/{s}", .{s.name});
  254. }
  255. @memcpy(src_paths[nvim_sources.items.len..], unit_test_sources.items);
  256. const flags = [_][]const u8{
  257. "-std=gnu99",
  258. "-DINCLUDE_GENERATED_DECLARATIONS",
  259. "-DZIG_BUILD",
  260. "-D_GNU_SOURCE",
  261. if (support_unittests) "-DUNIT_TESTING" else "",
  262. if (use_luajit) "" else "-DNVIM_VENDOR_BIT",
  263. };
  264. nvim_exe.addCSourceFiles(.{ .files = src_paths, .flags = &flags });
  265. nvim_exe.addCSourceFiles(.{ .files = &.{
  266. "src/xdiff/xdiffi.c",
  267. "src/xdiff/xemit.c",
  268. "src/xdiff/xhistogram.c",
  269. "src/xdiff/xpatience.c",
  270. "src/xdiff/xprepare.c",
  271. "src/xdiff/xutils.c",
  272. "src/cjson/lua_cjson.c",
  273. "src/cjson/fpconv.c",
  274. "src/cjson/strbuf.c",
  275. }, .flags = &flags });
  276. const nvim_exe_step = b.step("nvim_bin", "only the binary (not a fully working install!)");
  277. const nvim_exe_install = b.addInstallArtifact(nvim_exe, .{});
  278. nvim_exe_step.dependOn(&nvim_exe_install.step);
  279. const gen_runtime = try runtime.nvim_gen_runtime(b, nlua0, nvim_exe, funcs_data);
  280. const runtime_install = b.addInstallDirectory(.{ .source_dir = gen_runtime.getDirectory(), .install_dir = .prefix, .install_subdir = "runtime/" });
  281. const nvim = b.step("nvim", "build the editor");
  282. nvim.dependOn(&nvim_exe_install.step);
  283. nvim.dependOn(&runtime_install.step);
  284. const lua_dev_deps = b.dependency("lua_dev_deps", .{});
  285. const test_deps = b.step("test_deps", "test prerequisites");
  286. test_deps.dependOn(&nvim_exe_install.step);
  287. test_deps.dependOn(&runtime_install.step);
  288. test_deps.dependOn(test_fixture(b, "shell-test", null, target, optimize));
  289. test_deps.dependOn(test_fixture(b, "tty-test", libuv, target, optimize));
  290. test_deps.dependOn(test_fixture(b, "pwsh-test", null, target, optimize));
  291. test_deps.dependOn(test_fixture(b, "printargs-test", null, target, optimize));
  292. test_deps.dependOn(test_fixture(b, "printenv-test", null, target, optimize));
  293. test_deps.dependOn(test_fixture(b, "streams-test", libuv, target, optimize));
  294. const parser_c = b.dependency("treesitter_c", .{ .target = target, .optimize = optimize });
  295. test_deps.dependOn(add_ts_parser(b, "c", parser_c.path("."), false, target, optimize));
  296. const parser_markdown = b.dependency("treesitter_markdown", .{ .target = target, .optimize = optimize });
  297. test_deps.dependOn(add_ts_parser(b, "markdown", parser_markdown.path("tree-sitter-markdown/"), true, target, optimize));
  298. test_deps.dependOn(add_ts_parser(b, "markdown_inline", parser_markdown.path("tree-sitter-markdown-inline/"), true, target, optimize));
  299. const parser_vim = b.dependency("treesitter_vim", .{ .target = target, .optimize = optimize });
  300. test_deps.dependOn(add_ts_parser(b, "vim", parser_vim.path("."), true, target, optimize));
  301. const parser_vimdoc = b.dependency("treesitter_vimdoc", .{ .target = target, .optimize = optimize });
  302. test_deps.dependOn(add_ts_parser(b, "vimdoc", parser_vimdoc.path("."), false, target, optimize));
  303. const parser_lua = b.dependency("treesitter_lua", .{ .target = target, .optimize = optimize });
  304. test_deps.dependOn(add_ts_parser(b, "lua", parser_lua.path("."), true, target, optimize));
  305. const parser_query = b.dependency("treesitter_query", .{ .target = target, .optimize = optimize });
  306. test_deps.dependOn(add_ts_parser(b, "query", parser_query.path("."), false, target, optimize));
  307. const unit_headers: ?[]const LazyPath = if (support_unittests) &(include_path ++ .{gen_headers.getDirectory()}) else null;
  308. try tests.test_steps(b, nvim_exe, test_deps, lua_dev_deps.path("."), test_config_step.getDirectory(), unit_headers);
  309. }
  310. pub fn test_fixture(
  311. b: *std.Build,
  312. name: []const u8,
  313. libuv: ?*std.Build.Step.Compile,
  314. target: std.Build.ResolvedTarget,
  315. optimize: std.builtin.OptimizeMode,
  316. ) *std.Build.Step {
  317. const fixture = b.addExecutable(.{
  318. .name = name,
  319. .target = target,
  320. .optimize = optimize,
  321. });
  322. const source = if (std.mem.eql(u8, name, "pwsh-test")) "shell-test" else name;
  323. fixture.addCSourceFile(.{ .file = b.path(b.fmt("./test/functional/fixtures/{s}.c", .{source})) });
  324. fixture.linkLibC();
  325. if (libuv) |uv| fixture.linkLibrary(uv);
  326. return &b.addInstallArtifact(fixture, .{}).step;
  327. }
  328. pub fn add_ts_parser(
  329. b: *std.Build,
  330. name: []const u8,
  331. parser_dir: LazyPath,
  332. scanner: bool,
  333. target: std.Build.ResolvedTarget,
  334. optimize: std.builtin.OptimizeMode,
  335. ) *std.Build.Step {
  336. const parser = b.addLibrary(.{
  337. .name = name,
  338. .root_module = b.createModule(.{
  339. .target = target,
  340. .optimize = optimize,
  341. }),
  342. .linkage = .dynamic,
  343. });
  344. parser.addCSourceFile(.{ .file = parser_dir.path(b, "src/parser.c") });
  345. if (scanner) parser.addCSourceFile(.{ .file = parser_dir.path(b, "src/scanner.c") });
  346. parser.addIncludePath(parser_dir.path(b, "src"));
  347. parser.linkLibC();
  348. const parser_install = b.addInstallArtifact(parser, .{ .dest_sub_path = b.fmt("parser/{s}.so", .{name}) });
  349. return &parser_install.step;
  350. }
  351. pub fn lua_version_info(b: *std.Build) []u8 {
  352. const v = version;
  353. return b.fmt(
  354. \\return {{
  355. \\ {{"major", {}}},
  356. \\ {{"minor", {}}},
  357. \\ {{"patch", {}}},
  358. \\ {{"prerelease", {}}},
  359. \\ {{"api_level", {}}},
  360. \\ {{"api_compatible", {}}},
  361. \\ {{"api_prerelease", {}}},
  362. \\}}
  363. , .{ v.major, v.minor, v.patch, v.prerelease.len > 0, v.api_level, v.api_level_compat, v.api_prerelease });
  364. }
  365. pub fn test_config(b: *std.Build) ![]u8 {
  366. var buf: [std.fs.max_path_bytes]u8 = undefined;
  367. const src_path = try b.build_root.handle.realpath(".", &buf);
  368. // we don't use test/cmakeconfig/paths.lua.in because it contains cmake specific logic
  369. return b.fmt(
  370. \\local M = {{}}
  371. \\
  372. \\M.apple_sysroot = ""
  373. \\M.translations_enabled = "$ENABLE_TRANSLATIONS" == "ON"
  374. \\M.is_asan = "$ENABLE_ASAN_UBSAN" == "ON"
  375. \\M.is_zig_build = true
  376. \\M.vterm_test_file = "test/vterm_test_output"
  377. \\M.test_build_dir = "{[bin_dir]s}" -- bull
  378. \\M.test_source_path = "{[src_path]s}"
  379. \\M.test_lua_prg = ""
  380. \\M.test_luajit_prg = ""
  381. \\ -- include path passed on the cmdline, see test/lua_runner.lua
  382. \\M.include_paths = _G.c_include_path or {{}}
  383. \\
  384. \\return M
  385. , .{ .bin_dir = b.install_path, .src_path = src_path });
  386. }