diff --git a/lib/compiler/Maker/Step.zig b/lib/compiler/Maker/Step.zig index a4760274cdeb5ab6b64651b2e79be61e1d8ebae6..5c618660ce787ce8913b9dfb597f0bf882856b7e 100644 --- a/lib/compiler/Maker/Step.zig +++ b/lib/compiler/Maker/Step.zig @@ -19,6 +19,7 @@ const WebServer = @import("WebServer.zig"); const Maker = @import("../Maker.zig"); pub const Compile = @import("Step/Compile.zig"); +pub const FindProgram = @import("Step/FindProgram.zig"); pub const Fmt = @import("Step/Fmt.zig"); pub const InstallArtifact = @import("Step/InstallArtifact.zig"); pub const InstallDir = @import("Step/InstallDir.zig"); @@ -78,7 +79,7 @@ pub const Extended = union(enum) { compile: Compile, config_header: Todo, fail: Fail, - find_program: Todo, + find_program: FindProgram, fmt: Fmt, install_artifact: InstallArtifact, install_dir: InstallDir, diff --git a/lib/compiler/Maker/Step/FindProgram.zig b/lib/compiler/Maker/Step/FindProgram.zig new file mode 100644 index 0000000000000000000000000000000000000000..c21374c90522d07257c1b4ce3439f1cbf87c7b61 --- /dev/null +++ b/lib/compiler/Maker/Step/FindProgram.zig @@ -0,0 +1,120 @@ +const FindProgram = @This(); +const builtin = @import("builtin"); + +const std = @import("std"); +const Io = std.Io; +const Configuration = std.Build.Configuration; +const assert = std.debug.assert; + +const Step = @import("../Step.zig"); +const Maker = @import("../../Maker.zig"); + +pub fn make( + find_program: *FindProgram, + step_index: Configuration.Step.Index, + maker: *Maker, + progress_node: std.Progress.Node, +) Step.ExtendedMakeError!void { + _ = find_program; + _ = progress_node; + const graph = maker.graph; + const step = maker.stepByIndex(step_index); + const arena = graph.arena; // TODO don't leak into the process arena + const conf = &maker.scanned_config.configuration; + const conf_step = step_index.ptr(conf); + const conf_fp = conf_step.extended.get(conf.extra).find_program; + const found_path = conf_fp.found_path; + const names = conf_fp.names.slice(conf); + + // In case we fail at the end. + var err_msg: std.ArrayList(u8) = .empty; + try err_msg.appendSlice(arena, "program not found. searched paths:\n"); + + for (names) |name_index| { + const name = name_index.slice(conf); + + if (Io.Dir.path.isAbsolute(name)) { + if (try checkCandidate(maker, step, found_path, &err_msg, name)) return; + + continue; + } + + for (graph.search_prefixes.items) |search_prefix| { + const full_path = try Io.Dir.path.join(arena, &.{ search_prefix, "bin", name }); + + if (try checkCandidate(maker, step, found_path, &err_msg, full_path)) return; + } + } + + if (graph.environ_map.get("PATH")) |PATH| { + for (names) |name_index| { + const name = name_index.slice(conf); + + var it = std.mem.tokenizeScalar(u8, PATH, Io.Dir.path.delimiter); + while (it.next()) |p| { + const full_path = try Io.Dir.path.join(arena, &.{ p, name }); + + if (try checkCandidate(maker, step, found_path, &err_msg, full_path)) return; + } + } + } + + assert(err_msg.items[err_msg.items.len - 1] == '\n'); + const chopped = err_msg.items[0 .. err_msg.items.len - 1]; + try step.result_error_msgs.append(arena, chopped); + return error.MakeFailed; +} + +fn checkCandidate( + maker: *Maker, + step: *Step, + found_path: Configuration.GeneratedFileIndex, + err_msg: *std.ArrayList(u8), + full_path: []const u8, +) !bool { + const graph = maker.graph; + const arena = graph.arena; // TODO don't leak into process arena + const io = graph.io; + + if (Io.Dir.cwd().access(io, full_path, .{ .execute = true })) |_| { + maker.generatedPath(found_path).* = .initCwd(full_path); + return true; + } else |err| switch (err) { + error.Canceled => |e| return e, + error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { + try err_msg.print(arena, "{t} {s}\n", .{ e, full_path }); + }, + else => |e| return step.fail(maker, "failed accessing {s}: {t}", .{ full_path, e }), + } + + if (builtin.os.tag == .windows) { + if (graph.environ_map.get("PATHEXT")) |PATHEXT| { + var it = std.mem.tokenizeScalar(u8, PATHEXT, Io.Dir.path.delimiter); + while (it.next()) |ext| { + if (!supportedWindowsProgramExtension(ext)) continue; + + const extended_path = try std.mem.concat(arena, &.{ full_path, ext }); + + if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| { + maker.generatedPath(found_path).* = .initCwd(extended_path); + return true; + } else |err| switch (err) { + error.Canceled => |e| return e, + error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { + try err_msg.print(arena, "{s} {t}\n", .{ extended_path, e }); + }, + else => |e| return step.fail(maker, "failed accessing {s}: {t}", .{ extended_path, e }), + } + } + } + } + + return false; +} + +fn supportedWindowsProgramExtension(ext: []const u8) bool { + inline for (@typeInfo(std.process.WindowsExtension).@"enum".fields) |field| { + if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true; + } + return false; +} diff --git a/lib/compiler/configurer.zig b/lib/compiler/configurer.zig index c8aa77edd8d79c61533ec00e6ac0cd5248cbc86f..2e5b26c4b2fc87ec43473238d61fe893f71765e1 100644 --- a/lib/compiler/configurer.zig +++ b/lib/compiler/configurer.zig @@ -906,7 +906,13 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { .msg = sf.error_msg, }); }, - .find_program => @panic("TODO"), + .find_program => e: { + const fp: *Step.FindProgram = @fieldParentPtr("step", step); + break :e try wc.addExtraErased(Configuration.Step.FindProgram, .{ + .names = fp.names, + .found_path = fp.found_path, + }); + }, .fmt => e: { const sf: *Step.Fmt = @fieldParentPtr("step", step); break :e try wc.addExtraErased(Configuration.Step.Fmt, .{ diff --git a/lib/std/Build.zig b/lib/std/Build.zig index fbf2fb0e5047b7c598b86635fdf9e139ce96fc35..abaedb24daecee2b49a9c39db2f17d06860ee3b1 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1719,14 +1719,15 @@ pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 { /// globally installed and will therefore be possibly found in one of the /// search prefix paths. /// +/// Windows file name extensions are searched automatically, respecting the +/// PATHEXT environment variable, so they need not be included in this list. +/// However, even on Windows, the names will be checked without appending +/// extensions first, so that can be used as a priority system. +/// /// See also: /// * `findProgram` -pub fn findProgramLazy(b: *Build, names: []const []const u8) LazyPath { - const graph = b.graph; - const wc = &graph.wip_configuration; - const string_list = wc.addStringList(names) catch @panic("OOM"); - _ = string_list; - @panic("TODO"); +pub fn findProgramLazy(b: *Build, options: Step.FindProgram.Options) LazyPath { + return .{ .generated = .{ .index = Step.FindProgram.create(b, options).found_path } }; } /// Immediately (in the configure phase), searches for an executable on the host diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index 02a47a543575deb5fdef442c06b2a52a2eaccd16..63bc3027386868ecf5def1d62f32448f19402367 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -1192,9 +1192,9 @@ pub const Step = extern struct { }; pub const FindProgram = struct { - flags: @This().Flags, + flags: @This().Flags = .{}, names: StringList, - generated_file: GeneratedFileIndex, + found_path: GeneratedFileIndex, pub const Flags = packed struct(u32) { tag: Tag = .find_program, diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index e115732d0346774d3ea059129a404d29d9375172..4332737c16ed6e7250cde394b15b285c938ac542 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -78,19 +78,20 @@ pub fn Type(comptime tag: Tag) type { } pub const CheckFile = @import("Step/CheckFile.zig"); +pub const Compile = @import("Step/Compile.zig"); pub const ConfigHeader = @import("Step/ConfigHeader.zig"); pub const Fail = @import("Step/Fail.zig"); +pub const FindProgram = @import("Step/FindProgram.zig"); pub const Fmt = @import("Step/Fmt.zig"); pub const InstallArtifact = @import("Step/InstallArtifact.zig"); pub const InstallDir = @import("Step/InstallDir.zig"); pub const InstallFile = @import("Step/InstallFile.zig"); pub const ObjCopy = @import("Step/ObjCopy.zig"); -pub const Compile = @import("Step/Compile.zig"); pub const Options = @import("Step/Options.zig"); pub const Run = @import("Step/Run.zig"); pub const TranslateC = @import("Step/TranslateC.zig"); -pub const WriteFile = @import("Step/WriteFile.zig"); pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig"); +pub const WriteFile = @import("Step/WriteFile.zig"); pub const TopLevel = struct { pub const base_tag: Step.Tag = .top_level; diff --git a/lib/std/Build/Step/FindProgram.zig b/lib/std/Build/Step/FindProgram.zig new file mode 100644 index 0000000000000000000000000000000000000000..079f1037dccb68efdb38897166c831954a97834a --- /dev/null +++ b/lib/std/Build/Step/FindProgram.zig @@ -0,0 +1,31 @@ +const FindProgram = @This(); + +const std = @import("std"); +const Step = std.Build.Step; +const Configuration = std.Build.Configuration; + +step: Step, +found_path: Configuration.GeneratedFileIndex, +names: Configuration.StringList, + +pub const base_tag: Step.Tag = .find_program; + +pub const Options = struct { + names: []const []const u8, +}; + +pub fn create(owner: *std.Build, options: Options) *FindProgram { + const graph = owner.graph; + const wc = &graph.wip_configuration; + const fp = graph.create(FindProgram); + fp.* = .{ + .step = .init(.{ + .tag = base_tag, + .name = owner.fmt("find program {s} ({d} candidates)", .{ options.names[0], options.names.len }), + .owner = owner, + }), + .found_path = graph.addGeneratedFile(&fp.step), + .names = wc.addStringList(options.names) catch @panic("OOM"), + }; + return fp; +} diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index de003734cf58d31e333ed76fb46b782e9835928a..eda18e2579525f5299fda9a357bb89edb67adbd5 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -409,7 +409,10 @@ pub const PathNameError = error{ }; pub const AccessError = error{ + /// The requested `AccessOptions` would be denied to the file, or search + /// permission is denied for one of the directories in the path prefix. AccessDenied, + /// Write permission was requested but the file is immutable. PermissionDenied, FileNotFound, InputOutput,