From 9be9b8ca902bb7c250a6836c6090af8525464749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 18 Jun 2024 07:08:12 +0200 Subject: [PATCH] std.Build.findProgram(): Try with and without the Windows executable extensions. I renamed std.process.Child.CreateProcessSupportedExtension to WindowsExtension and made it public to avoid duplicating the list of extensions. While here, I also improved it to not misreport OOM from std.fs.realpathAlloc() as a generic failure to find the program, but instead panic like the rest of the build system does for OOM. Closes #20314. --- lib/std/Build.zig | 51 +++++++++++++++++++++++++++------------ lib/std/process/Child.zig | 11 +++++---- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index f5c83d2e7a77..942a8af406f2 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1727,20 +1727,47 @@ pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 { return std.fmt.allocPrint(b.allocator, format, args) catch @panic("OOM"); } +fn supportedWindowsProgramExtension(ext: []const u8) bool { + inline for (@typeInfo(std.process.Child.WindowsExtension).Enum.fields) |field| { + if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true; + } + return false; +} + +fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { + if (fs.realpathAlloc(b.allocator, full_path)) |p| { + return p; + } else |err| switch (err) { + error.OutOfMemory => @panic("OOM"), + else => {}, + } + + if (builtin.os.tag == .windows) { + if (b.graph.env_map.get("PATHEXT")) |PATHEXT| { + var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter); + + while (it.next()) |ext| { + if (!supportedWindowsProgramExtension(ext)) continue; + + return fs.realpathAlloc(b.allocator, b.fmt("{s}{s}", .{ full_path, ext })) catch |err| switch (err) { + error.OutOfMemory => @panic("OOM"), + else => continue, + }; + } + } + } + + return null; +} + pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const u8) ![]const u8 { // TODO report error for ambiguous situations - const exe_extension = b.graph.host.result.exeFileExt(); for (b.search_prefixes.items) |search_prefix| { for (names) |name| { if (fs.path.isAbsolute(name)) { return name; } - const full_path = b.pathJoin(&.{ - search_prefix, - "bin", - b.fmt("{s}{s}", .{ name, exe_extension }), - }); - return fs.realpathAlloc(b.allocator, full_path) catch continue; + return tryFindProgram(b, b.pathJoin(&.{ search_prefix, "bin", name })) orelse continue; } } if (b.graph.env_map.get("PATH")) |PATH| { @@ -1750,10 +1777,7 @@ pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const } var it = mem.tokenizeScalar(u8, PATH, fs.path.delimiter); while (it.next()) |p| { - const full_path = b.pathJoin(&.{ - p, b.fmt("{s}{s}", .{ name, exe_extension }), - }); - return fs.realpathAlloc(b.allocator, full_path) catch continue; + return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue; } } } @@ -1762,10 +1786,7 @@ pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const return name; } for (paths) |p| { - const full_path = b.pathJoin(&.{ - p, b.fmt("{s}{s}", .{ name, exe_extension }), - }); - return fs.realpathAlloc(b.allocator, full_path) catch continue; + return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue; } } return error.FileNotFound; diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 23cc8f59c1cf..2f8679420821 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -1103,7 +1103,7 @@ fn windowsCreateProcessPathExt( } var io_status: windows.IO_STATUS_BLOCK = undefined; - const num_supported_pathext = @typeInfo(CreateProcessSupportedExtension).Enum.fields.len; + const num_supported_pathext = @typeInfo(WindowsExtension).Enum.fields.len; var pathext_seen = [_]bool{false} ** num_supported_pathext; var any_pathext_seen = false; var unappended_exists = false; @@ -1389,8 +1389,9 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons var pipe_name_counter = std.atomic.Value(u32).init(1); -// Should be kept in sync with `windowsCreateProcessSupportsExtension` -const CreateProcessSupportedExtension = enum { +/// File name extensions supported natively by `CreateProcess()` on Windows. +// Should be kept in sync with `windowsCreateProcessSupportsExtension`. +pub const WindowsExtension = enum { bat, cmd, com, @@ -1398,7 +1399,7 @@ const CreateProcessSupportedExtension = enum { }; /// Case-insensitive WTF-16 lookup -fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupportedExtension { +fn windowsCreateProcessSupportsExtension(ext: []const u16) ?WindowsExtension { if (ext.len != 4) return null; const State = enum { start, @@ -1457,7 +1458,7 @@ fn windowsCreateProcessSupportsExtension(ext: []const u16) ?CreateProcessSupport } test windowsCreateProcessSupportsExtension { - try std.testing.expectEqual(CreateProcessSupportedExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?); + try std.testing.expectEqual(WindowsExtension.exe, windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e' }).?); try std.testing.expect(windowsCreateProcessSupportsExtension(&[_]u16{ '.', 'e', 'X', 'e', 'c' }) == null); }