From c2e92deb365c593a475601f8270b02fd1d0d94fe 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. This roughly mirrors the logic in std.process.Child. 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 | 52 ++++++++++++++++++++++++++++----------- lib/std/process/Child.zig | 3 ++- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index bb3e1486e6e4..cad716faef6a 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1727,20 +1727,48 @@ pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 { return std.fmt.allocPrint(b.allocator, format, args) catch @panic("OOM"); } +// Keep this logic in sync with std.process.Child.windowsCreateProcessSupportsExtension(). +fn supportedWindowsProgramExtension(ext: [] const u8) bool { + inline for (&.{ "bat", "cmd", "com", "exe" }) |e| { + if (std.ascii.eqlIgnoreCase(ext, e)) 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 +1778,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 +1787,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..3b81ab14536d 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -1389,7 +1389,8 @@ 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` +// Should be kept in sync with `windowsCreateProcessSupportsExtension` and +// `std.Build.supportedWindowsProgramExtension()`. const CreateProcessSupportedExtension = enum { bat, cmd,