1const FindProgram = @This();
2const builtin = @import("builtin");
3
4const std = @import("std");
5const Io = std.Io;
6const Configuration = std.Build.Configuration;
7const assert = std.debug.assert;
8
9const Step = @import("../Step.zig");
10const Maker = @import("../../Maker.zig");
11
12pub fn make(
13 find_program: *FindProgram,
14 step_index: Configuration.Step.Index,
15 maker: *Maker,
16 progress_node: std.Progress.Node,
17) Step.ExtendedMakeError!void {
18 _ = find_program;
19 _ = progress_node;
20 const graph = maker.graph;
21 const step = maker.stepByIndex(step_index);
22 const arena = graph.arena; // TODO don't leak into the process arena
23 const conf = &maker.scanned_config.configuration;
24 const conf_step = step_index.ptr(conf);
25 const conf_fp = conf_step.extended.get(conf.extra).find_program;
26 const found_path = conf_fp.found_path;
27 const names = conf_fp.names.slice(conf);
28
29 // In case we fail at the end.
30 var err_msg: std.ArrayList(u8) = .empty;
31 try err_msg.appendSlice(arena, "program not found. searched paths:\n");
32
33 for (names) |name_index| {
34 const name = name_index.slice(conf);
35
36 if (Io.Dir.path.isAbsolute(name)) {
37 if (try checkCandidate(maker, step, found_path, &err_msg, name)) return;
38
39 continue;
40 }
41
42 for (graph.search_prefixes.items) |search_prefix| {
43 const full_path = try Io.Dir.path.join(arena, &.{ search_prefix, "bin", name });
44
45 if (try checkCandidate(maker, step, found_path, &err_msg, full_path)) return;
46 }
47 }
48
49 if (graph.environ_map.get("PATH")) |PATH| {
50 for (names) |name_index| {
51 const name = name_index.slice(conf);
52
53 var it = std.mem.tokenizeScalar(u8, PATH, Io.Dir.path.delimiter);
54 while (it.next()) |p| {
55 const full_path = try Io.Dir.path.join(arena, &.{ p, name });
56
57 if (try checkCandidate(maker, step, found_path, &err_msg, full_path)) return;
58 }
59 }
60 }
61
62 assert(err_msg.items[err_msg.items.len - 1] == '\n');
63 const chopped = err_msg.items[0 .. err_msg.items.len - 1];
64 try step.result_error_msgs.append(arena, chopped);
65 return error.MakeFailed;
66}
67
68fn checkCandidate(
69 maker: *Maker,
70 step: *Step,
71 found_path: Configuration.GeneratedFileIndex,
72 err_msg: *std.ArrayList(u8),
73 full_path: []const u8,
74) !bool {
75 const graph = maker.graph;
76 const arena = graph.arena; // TODO don't leak into process arena
77 const io = graph.io;
78
79 if (Io.Dir.cwd().access(io, full_path, .{ .execute = true })) |_| {
80 maker.generatedPath(found_path).* = .initCwd(full_path);
81 return true;
82 } else |err| switch (err) {
83 error.Canceled => |e| return e,
84 error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| {
85 try err_msg.print(arena, "{t} {s}\n", .{ e, full_path });
86 },
87 else => |e| return step.fail(maker, "failed accessing {s}: {t}", .{ full_path, e }),
88 }
89
90 if (builtin.os.tag == .windows) {
91 if (graph.environ_map.get("PATHEXT")) |PATHEXT| {
92 var it = std.mem.tokenizeScalar(u8, PATHEXT, Io.Dir.path.delimiter);
93 while (it.next()) |ext| {
94 if (!supportedWindowsProgramExtension(ext)) continue;
95
96 const extended_path = try std.mem.concat(arena, u8, &.{ full_path, ext });
97
98 if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| {
99 maker.generatedPath(found_path).* = .initCwd(extended_path);
100 return true;
101 } else |err| switch (err) {
102 error.Canceled => |e| return e,
103 error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| {
104 try err_msg.print(arena, "{t} {s}\n", .{ e, extended_path });
105 },
106 else => |e| return step.fail(maker, "failed accessing {s}: {t}", .{ extended_path, e }),
107 }
108 }
109 }
110 }
111
112 return false;
113}
114
115fn supportedWindowsProgramExtension(ext: []const u8) bool {
116 inline for (@typeInfo(std.process.WindowsExtension).@"enum".field_names) |field_name| {
117 if (std.ascii.eqlIgnoreCase(ext, "." ++ field_name)) return true;
118 }
119 return false;
120}