Windows build fixes (#11195)

Some more fixes to get Windows building again. `zig build` on
x64_64-windows now succeeds but `zig build test` fails in
`src/terminal/page.zig` because Zig/Windows lacks a POSIX `mmap`
implementation.
This commit is contained in:
Mitchell Hashimoto
2026-03-05 12:59:06 -08:00
committed by GitHub
6 changed files with 97 additions and 22 deletions

View File

@@ -6,6 +6,7 @@
const CodepointWidth = @This();
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Benchmark = @import("Benchmark.zig");
@@ -104,6 +105,11 @@ fn stepNoop(ptr: *anyopaque) Benchmark.Error!void {
extern "c" fn wcwidth(c: u32) c_int;
fn stepWcwidth(ptr: *anyopaque) Benchmark.Error!void {
if (comptime builtin.os.tag == .windows) {
log.warn("wcwidth is not available on Windows", .{});
return;
}
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return;

View File

@@ -57,6 +57,16 @@ pub fn clear(self: DiskCache) !void {
pub const AddResult = enum { added, updated };
pub const AddError = std.fs.Dir.MakeError ||
std.fs.Dir.StatFileError ||
std.fs.File.OpenError ||
std.fs.File.ChmodError ||
std.io.Reader.LimitedAllocError ||
FixupPermissionsError ||
ReadEntriesError ||
WriteCacheFileError ||
Error;
/// Add or update a hostname entry in the cache.
/// Returns AddResult.added for new entries or AddResult.updated for existing ones.
/// The cache file is created if it doesn't exist with secure permissions (0600).
@@ -64,7 +74,7 @@ pub fn add(
self: DiskCache,
alloc: Allocator,
hostname: []const u8,
) !AddResult {
) AddError!AddResult {
if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid;
// Create cache directory if needed
@@ -128,13 +138,19 @@ pub fn add(
return result;
}
pub const RemoveError = std.fs.File.OpenError ||
FixupPermissionsError ||
ReadEntriesError ||
WriteCacheFileError ||
Error;
/// Remove a hostname entry from the cache.
/// No error is returned if the hostname doesn't exist or the cache file is missing.
pub fn remove(
self: DiskCache,
alloc: Allocator,
hostname: []const u8,
) !void {
) RemoveError!void {
if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid;
// Open our file
@@ -168,13 +184,17 @@ pub fn remove(
try self.writeCacheFile(entries, null);
}
pub const ContainsError = std.fs.File.OpenError ||
ReadEntriesError ||
error{HostnameIsInvalid};
/// Check if a hostname exists in the cache.
/// Returns false if the cache file doesn't exist.
pub fn contains(
self: DiskCache,
alloc: Allocator,
hostname: []const u8,
) !bool {
) ContainsError!bool {
if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid;
// Open our file
@@ -194,7 +214,9 @@ pub fn contains(
return entries.contains(hostname);
}
fn fixupPermissions(file: std.fs.File) (std.fs.File.StatError || std.fs.File.ChmodError)!void {
pub const FixupPermissionsError = (std.fs.File.StatError || std.fs.File.ChmodError);
fn fixupPermissions(file: std.fs.File) FixupPermissionsError!void {
// Windows does not support chmod
if (comptime builtin.os.tag == .windows) return;
@@ -206,11 +228,18 @@ fn fixupPermissions(file: std.fs.File) (std.fs.File.StatError || std.fs.File.Chm
}
}
pub const WriteCacheFileError = std.fs.Dir.OpenError ||
std.fs.AtomicFile.InitError ||
std.fs.AtomicFile.FlushError ||
std.fs.AtomicFile.FinishError ||
Entry.FormatError ||
error{InvalidCachePath};
fn writeCacheFile(
self: DiskCache,
entries: std.StringHashMap(Entry),
expire_days: ?u32,
) !void {
) WriteCacheFileError!void {
const cache_dir = std.fs.path.dirname(self.path) orelse return error.InvalidCachePath;
const cache_basename = std.fs.path.basename(self.path);
@@ -270,10 +299,12 @@ pub fn deinitEntries(
entries.deinit();
}
pub const ReadEntriesError = std.mem.Allocator.Error || std.io.Reader.LimitedAllocError;
fn readEntries(
alloc: Allocator,
file: std.fs.File,
) !std.StringHashMap(Entry) {
) ReadEntriesError!std.StringHashMap(Entry) {
var reader = file.reader(&.{});
const content = try reader.interface.allocRemaining(
alloc,

View File

@@ -33,7 +33,9 @@ pub fn parse(line: []const u8) ?Entry {
};
}
pub fn format(self: Entry, writer: *std.Io.Writer) !void {
pub const FormatError = std.Io.Writer.Error;
pub fn format(self: Entry, writer: *std.Io.Writer) FormatError!void {
try writer.print(
"{s}|{d}|{s}\n",
.{ self.hostname, self.timestamp, self.terminfo_version },

View File

@@ -13,7 +13,7 @@ const Error = error{
/// is generally an expensive process so the value should be cached.
pub inline fn home(buf: []u8) !?[]const u8 {
return switch (builtin.os.tag) {
inline .linux, .freebsd, .macos => try homeUnix(buf),
.linux, .freebsd, .macos => try homeUnix(buf),
.windows => try homeWindows(buf),
// iOS doesn't have a user-writable home directory
@@ -122,7 +122,13 @@ pub const ExpandError = error{
pub fn expandHome(path: []const u8, buf: []u8) ExpandError![]const u8 {
return switch (builtin.os.tag) {
.linux, .freebsd, .macos => try expandHomeUnix(path, buf),
// `~/` is not an idiom generally used on Windows
.windows => return path,
// iOS doesn't have a user-writable home directory
.ios => return path,
else => @compileError("unimplemented"),
};
}

View File

@@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const posix = std.posix;
pub const LocalHostnameValidationError = error{
@@ -99,9 +100,21 @@ pub fn isLocal(hostname: []const u8) LocalHostnameValidationError!bool {
if (std.mem.eql(u8, "localhost", hostname)) return true;
// If hostname is not "localhost" it must match our hostname.
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
const ourHostname = try posix.gethostname(&buf);
return std.mem.eql(u8, hostname, ourHostname);
switch (builtin.os.tag) {
.windows => {
const windows = @import("windows.zig");
var buf: [256:0]u8 = undefined;
var nSize: windows.DWORD = buf.len;
if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == 0) return false;
const ourHostname = buf[0..nSize];
return std.mem.eql(u8, hostname, ourHostname);
},
else => {
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
const ourHostname = try posix.gethostname(&buf);
return std.mem.eql(u8, hostname, ourHostname);
},
}
}
test "isLocal returns true when provided hostname is localhost" {
@@ -109,9 +122,21 @@ test "isLocal returns true when provided hostname is localhost" {
}
test "isLocal returns true when hostname is local" {
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
const localHostname = try posix.gethostname(&buf);
try std.testing.expect(try isLocal(localHostname));
switch (builtin.os.tag) {
.windows => {
const windows = @import("windows.zig");
var buf: [256:0]u8 = undefined;
var nSize: windows.DWORD = buf.len;
if (windows.exp.kernel32.GetComputerNameA(&buf, &nSize) == 0) return error.GetComputerNameFailed;
const localHostname = buf[0..nSize];
try std.testing.expect(try isLocal(localHostname));
},
else => {
var buf: [posix.HOST_NAME_MAX]u8 = undefined;
const localHostname = try posix.gethostname(&buf);
try std.testing.expect(try isLocal(localHostname));
},
}
}
test "isLocal returns false when hostname is not local" {

View File

@@ -53,22 +53,22 @@ pub const exp = struct {
hWritePipe: *windows.HANDLE,
lpPipeAttributes: ?*const windows.SECURITY_ATTRIBUTES,
nSize: windows.DWORD,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
pub extern "kernel32" fn CreatePseudoConsole(
size: windows.COORD,
hInput: windows.HANDLE,
hOutput: windows.HANDLE,
dwFlags: windows.DWORD,
phPC: *HPCON,
) callconv(windows.WINAPI) windows.HRESULT;
pub extern "kernel32" fn ResizePseudoConsole(hPC: HPCON, size: windows.COORD) callconv(windows.WINAPI) windows.HRESULT;
pub extern "kernel32" fn ClosePseudoConsole(hPC: HPCON) callconv(windows.WINAPI) void;
) callconv(.winapi) windows.HRESULT;
pub extern "kernel32" fn ResizePseudoConsole(hPC: HPCON, size: windows.COORD) callconv(.winapi) windows.HRESULT;
pub extern "kernel32" fn ClosePseudoConsole(hPC: HPCON) callconv(.winapi) void;
pub extern "kernel32" fn InitializeProcThreadAttributeList(
lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
dwAttributeCount: windows.DWORD,
dwFlags: windows.DWORD,
lpSize: *windows.SIZE_T,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
pub extern "kernel32" fn UpdateProcThreadAttribute(
lpAttributeList: LPPROC_THREAD_ATTRIBUTE_LIST,
dwFlags: windows.DWORD,
@@ -77,7 +77,7 @@ pub const exp = struct {
cbSize: windows.SIZE_T,
lpPreviousValue: ?windows.PVOID,
lpReturnSize: ?*windows.SIZE_T,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
pub extern "kernel32" fn PeekNamedPipe(
hNamedPipe: windows.HANDLE,
lpBuffer: ?windows.LPVOID,
@@ -85,7 +85,7 @@ pub const exp = struct {
lpBytesRead: ?*windows.DWORD,
lpTotalBytesAvail: ?*windows.DWORD,
lpBytesLeftThisMessage: ?*windows.DWORD,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
// Duplicated here because lpCommandLine is not marked optional in zig std
pub extern "kernel32" fn CreateProcessW(
lpApplicationName: ?windows.LPWSTR,
@@ -98,7 +98,12 @@ pub const exp = struct {
lpCurrentDirectory: ?windows.LPWSTR,
lpStartupInfo: *windows.STARTUPINFOW,
lpProcessInformation: *windows.PROCESS_INFORMATION,
) callconv(windows.WINAPI) windows.BOOL;
) callconv(.winapi) windows.BOOL;
/// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcomputernamea
pub extern "kernel32" fn GetComputerNameA(
lpBuffer: windows.LPSTR,
nSize: *windows.DWORD,
) callconv(.winapi) windows.BOOL;
};
pub const PROC_THREAD_ATTRIBUTE_NUMBER = 0x0000FFFF;