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
This commit is contained in:
bfredl
2025-08-07 11:36:21 +02:00
parent 2debe2f30a
commit 0458a1e694
8 changed files with 95 additions and 33 deletions

View File

@@ -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

View File

@@ -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) });
}

View File

@@ -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 = .{

View File

@@ -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]

View File

@@ -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);
}
}

View File

@@ -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'))

View File

@@ -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)

View File

@@ -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);