diff --git a/build.zig b/build.zig index facaa2cf4f..66603c3118 100644 --- a/build.zig +++ b/build.zig @@ -39,6 +39,12 @@ pub fn lazyArtifact(d: *std.Build.Dependency, name: []const u8) ?*std.Build.Step return found; } +fn addEmscriptenIncludes(emscripten_include: ?std.Build.LazyPath, artifact: *std.Build.Step.Compile) void { + if (emscripten_include) |inc| { + artifact.root_module.addSystemIncludePath(inc); + } +} + pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); @@ -50,10 +56,30 @@ pub fn build(b: *std.Build) !void { const is_linux = (os_tag == .linux); const is_darwin = os_tag.isDarwin(); const modern_unix = is_darwin or os_tag.isBSD() or is_linux; + const is_wasm = t.cpu.arch == .wasm32; + + const cross_compiling = b.option(bool, "cross", "cross compile") orelse is_wasm; + const emscripten_sysroot = b.option([]const u8, "emscripten-sysroot", "path to emscripten sysroot"); + const emscripten_include = if (emscripten_sysroot) |s| + std.Build.LazyPath{ .cwd_relative = b.pathJoin(&.{ s, "include" }) } + else + null; + + const emscripten_libc_path: ?std.Build.LazyPath = if (is_wasm) blk: { + if (emscripten_sysroot) |s| { + const libc_txt = b.fmt( + "include_dir={s}/include\nsys_include_dir={s}/include\ncrt_dir={s}/lib/wasm32-emscripten\nmsvc_lib_dir=\nkernel32_lib_dir=\ngcc_dir=\n", + .{ s, s, s }, + ); + const wf = b.addWriteFiles(); + break :blk wf.add("emscripten-libc.txt", libc_txt); + } + break :blk null; + } else null; - const cross_compiling = b.option(bool, "cross", "cross compile") orelse false; // TODO(bfredl): option to set nlua0 target explicitly when cross compiling? const target_host = if (cross_compiling) b.graph.host else target; + // without cross_compiling we like to reuse libluv etc at the same optimize level const optimize_host = if (cross_compiling) .ReleaseSafe else optimize; @@ -62,7 +88,7 @@ pub fn build(b: *std.Build) !void { // puc lua 5.1 is not ReleaseSafe "safe" const optimize_lua = if (optimize == .Debug or optimize == .ReleaseSafe) .ReleaseSmall else optimize; - const use_luajit = b.option(bool, "luajit", "use luajit") orelse true; + const use_luajit = b.option(bool, "luajit", "use luajit") orelse !is_wasm; 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 }; @@ -98,6 +124,8 @@ pub fn build(b: *std.Build) !void { // 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; + addEmscriptenIncludes(emscripten_include, lua.?); + if (cross_compiling) { _ = lazyArtifact(ziglua_host, "lua") orelse return; } @@ -108,8 +136,14 @@ pub fn build(b: *std.Build) !void { const optimize_uv = if (optimize == .Debug and target.result.os.tag == .windows) .ReleaseSafe else optimize; if (b.lazyDependency("libuv", .{ .target = target, .optimize = optimize_uv })) |dep| { libuv = dep.artifact("uv"); + + if (emscripten_libc_path) |lp| libuv.?.setLibCFile(lp); + addEmscriptenIncludes(emscripten_include, libuv.?); + libluv = try build_lua.build_libluv(b, target, optimize, lua, libuv.?, use_luajit); + if (libluv) |luv| addEmscriptenIncludes(emscripten_include, luv); + libluv_host = if (cross_compiling) libluv_host: { const libuv_dep_host = b.lazyDependency("libuv", .{ .target = target_host, @@ -139,6 +173,7 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, }); + const unibilium = if (use_unibilium and !sys_opts.unibilium) b.lazyDependency("unibilium", .{ .target = target, .optimize = optimize, @@ -151,6 +186,15 @@ pub fn build(b: *std.Build) !void { .optimize = optimize_ts, }); + if (utf8proc) |dep| + addEmscriptenIncludes(emscripten_include, dep.artifact("utf8proc")); + + if (unibilium) |dep| + addEmscriptenIncludes(emscripten_include, dep.artifact("unibilium")); + + if (treesitter) |dep| + addEmscriptenIncludes(emscripten_include, dep.artifact("tree-sitter")); + const nlua0 = try build_lua.build_nlua0( b, io, @@ -442,8 +486,16 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, .link_libc = true, }); - const nvim_exe = b.addExecutable(.{ .name = "nvim", .root_module = nvim_mod }); + + const nvim_exe = if (is_wasm) b.addLibrary(.{ + .linkage = .static, + .name = "nvim", + .root_module = nvim_mod, + }) else b.addExecutable(.{ .name = "nvim", .root_module = nvim_mod }); nvim_exe.rdynamic = true; // -E + if (emscripten_libc_path) |lp| nvim_exe.setLibCFile(lp); + if (is_wasm) nvim_exe.entry = .disabled; + if (is_wasm) nvim_exe.linker_allow_shlib_undefined = true; if (sys_opts.lua) { nvim_exe.root_module.linkSystemLibrary(lualib_name, .{}); @@ -496,11 +548,15 @@ pub fn build(b: *std.Build) !void { } } - const src_paths = try b.allocator.alloc([]u8, nvim_sources.items.len + unit_test_sources.items.len); - for (nvim_sources.items, 0..) |s, i| { - src_paths[i] = b.fmt("src/nvim/{s}", .{s.name}); + var filtered_sources = try std.ArrayList([]u8).initCapacity(b.allocator, nvim_sources.items.len); + for (nvim_sources.items) |s| { + // wasm_stubs.c defines libuv symbols & excludes it when real libuv is linked + if (!is_wasm and std.mem.eql(u8, s.name, "wasm_stubs.c")) continue; + try filtered_sources.append(b.allocator, b.fmt("src/nvim/{s}", .{s.name})); } - @memcpy(src_paths[nvim_sources.items.len..], unit_test_sources.items); + const src_paths = try b.allocator.alloc([]u8, filtered_sources.items.len + unit_test_sources.items.len); + @memcpy(src_paths[0..filtered_sources.items.len], filtered_sources.items); + @memcpy(src_paths[filtered_sources.items.len..], unit_test_sources.items); const flags = [_][]const u8{ "-std=gnu99", @@ -532,15 +588,46 @@ pub fn build(b: *std.Build) !void { } const nvim_exe_step = b.step("nvim_bin", "only the binary (not a fully working install!)"); - const nvim_exe_install = b.addInstallArtifact(nvim_exe, .{}); + const nvim_exe_install = b.addInstallArtifact(nvim_exe, .{ .dest_dir = if (is_wasm) .{ .override = .{ .custom = "wasm" } } else .default }); - nvim_exe_step.dependOn(&nvim_exe_install.step); + if (is_wasm) { + const s = emscripten_sysroot orelse @panic("-Demscripten-sysroot required for wasm target"); + const emcc = b.addSystemCommand(&.{"emcc"}); + emcc.step.dependOn(&nvim_exe.step); + + emcc.addArg("-Wl,--whole-archive"); + emcc.addFileArg(nvim_exe.getEmittedBin()); + if (lua) |l| emcc.addFileArg(l.getEmittedBin()); + if (libuv) |uv| emcc.addFileArg(uv.getEmittedBin()); + if (libluv) |luv| emcc.addFileArg(luv.getEmittedBin()); + if (utf8proc) |dep| emcc.addFileArg(dep.artifact("utf8proc").getEmittedBin()); + if (unibilium) |dep| emcc.addFileArg(dep.artifact("unibilium").getEmittedBin()); + if (treesitter) |dep| emcc.addFileArg(dep.artifact("tree-sitter").getEmittedBin()); + emcc.addArg("-Wl,--no-whole-archive"); + + emcc.addArgs(&.{ + b.fmt("--sysroot={s}", .{s}), + "-sALLOW_MEMORY_GROWTH=1", + "-sEXPORT_ALL=1", + "-sINITIAL_MEMORY=67108864", + "-sFORCE_FILESYSTEM=1", + "-sERROR_ON_UNDEFINED_SYMBOLS=0", + "--no-entry", + "-Wl,--export-all", + "-Wl,--no-gc-sections", + }); + emcc.addArg("-o"); + const nvim_js = emcc.addOutputFileArg("nvim.js"); + nvim_exe_step.dependOn(&b.addInstallFileWithDir(nvim_js, .bin, "nvim.js").step); + nvim_exe_step.dependOn(&b.addInstallFileWithDir(nvim_js.dirname().path(b, "nvim.wasm"), .bin, "nvim.wasm").step); + } else { + nvim_exe_step.dependOn(&nvim_exe_install.step); + } const gen_runtime = try runtime.nvim_gen_runtime(b, nlua0, funcs_data); 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 + if (!is_wasm) 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, @@ -551,7 +638,7 @@ pub fn build(b: *std.Build) !void { const nvim_dev = b.step("nvim_dev", "build the editor for development"); b.default_step = nvim_dev; - nvim_dev.dependOn(&nvim_exe_install.step); + if (!is_wasm) nvim_dev.dependOn(&nvim_exe_install.step); nvim_dev.dependOn(&test_runtime_install.step); // run from dev environment @@ -568,7 +655,7 @@ pub fn build(b: *std.Build) !void { // installation const install = b.getInstallStep(); - install.dependOn(&nvim_exe_install.step); + if (!is_wasm) install.dependOn(&nvim_exe_install.step); b.installDirectory(.{ .source_dir = b.path("runtime/"), .install_dir = .prefix,