diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index d73fb6d29dc6..0660068250b7 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -176,6 +176,13 @@ pub fn main() !void { } else if (mem.eql(u8, arg, "--search-prefix")) { const search_prefix = nextArgOrFatal(args, &arg_idx); builder.addSearchPrefix(search_prefix); + } else if (mem.eql(u8, arg, "--search-paths")) { + const search_paths_spec = nextArgOrFatal(args, &arg_idx); + + const search_paths = parseZonFile(std.Build.SearchMethod.Paths, builder, search_paths_spec) catch |err| + return fatal("Unable to parse ZON file '{s}': {s}", .{ search_paths_spec, @errorName(err) }); + + builder.addSearchMethod(.{ .paths = search_paths }); } else if (mem.eql(u8, arg, "--libc")) { builder.libc_file = nextArgOrFatal(args, &arg_idx); } else if (mem.eql(u8, arg, "--color")) { @@ -1343,6 +1350,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void { \\ --search-prefix [path] Add a path to look for binaries, libraries, headers \\ --sysroot [path] Set the system root directory (usually /) \\ --libc [file] Provide a file which specifies libc paths + \\ --search-paths [file] Provide a file which specifies search paths \\ \\ --system [pkgdir] Disable package fetching; enable all integrations \\ -fsys=[name] Enable a system integration @@ -1544,3 +1552,55 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { }; } } + +/// Parse ZON file where all fields are paths relative to +/// current working directory. +fn parseZonFile(comptime T: type, b: *std.Build, path: []const u8) !T { + const spec_file = try std.fs.cwd().openFile(path, .{}); + defer spec_file.close(); + + const gpa = b.allocator; + + const spec = try std.zig.readSourceFileToEndAlloc(gpa, spec_file, null); + defer gpa.free(spec); + + var ast: std.zig.Ast = try .parse(gpa, spec, .zon); + defer ast.deinit(gpa); + + const zoir = try std.zig.ZonGen.generate(gpa, ast, .{}); + defer zoir.deinit(gpa); + + if (zoir.hasCompileErrors()) { + std.log.err("Can't parse ZON file '{s}: {d} errors", .{ path, zoir.compile_errors.len }); + for (zoir.compile_errors, 1..) |compile_error, i| { + std.log.err("[{d}] {s}", .{ i, compile_error.msg.get(zoir) }); + for (compile_error.getNotes(zoir)) |note| { + std.log.err("note: {s}", .{note.msg.get(zoir)}); + } + } + return process.exit(1); + } + + var result: T = .{}; + const root_struct = switch (std.zig.Zoir.Node.Index.root.get(zoir)) { + .struct_literal => |struct_literal| struct_literal, + .empty_literal => return result, + else => return fatal("Can't parse ZON file '{s}': not a struct", .{path}), + }; + + for (root_struct.names, 0..) |name_i, val_i| { + const name = name_i.get(zoir); + const val = root_struct.vals.at(@intCast(val_i)).get(zoir); + + inline for (@typeInfo(T).@"struct".fields) |field| { + if (std.mem.eql(u8, name, field.name)) { + const string = switch (val) { + .string_literal => |string_literal| string_literal, + else => return fatal("Can't parse field '{s}' in ZON file '{s}': not a string", .{ field.name, path }), + }; + @field(result, field.name) = .{ .cwd_relative = b.dupePath(string) }; + } + } + } + return result; +} diff --git a/lib/std/Build.zig b/lib/std/Build.zig index ab064d1aea2b..bace66d10ee1 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -50,7 +50,7 @@ exe_dir: []const u8, h_dir: []const u8, install_path: []const u8, sysroot: ?[]const u8 = null, -search_prefixes: std.ArrayListUnmanaged([]const u8), +search_methods: std.ArrayListUnmanaged(SearchMethod), libc_file: ?[]const u8 = null, /// Path to the directory containing build.zig. build_root: Cache.Directory, @@ -277,7 +277,7 @@ pub fn create( .available_options_list = ArrayList(AvailableOption).init(arena), .top_level_steps = .{}, .default_step = undefined, - .search_prefixes = .{}, + .search_methods = .{}, .install_prefix = undefined, .lib_dir = undefined, .exe_dir = undefined, @@ -380,7 +380,7 @@ fn createChildOnly( .h_dir = parent.h_dir, .install_path = parent.install_path, .sysroot = parent.sysroot, - .search_prefixes = parent.search_prefixes, + .search_methods = parent.search_methods, .libc_file = parent.libc_file, .build_root = build_root, .cache_root = parent.cache_root, @@ -2008,12 +2008,15 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const u8) error{FileNotFound}![]const u8 { // TODO report error for ambiguous situations - for (b.search_prefixes.items) |search_prefix| { + for (b.search_methods.items) |search| { + const binaries_lazy_path = search.binaries(b) orelse continue; + const binaries_path = binaries_lazy_path.getPath3(b, null); for (names) |name| { if (fs.path.isAbsolute(name)) { return name; } - return tryFindProgram(b, b.pathJoin(&.{ search_prefix, "bin", name })) orelse continue; + const binary_path = binaries_path.joinString(b.allocator, name) catch @panic("OOM"); + return tryFindProgram(b, binary_path) orelse continue; } } if (b.graph.env_map.get("PATH")) |PATH| { @@ -2101,8 +2104,49 @@ pub fn run(b: *Build, argv: []const []const u8) []u8 { }; } +pub const SearchMethod = union(enum) { + /// Try to find `bin`, `lib`, and `include` sub-dirs. + prefix: LazyPath, + /// Use as written, without guessing sub-dirs. + /// If some path is not passed, it will be skipped. + paths: Paths, + + pub const Paths = struct { + binaries: ?LazyPath = null, + libraries: ?LazyPath = null, + includes: ?LazyPath = null, + }; + + pub fn binaries(self: SearchMethod, b: *std.Build) ?LazyPath { + return switch (self) { + .prefix => |prefix| prefix.path(b, "bin"), + .paths => |paths| paths.binaries, + }; + } + + pub fn libraries(self: SearchMethod, b: *std.Build) ?LazyPath { + return switch (self) { + .prefix => |prefix| prefix.path(b, "lib"), + .paths => |paths| paths.libraries, + }; + } + + pub fn includes(self: SearchMethod, b: *std.Build) ?LazyPath { + return switch (self) { + .prefix => |prefix| prefix.path(b, "include"), + .paths => |paths| paths.includes, + }; + } +}; + pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void { - b.search_prefixes.append(b.allocator, b.dupePath(search_prefix)) catch @panic("OOM"); + b.addSearchMethod(.{ + .prefix = .{ .cwd_relative = b.dupePath(search_prefix) }, + }); +} + +pub fn addSearchMethod(b: *Build, search_method: SearchMethod) void { + b.search_methods.append(b.allocator, search_method) catch @panic("OOM"); } pub fn getInstallPath(b: *Build, dir: InstallDir, dest_rel_path: []const u8) []const u8 { diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 9352280d9641..b1224d164434 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1652,38 +1652,38 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { } // -I and -L arguments that appear after the last --mod argument apply to all modules. - for (b.search_prefixes.items) |search_prefix| { - var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| { - return step.fail("unable to open prefix directory '{s}': {s}", .{ - search_prefix, @errorName(err), - }); - }; - defer prefix_dir.close(); + for (b.search_methods.items) |search| { // Avoid passing -L and -I flags for nonexistent directories. // This prevents a warning, that should probably be upgraded to an error in Zig's // CLI parsing code, when the linker sees an -L directory that does not exist. - if (prefix_dir.accessZ("lib", .{})) |_| { - try zig_args.appendSlice(&.{ - "-L", b.pathJoin(&.{ search_prefix, "lib" }), - }); - } else |err| switch (err) { - error.FileNotFound => {}, - else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{ - search_prefix, @errorName(e), - }), + if (search.libraries(b)) |libraries_lazy_path| { + const libraries_path = libraries_lazy_path.getPath3(b, step); + if (libraries_path.access(".", .{})) { + try zig_args.appendSlice(&.{ + "-L", libraries_path.toString(b.allocator) catch @panic("OOM"), + }); + } else |err| switch (err) { + error.FileNotFound => {}, + else => return step.fail("unable to access '{}' directory: {s}", .{ + libraries_path, @errorName(err), + }), + } } - if (prefix_dir.accessZ("include", .{})) |_| { - try zig_args.appendSlice(&.{ - "-I", b.pathJoin(&.{ search_prefix, "include" }), - }); - } else |err| switch (err) { - error.FileNotFound => {}, - else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{ - search_prefix, @errorName(e), - }), + if (search.includes(b)) |includes_lazy_path| { + const includes_path = includes_lazy_path.getPath3(b, step); + if (includes_path.access(".", .{})) { + try zig_args.appendSlice(&.{ + "-I", includes_path.toString(b.allocator) catch @panic("OOM"), + }); + } else |err| switch (err) { + error.FileNotFound => {}, + else => return step.fail("unable to access '{}' directory: {s}", .{ + includes_path, @errorName(err), + }), + } } }