authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-20 18:53:30-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-21 13:08:49-07:00
logecd95943a9a174284a5e90f208df4d508aafd098
tree7503a42ff93edc6bd74c805ef236b5e43f06164a
parent4f32c51b9de49eced084ebd88ef04f87d6e100f0

link frontend: different semantics for colon prefixed -l args


2 files changed, 50 insertions(+), 39 deletions(-)

src/link.zig+32-29
......@@ -995,11 +995,7 @@ pub const File = struct {
995995 .{},
996996 ) catch |err| {
997997 const diags = &base.comp.link_diags;
998 return diags.fail("failed to copy '{f}' to '{f}': {t}", .{
999 std.fmt.alt(@as(Path, cached_pp_file_path), .formatEscapeChar),
1000 std.fmt.alt(@as(Path, emit), .formatEscapeChar),
1001 err,
1002 });
998 return diags.fail("failed to copy {qf} to {qf}: {t}", .{ cached_pp_file_path, emit, err });
1003999 };
10041000 return;
10051001 }
......@@ -1740,7 +1736,7 @@ pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void
17401736 if (comp.bin_file) |lf| {
17411737 lf.updateLineNumber(pt, line_update.inst, line_update.line) catch |err| switch (err) {
17421738 error.OutOfMemory => diags.setAllocFailure(),
1743 else => |e| log.err("update line number failed: {s}", .{@errorName(e)}),
1739 else => |e| log.err("update line number failed: {t}", .{e}),
17441740 };
17451741 }
17461742 }
......@@ -1753,7 +1749,7 @@ pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void
17531749 if (comp.bin_file) |lf| {
17541750 lf.lostTracking(pt, ti) catch |err| switch (err) {
17551751 error.OutOfMemory => diags.setAllocFailure(),
1756 else => |e| log.err("lost tracking failed: {s}", .{@errorName(e)}),
1752 else => |e| log.err("lost tracking failed: {t}", .{e}),
17571753 };
17581754 }
17591755 }
......@@ -1845,6 +1841,14 @@ pub const UnresolvedInput = union(enum) {
18451841 must_link: bool = false,
18461842 hidden: bool = false,
18471843 allow_so_scripts: bool = false,
1844 // Corresponds to GNU ld `-l :path/to/filename`:
1845 // * No extra rpaths.
1846 // * NEEDED entry should be exactly the string
1847 // after the colon. No file system paths prepended.
1848 // * The DSO still must be found at compile/link
1849 // time and its entries used to resolve symbols.
1850 // CGo compilation depends on this.
1851 name_done: bool = false,
18481852 preferred_mode: std.lang.LinkMode,
18491853 search_strategy: SearchStrategy,
18501854
......@@ -2201,8 +2205,8 @@ pub fn resolveInputs(
22012205 if (failed_libs.items.len > 0) {
22022206 for (failed_libs.items) |f| {
22032207 const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths;
2204 std.log.err("unable to find {s} system library '{s}' using strategy '{s}'. searched paths:{s}", .{
2205 @tagName(f.preferred_mode), f.name, @tagName(f.strategy), searched_paths,
2208 std.log.err("unable to find {t} system library {q} using strategy {t}. searched paths:{s}", .{
2209 f.preferred_mode, f.name, f.strategy, searched_paths,
22062210 });
22072211 }
22082212 std.process.exit(1);
......@@ -2237,7 +2241,7 @@ fn resolveLibInput(
22372241
22382242 const lib_name = name_query.name;
22392243
2240 if (target.os.tag.isDarwin() and link_mode == .dynamic) tbd: {
2244 if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.query.name_done) tbd: {
22412245 // Prefer .tbd over .dylib.
22422246 const test_path: Path = .{
22432247 .root_dir = lib_directory,
......@@ -2246,7 +2250,7 @@ fn resolveLibInput(
22462250 try checked_paths.print(gpa, "\n {f}", .{test_path});
22472251 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
22482252 error.FileNotFound => break :tbd,
2249 else => |e| fatal("unable to search for tbd library '{f}': {s}", .{ test_path, @errorName(e) }),
2253 else => |e| fatal("searching for tbd library {qf}: {t}", .{ test_path, e }),
22502254 };
22512255 errdefer file.close(io);
22522256 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);
......@@ -2255,8 +2259,9 @@ fn resolveLibInput(
22552259 {
22562260 const test_path: Path = .{
22572261 .root_dir = lib_directory,
2258 .sub_path = try std.fmt.allocPrint(arena, "{s}{s}{s}", .{
2259 target.libPrefix(), lib_name,
2262 .sub_path = if (name_query.query.name_done) lib_name else try std.fmt.allocPrint(arena, "{s}{s}{s}", .{
2263 target.libPrefix(),
2264 lib_name,
22602265 switch (link_mode) {
22612266 .static => target.staticLibSuffix(),
22622267 .dynamic => target.dynamicLibSuffix(),
......@@ -2275,7 +2280,7 @@ fn resolveLibInput(
22752280
22762281 // In the case of Darwin, the main check will be .dylib, so here we
22772282 // additionally check for .so files.
2278 if (target.os.tag.isDarwin() and link_mode == .dynamic) so: {
2283 if (target.os.tag.isDarwin() and link_mode == .dynamic and !name_query.query.name_done) so: {
22792284 const test_path: Path = .{
22802285 .root_dir = lib_directory,
22812286 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),
......@@ -2283,9 +2288,7 @@ fn resolveLibInput(
22832288 try checked_paths.print(gpa, "\n {f}", .{test_path});
22842289 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
22852290 error.FileNotFound => break :so,
2286 else => |e| fatal("unable to search for so library '{f}': {s}", .{
2287 test_path, @errorName(e),
2288 }),
2291 else => |e| fatal("unable to search for so library {qf}: {t}", .{ test_path, e }),
22892292 };
22902293 errdefer file.close(io);
22912294 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);
......@@ -2293,7 +2296,7 @@ fn resolveLibInput(
22932296
22942297 // In the case of MinGW, the main check will be .lib but we also need to
22952298 // look for `libfoo.a`.
2296 if (target.isMinGW() and link_mode == .static) mingw: {
2299 if (target.isMinGW() and link_mode == .static and !name_query.query.name_done) mingw: {
22972300 const test_path: Path = .{
22982301 .root_dir = lib_directory,
22992302 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),
......@@ -2301,7 +2304,7 @@ fn resolveLibInput(
23012304 try checked_paths.print(gpa, "\n {f}", .{test_path});
23022305 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
23032306 error.FileNotFound => break :mingw,
2304 else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }),
2307 else => |e| fatal("unable to search for static library {qf}: {t}", .{ test_path, e }),
23052308 };
23062309 errdefer file.close(io);
23072310 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);
......@@ -2310,12 +2313,14 @@ fn resolveLibInput(
23102313 // In the case of OpenBSD, dynamic libraries are always versioned, without
23112314 // unversioned symlinks. OpenBSD patches LLD to select the highest-versioned
23122315 // shared library, and this code is intended to match that upstream behavior.
2313 if (target.isOpenBSDLibC() and link_mode == .dynamic) versioned: {
2316 if (target.isOpenBSDLibC() and link_mode == .dynamic and !name_query.query.name_done) versioned: {
23142317 const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name});
23152318
23162319 var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) {
23172320 error.NotDir, error.FileNotFound => break :versioned,
2318 else => |e| fatal("unable to search for shared library '{s}.*': {s}", .{ prefix, @errorName(e) }),
2321 else => |e| fatal("unable to search for shared library \"{f}.*\": {t}", .{
2322 std.zig.fmtString(prefix), e,
2323 }),
23192324 };
23202325 defer dir.close(io);
23212326
......@@ -2325,7 +2330,7 @@ fn resolveLibInput(
23252330
23262331 var iter = dir.iterate();
23272332 while (iter.next(io) catch |err| {
2328 fatal("unable to scan library directory '{s}'", .{@errorName(err)});
2333 fatal("scanning library directory: {t}", .{err});
23292334 }) |entry| {
23302335 if (entry.kind != .file) continue;
23312336 if (!std.mem.startsWith(u8, entry.name, prefix)) continue;
......@@ -2445,7 +2450,7 @@ fn resolvePathInput(
24452450 .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .dynamic, color),
24462451 .object => {
24472452 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|
2448 fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) });
2453 fatal("failed to open object {f}: {t}", .{ pq.path, err });
24492454 errdefer file.close(io);
24502455 try resolved_inputs.append(gpa, .{ .object = .{
24512456 .path = pq.path,
......@@ -2457,7 +2462,7 @@ fn resolvePathInput(
24572462 },
24582463 .res => {
24592464 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|
2460 fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) });
2465 fatal("failed to open windows resource {f}: {t}", .{ pq.path, err });
24612466 errdefer file.close(io);
24622467 try resolved_inputs.append(gpa, .{ .res = .{
24632468 .path = pq.path,
......@@ -2498,14 +2503,14 @@ fn resolvePathInputLib(
24982503 }) {
24992504 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
25002505 error.FileNotFound => return .no_match,
2501 else => |e| fatal("unable to search for {t} library '{f}': {t}", .{
2506 else => |e| fatal("unable to search for {t} library {qf}: {t}", .{
25022507 link_mode, std.fmt.alt(test_path, .formatEscapeChar), e,
25032508 }),
25042509 };
25052510 errdefer file.close(io);
25062511 try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len));
25072512 const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err|
2508 fatal("failed to read '{f}': {t}", .{ std.fmt.alt(test_path, .formatEscapeChar), err });
2513 fatal("failed to read {qf}: {t}", .{ test_path, err });
25092514 const buf = ld_script_bytes.items[0..n];
25102515 if (mem.startsWith(u8, buf, std.elf.MAGIC) or
25112516 mem.startsWith(u8, buf, std.elf.ARMAG) or
......@@ -2577,9 +2582,7 @@ fn resolvePathInputLib(
25772582
25782583 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
25792584 error.FileNotFound => return .no_match,
2580 else => |e| fatal("unable to search for {s} library {f}: {s}", .{
2581 @tagName(link_mode), test_path, @errorName(e),
2582 }),
2585 else => |e| fatal("unable to search for {t} library {f}: {t}", .{ link_mode, test_path, e }),
25832586 };
25842587 errdefer file.close(io);
25852588 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query);
src/main.zig+18-10
......@@ -70,10 +70,6 @@ var stdin_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
7070/// This can be global since stdout is a singleton.
7171var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
7272
73/// Shaming all the locations that inappropriately use an O(N) search algorithm.
74/// Please delete this and fix the compilation errors!
75pub const @"bad O(N)" = void;
76
7773const normal_usage =
7874 \\Usage: zig [command] [options]
7975 \\
......@@ -2083,12 +2079,24 @@ fn buildOutputType(
20832079 // We don't know whether this library is part of libc or libc++ until
20842080 // we resolve the target, so we simply append to the list for now.
20852081 if (mem.startsWith(u8, it.only_arg, ":")) {
2086 // -l :path/to/filename is used when callers need
2087 // more control over what's in the resulting
2088 // binary: no extra rpaths and DSO filename exactly
2089 // as provided. CGo compilation depends on this.
2090 try create_module.cli_link_inputs.append(arena, .{ .dso_exact = .{
2091 .name = it.only_arg,
2082 // -l :path/to/filename indicates:
2083 // * No extra rpaths.
2084 // * NEEDED entry should be exactly the string
2085 // after the colon. No file system paths prepended.
2086 // * The DSO still must be found at compile/link
2087 // time and its entries used to resolve symbols.
2088 // CGo compilation depends on this.
2089 try create_module.cli_link_inputs.append(arena, .{ .name_query = .{
2090 .name = it.only_arg[1..],
2091 .query = .{
2092 .must_link = must_link,
2093 .needed = needed,
2094 .weak = false,
2095 .preferred_mode = lib_preferred_mode,
2096 .search_strategy = lib_search_strategy,
2097 .allow_so_scripts = allow_so_scripts,
2098 .name_done = true,
2099 },
20922100 } });
20932101 } else {
20942102 const compiler_rt_classification = target_util.classifyCompilerRtLibName(it.only_arg);