From 0458a1e694196c6d0959a96c732b8daf4d30c306 Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 7 Aug 2025 11:36:21 +0200 Subject: [PATCH] build(ci): bump zig to 0.15.1 and add more platforms - Bump zig version to 0.15.1 and workaround zig fetch hang (ziglang/zig#24916) - add mac os zig build (currently without luajit, linker failure) - Add windows zig build, currently with very limited testing --- .github/workflows/test.yml | 48 ++++++++++++++++++++- build.zig | 45 +++++++++++++------ build.zig.zon | 2 +- src/gen/gen_api_dispatch.lua | 2 +- src/gen/gen_steps.zig | 2 +- test/functional/lua/option_and_var_spec.lua | 10 ++--- test/functional/lua/xdiff_spec.lua | 15 ++++--- test/functional/preload.lua | 4 +- 8 files changed, 95 insertions(+), 33 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b02b7e555f..257cf7b8ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -204,7 +204,8 @@ jobs: name: Show logs run: cat $(find "$LOG_DIR" -type f) - zig-build: + # TODO: matrixify + zig-build-linux: runs-on: ubuntu-24.04 timeout-minutes: 45 name: build using zig build (linux) @@ -212,8 +213,15 @@ jobs: - uses: actions/checkout@v5 - uses: mlugg/setup-zig@v2 with: - version: 0.14.1 + version: 0.15.1 - run: sudo apt-get install -y inotify-tools + + # This is a workaround for "zig fetch" being unable to decompress lua-dev-deps.tar.gz + # As the hash in build.zig.zon is calculated after decompression, we can preload the cache instead. + # This is hopefully fixed for zig 0.15.2, see https://github.com/ziglang/zig/issues/24916 + - run: curl -L https://github.com/neovim/deps/raw/06ef2b58b0876f8de1a3f5a710473dcd7afff251/opt/lua-dev-deps.tar.gz | zcat > lua-dev-deps.tar + - run: zig fetch lua-dev-deps.tar + - run: zig build test_nlua0 - run: zig build nvim && ./zig-out/bin/nvim --version - run: zig build unittest @@ -223,6 +231,42 @@ jobs: - run: cd runtime; ../zig-out/bin/nvim -u NONE -i NONE -e --headless -c "helptags ++t doc" -c quit - run: diff -u runtime/doc/tags zig-out/runtime/doc/tags + zig-build-macos-15: + runs-on: macos-15 + timeout-minutes: 45 + name: build using zig build (macos 15) + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: 0.15.1 + + - run: curl -L https://github.com/neovim/deps/raw/06ef2b58b0876f8de1a3f5a710473dcd7afff251/opt/lua-dev-deps.tar.gz | zcat > lua-dev-deps.tar + - run: zig fetch lua-dev-deps.tar + + - run: zig build test_nlua0 -Dluajit=false + - run: zig build nvim_bin -Dluajit=false && ./zig-out/bin/nvim --version + - run: zig build functionaltest -Dluajit=false + + zig-build-windows: + runs-on: windows-2022 + timeout-minutes: 45 + name: build using zig build (windows) + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: 0.15.1 + + - run: curl -L https://github.com/neovim/deps/raw/06ef2b58b0876f8de1a3f5a710473dcd7afff251/opt/lua-dev-deps.tar.gz -O + - run: 7z x lua-dev-deps.tar.gz + - run: zig fetch lua-dev-deps.tar + - run: zig build test_nlua0 + - run: zig build nvim_bin + - run: ./zig-out/bin/nvim --version + # TODO: support entire test suite + - run: zig build functionaltest -- test/functional/api/buffer_spec.lua + windows: uses: ./.github/workflows/test_windows.yml diff --git a/build.zig b/build.zig index ea1d45077b..13f5d8eeb1 100644 --- a/build.zig +++ b/build.zig @@ -43,7 +43,8 @@ pub fn build(b: *std.Build) !void { 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; - const optimize_host = .ReleaseSafe; + // without cross_compiling we like to reuse libluv etc at the same optimize level + const optimize_host = if (cross_compiling) .ReleaseSafe else optimize; // puc lua 5.1 is not ReleaseSafe "safe" const optimize_lua = if (optimize == .Debug or optimize == .ReleaseSafe) .ReleaseSmall else optimize; @@ -63,7 +64,7 @@ pub fn build(b: *std.Build) !void { const ziglua_host = if (cross_compiling) b.dependency("zlua", .{ .target = target_host, - .optimize = optimize_lua, + .optimize = .ReleaseSmall, .lang = if (host_use_luajit) E.luajit else E.lua51, .shared = false, }) else ziglua; @@ -146,12 +147,12 @@ pub fn build(b: *std.Build) !void { } } if (std.mem.eql(u8, ".c", entry.name[entry.name.len - 2 ..])) { - try nvim_sources.append(.{ .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.fmt("{s}{s}", .{ s, entry.name })); + try nvim_headers.append(b.allocator, b.fmt("{s}{s}", .{ s, entry.name })); if (api_export and !std.mem.eql(u8, "ui_events.in.h", entry.name)) { - try api_headers.append(b.path(b.fmt("src/nvim/{s}{s}", .{ s, entry.name }))); + try api_headers.append(b.allocator, b.path(b.fmt("src/nvim/{s}{s}", .{ s, entry.name }))); } } } @@ -296,7 +297,7 @@ pub fn build(b: *std.Build) !void { while (try it.next()) |entry| { if (entry.name.len < 3) continue; if (std.mem.eql(u8, ".c", entry.name[entry.name.len - 2 ..])) { - try unit_test_sources.append(b.fmt("test/unit/fixtures/{s}", .{entry.name})); + try unit_test_sources.append(b.allocator, b.fmt("test/unit/fixtures/{s}", .{entry.name})); } } } @@ -331,6 +332,10 @@ pub fn build(b: *std.Build) !void { "src/cjson/strbuf.c", }, .flags = &flags }); + if (is_windows) { + nvim_exe.addWin32ResourceFile(.{ .file = b.path("src/nvim/os/nvim.rc") }); + } + const nvim_exe_step = b.step("nvim_bin", "only the binary (not a fully working install!)"); const nvim_exe_install = b.addInstallArtifact(nvim_exe, .{}); @@ -350,12 +355,12 @@ pub fn build(b: *std.Build) !void { test_deps.dependOn(&nvim_exe_install.step); test_deps.dependOn(&runtime_install.step); - test_deps.dependOn(test_fixture(b, "shell-test", null, target, optimize)); - test_deps.dependOn(test_fixture(b, "tty-test", libuv, target, optimize)); - test_deps.dependOn(test_fixture(b, "pwsh-test", null, target, optimize)); - test_deps.dependOn(test_fixture(b, "printargs-test", null, target, optimize)); - test_deps.dependOn(test_fixture(b, "printenv-test", null, target, optimize)); - test_deps.dependOn(test_fixture(b, "streams-test", libuv, target, optimize)); + 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)); 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)); @@ -382,6 +387,7 @@ pub fn test_fixture( libuv: ?*std.Build.Step.Compile, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, + flags: []const []const u8, ) *std.Build.Step { const fixture = b.addExecutable(.{ .name = name, @@ -391,7 +397,11 @@ pub fn test_fixture( }), }); const source = if (std.mem.eql(u8, name, "pwsh-test")) "shell-test" else name; - fixture.addCSourceFile(.{ .file = b.path(b.fmt("./test/functional/fixtures/{s}.c", .{source})) }); + if (std.mem.eql(u8, name, "printenv-test")) { + 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.linkLibC(); if (libuv) |uv| fixture.linkLibrary(uv); return &b.addInstallArtifact(fixture, .{}).step; @@ -437,6 +447,13 @@ pub fn lua_version_info(b: *std.Build) []u8 { , .{ v.major, v.minor, v.patch, v.prerelease.len > 0, v.api_level, v.api_level_compat, v.api_prerelease }); } +fn esc(b: *std.Build, input: []const u8) ![]const u8 { + return if (b.graph.host.result.os.tag == .windows) + std.mem.replaceOwned(u8, b.graph.arena, input, "\\", "/") + else + input; +} + pub fn test_config(b: *std.Build) ![]u8 { var buf: [std.fs.max_path_bytes]u8 = undefined; const src_path = try b.build_root.handle.realpath(".", &buf); @@ -458,5 +475,5 @@ pub fn test_config(b: *std.Build) ![]u8 { \\M.include_paths = _G.c_include_path or {{}} \\ \\return M - , .{ .bin_dir = b.install_path, .src_path = src_path }); + , .{ .bin_dir = try esc(b, b.install_path), .src_path = try esc(b, src_path) }); } diff --git a/build.zig.zon b/build.zig.zon index ea1975c3a8..12895cb746 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,7 +2,7 @@ .name = .neovim, .fingerprint = 0x66eb090879307a38, .version = "0.12.0", - .minimum_zig_version = "0.14.0", + .minimum_zig_version = "0.15.0", .dependencies = .{ .zlua = .{ diff --git a/src/gen/gen_api_dispatch.lua b/src/gen/gen_api_dispatch.lua index b62f1bfeb6..72df33b970 100644 --- a/src/gen/gen_api_dispatch.lua +++ b/src/gen/gen_api_dispatch.lua @@ -171,7 +171,7 @@ local ui_options_text = nil for i = pre_args + 1, #arg do local full_path = arg[i] local parts = {} --- @type string[] - for part in full_path:gmatch('[^/]+') do + for part in full_path:gmatch('[^/\\]+') do parts[#parts + 1] = part end headers[#headers + 1] = parts[#parts - 1] .. '/' .. parts[#parts] diff --git a/src/gen/gen_steps.zig b/src/gen/gen_steps.zig index 7aeec2a71c..7d3d9bb8ae 100644 --- a/src/gen/gen_steps.zig +++ b/src/gen/gen_steps.zig @@ -186,7 +186,7 @@ fn generate_header_for( } else { const h_file = gen_header(b, run_step, b.fmt("{s}.h.generated.h", .{basename}), gen_headers); if (api_export) |api_files| { - try api_files.append(h_file); + try api_files.append(b.allocator, h_file); } } diff --git a/test/functional/lua/option_and_var_spec.lua b/test/functional/lua/option_and_var_spec.lua index 69fc47d93c..4953100622 100644 --- a/test/functional/lua/option_and_var_spec.lua +++ b/test/functional/lua/option_and_var_spec.lua @@ -463,9 +463,9 @@ describe('lua stdlib', function() eq(false, fn.luaeval "vim.v['false']") eq(NIL, fn.luaeval 'vim.v.null') matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.v[0].progpath')) - eq('Key is read-only: count', pcall_err(exec_lua, [[vim.v.count = 42]])) - eq('Dict is locked', pcall_err(exec_lua, [[vim.v.nosuchvar = 42]])) - eq('Key is fixed: errmsg', pcall_err(exec_lua, [[vim.v.errmsg = nil]])) + matches('Key is read%-only: count$', pcall_err(exec_lua, [[vim.v.count = 42]])) + matches('Dict is locked$', pcall_err(exec_lua, [[vim.v.nosuchvar = 42]])) + matches('Key is fixed: errmsg$', pcall_err(exec_lua, [[vim.v.errmsg = nil]])) exec_lua([[vim.v.errmsg = 'set by Lua']]) eq('set by Lua', eval('v:errmsg')) exec_lua([[vim.v.errmsg = 42]]) @@ -474,8 +474,8 @@ describe('lua stdlib', function() eq({ 'one', 'two' }, eval('v:oldfiles')) exec_lua([[vim.v.oldfiles = {}]]) eq({}, eval('v:oldfiles')) - eq( - 'Setting v:oldfiles to value with wrong type', + matches( + 'Setting v:oldfiles to value with wrong type$', pcall_err(exec_lua, [[vim.v.oldfiles = 'a']]) ) eq({}, eval('v:oldfiles')) diff --git a/test/functional/lua/xdiff_spec.lua b/test/functional/lua/xdiff_spec.lua index 3ffe16e4d9..963cb6b5fc 100644 --- a/test/functional/lua/xdiff_spec.lua +++ b/test/functional/lua/xdiff_spec.lua @@ -4,6 +4,7 @@ local n = require('test.functional.testnvim')() local clear = n.clear local exec_lua = n.exec_lua local eq = t.eq +local matches = t.matches local pcall_err = t.pcall_err describe('xdiff bindings', function() @@ -169,25 +170,25 @@ describe('xdiff bindings', function() end) it('can handle bad args', function() - eq([[Expected at least 2 arguments]], pcall_err(exec_lua, [[vim.text.diff('a')]])) + matches([[Expected at least 2 arguments$]], pcall_err(exec_lua, [[vim.text.diff('a')]])) - t.matches( + matches( [[bad argument %#1 to '_?diff' %(expected string%)]], pcall_err(exec_lua, [[vim.text.diff(1, 2)]]) ) - t.matches( + matches( [[bad argument %#3 to '_?diff' %(expected table%)]], pcall_err(exec_lua, [[vim.text.diff('a', 'b', true)]]) ) - eq( - [[invalid key: bad_key]], + matches( + [[invalid key: bad_key$]], pcall_err(exec_lua, [[vim.text.diff('a', 'b', { bad_key = true })]]) ) - eq( - [[on_hunk is not a function]], + matches( + [[on_hunk is not a function$]], pcall_err(exec_lua, [[vim.text.diff('a', 'b', { on_hunk = true })]]) ) end) diff --git a/test/functional/preload.lua b/test/functional/preload.lua index e524ab34aa..02832bb988 100644 --- a/test/functional/preload.lua +++ b/test/functional/preload.lua @@ -4,8 +4,8 @@ local t = require('test.testutil') require('test.functional.ui.screen') -if t.is_os('win') then - local ffi = require('ffi') +local has_ffi, ffi = pcall(require, 'ffi') +if t.is_os('win') and has_ffi then ffi.cdef [[ typedef int errno_t; errno_t _set_fmode(int mode);