zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 1edc5d7d67f084941c2162dc71bd8a417189f265 (tree)
parent 54bb8d2dd9369f5e5b43b4773878edc32fd3851e
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Fri, 22 May 2026 17:51:19 -0700

Maker: implement FindProgram (lazy)

Diffstat:
Mlib/compiler/Maker/Step.zig | 3++-
Alib/compiler/Maker/Step/FindProgram.zig | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/compiler/configurer.zig | 8+++++++-
Mlib/std/Build.zig | 13+++++++------
Mlib/std/Build/Configuration.zig | 4++--
Mlib/std/Build/Step.zig | 5+++--
Alib/std/Build/Step/FindProgram.zig | 31+++++++++++++++++++++++++++++++
Mlib/std/Io/Dir.zig | 3+++
8 files changed, 175 insertions(+), 12 deletions(-)

diff --git 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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,