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 {...@@ -995,11 +995,7 @@ pub const File = struct {
995 .{},995 .{},
996 ) catch |err| {996 ) catch |err| {
997 const diags = &base.comp.link_diags;997 const diags = &base.comp.link_diags;
998 return diags.fail("failed to copy '{f}' to '{f}': {t}", .{998 return diags.fail("failed to copy {qf} to {qf}: {t}", .{ cached_pp_file_path, emit, err });
999 std.fmt.alt(@as(Path, cached_pp_file_path), .formatEscapeChar),
1000 std.fmt.alt(@as(Path, emit), .formatEscapeChar),
1001 err,
1002 });
1003 };999 };
1004 return;1000 return;
1005 }1001 }
...@@ -1740,7 +1736,7 @@ pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void...@@ -1740,7 +1736,7 @@ pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void
1740 if (comp.bin_file) |lf| {1736 if (comp.bin_file) |lf| {
1741 lf.updateLineNumber(pt, line_update.inst, line_update.line) catch |err| switch (err) {1737 lf.updateLineNumber(pt, line_update.inst, line_update.line) catch |err| switch (err) {
1742 error.OutOfMemory => diags.setAllocFailure(),1738 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}),
1744 };1740 };
1745 }1741 }
1746 }1742 }
...@@ -1753,7 +1749,7 @@ pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void...@@ -1753,7 +1749,7 @@ pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void
1753 if (comp.bin_file) |lf| {1749 if (comp.bin_file) |lf| {
1754 lf.lostTracking(pt, ti) catch |err| switch (err) {1750 lf.lostTracking(pt, ti) catch |err| switch (err) {
1755 error.OutOfMemory => diags.setAllocFailure(),1751 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}),
1757 };1753 };
1758 }1754 }
1759 }1755 }
...@@ -1845,6 +1841,14 @@ pub const UnresolvedInput = union(enum) {...@@ -1845,6 +1841,14 @@ pub const UnresolvedInput = union(enum) {
1845 must_link: bool = false,1841 must_link: bool = false,
1846 hidden: bool = false,1842 hidden: bool = false,
1847 allow_so_scripts: bool = false,1843 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,
1848 preferred_mode: std.lang.LinkMode,1852 preferred_mode: std.lang.LinkMode,
1849 search_strategy: SearchStrategy,1853 search_strategy: SearchStrategy,
18501854
...@@ -2201,8 +2205,8 @@ pub fn resolveInputs(...@@ -2201,8 +2205,8 @@ pub fn resolveInputs(
2201 if (failed_libs.items.len > 0) {2205 if (failed_libs.items.len > 0) {
2202 for (failed_libs.items) |f| {2206 for (failed_libs.items) |f| {
2203 const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths;2207 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}", .{2208 std.log.err("unable to find {t} system library {q} using strategy {t}. searched paths:{s}", .{
2205 @tagName(f.preferred_mode), f.name, @tagName(f.strategy), searched_paths,2209 f.preferred_mode, f.name, f.strategy, searched_paths,
2206 });2210 });
2207 }2211 }
2208 std.process.exit(1);2212 std.process.exit(1);
...@@ -2237,7 +2241,7 @@ fn resolveLibInput(...@@ -2237,7 +2241,7 @@ fn resolveLibInput(
22372241
2238 const lib_name = name_query.name;2242 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: {
2241 // Prefer .tbd over .dylib.2245 // Prefer .tbd over .dylib.
2242 const test_path: Path = .{2246 const test_path: Path = .{
2243 .root_dir = lib_directory,2247 .root_dir = lib_directory,
...@@ -2246,7 +2250,7 @@ fn resolveLibInput(...@@ -2246,7 +2250,7 @@ fn resolveLibInput(
2246 try checked_paths.print(gpa, "\n {f}", .{test_path});2250 try checked_paths.print(gpa, "\n {f}", .{test_path});
2247 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {2251 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2248 error.FileNotFound => break :tbd,2252 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 }),
2250 };2254 };
2251 errdefer file.close(io);2255 errdefer file.close(io);
2252 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);2256 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);
...@@ -2255,8 +2259,9 @@ fn resolveLibInput(...@@ -2255,8 +2259,9 @@ fn resolveLibInput(
2255 {2259 {
2256 const test_path: Path = .{2260 const test_path: Path = .{
2257 .root_dir = lib_directory,2261 .root_dir = lib_directory,
2258 .sub_path = try std.fmt.allocPrint(arena, "{s}{s}{s}", .{2262 .sub_path = if (name_query.query.name_done) lib_name else try std.fmt.allocPrint(arena, "{s}{s}{s}", .{
2259 target.libPrefix(), lib_name,2263 target.libPrefix(),
2264 lib_name,
2260 switch (link_mode) {2265 switch (link_mode) {
2261 .static => target.staticLibSuffix(),2266 .static => target.staticLibSuffix(),
2262 .dynamic => target.dynamicLibSuffix(),2267 .dynamic => target.dynamicLibSuffix(),
...@@ -2275,7 +2280,7 @@ fn resolveLibInput(...@@ -2275,7 +2280,7 @@ fn resolveLibInput(
22752280
2276 // In the case of Darwin, the main check will be .dylib, so here we2281 // In the case of Darwin, the main check will be .dylib, so here we
2277 // additionally check for .so files.2282 // 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: {
2279 const test_path: Path = .{2284 const test_path: Path = .{
2280 .root_dir = lib_directory,2285 .root_dir = lib_directory,
2281 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),2286 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),
...@@ -2283,9 +2288,7 @@ fn resolveLibInput(...@@ -2283,9 +2288,7 @@ fn resolveLibInput(
2283 try checked_paths.print(gpa, "\n {f}", .{test_path});2288 try checked_paths.print(gpa, "\n {f}", .{test_path});
2284 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {2289 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2285 error.FileNotFound => break :so,2290 error.FileNotFound => break :so,
2286 else => |e| fatal("unable to search for so library '{f}': {s}", .{2291 else => |e| fatal("unable to search for so library {qf}: {t}", .{ test_path, e }),
2287 test_path, @errorName(e),
2288 }),
2289 };2292 };
2290 errdefer file.close(io);2293 errdefer file.close(io);
2291 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);2294 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);
...@@ -2293,7 +2296,7 @@ fn resolveLibInput(...@@ -2293,7 +2296,7 @@ fn resolveLibInput(
22932296
2294 // In the case of MinGW, the main check will be .lib but we also need to2297 // In the case of MinGW, the main check will be .lib but we also need to
2295 // look for `libfoo.a`.2298 // 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: {
2297 const test_path: Path = .{2300 const test_path: Path = .{
2298 .root_dir = lib_directory,2301 .root_dir = lib_directory,
2299 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),2302 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),
...@@ -2301,7 +2304,7 @@ fn resolveLibInput(...@@ -2301,7 +2304,7 @@ fn resolveLibInput(
2301 try checked_paths.print(gpa, "\n {f}", .{test_path});2304 try checked_paths.print(gpa, "\n {f}", .{test_path});
2302 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {2305 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2303 error.FileNotFound => break :mingw,2306 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 }),
2305 };2308 };
2306 errdefer file.close(io);2309 errdefer file.close(io);
2307 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);2310 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);
...@@ -2310,12 +2313,14 @@ fn resolveLibInput(...@@ -2310,12 +2313,14 @@ fn resolveLibInput(
2310 // In the case of OpenBSD, dynamic libraries are always versioned, without2313 // In the case of OpenBSD, dynamic libraries are always versioned, without
2311 // unversioned symlinks. OpenBSD patches LLD to select the highest-versioned2314 // unversioned symlinks. OpenBSD patches LLD to select the highest-versioned
2312 // shared library, and this code is intended to match that upstream behavior.2315 // 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: {
2314 const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name});2317 const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name});
23152318
2316 var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) {2319 var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) {
2317 error.NotDir, error.FileNotFound => break :versioned,2320 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 }),
2319 };2324 };
2320 defer dir.close(io);2325 defer dir.close(io);
23212326
...@@ -2325,7 +2330,7 @@ fn resolveLibInput(...@@ -2325,7 +2330,7 @@ fn resolveLibInput(
23252330
2326 var iter = dir.iterate();2331 var iter = dir.iterate();
2327 while (iter.next(io) catch |err| {2332 while (iter.next(io) catch |err| {
2328 fatal("unable to scan library directory '{s}'", .{@errorName(err)});2333 fatal("scanning library directory: {t}", .{err});
2329 }) |entry| {2334 }) |entry| {
2330 if (entry.kind != .file) continue;2335 if (entry.kind != .file) continue;
2331 if (!std.mem.startsWith(u8, entry.name, prefix)) continue;2336 if (!std.mem.startsWith(u8, entry.name, prefix)) continue;
...@@ -2445,7 +2450,7 @@ fn resolvePathInput(...@@ -2445,7 +2450,7 @@ fn resolvePathInput(
2445 .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .dynamic, color),2450 .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .dynamic, color),
2446 .object => {2451 .object => {
2447 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|2452 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 });
2449 errdefer file.close(io);2454 errdefer file.close(io);
2450 try resolved_inputs.append(gpa, .{ .object = .{2455 try resolved_inputs.append(gpa, .{ .object = .{
2451 .path = pq.path,2456 .path = pq.path,
...@@ -2457,7 +2462,7 @@ fn resolvePathInput(...@@ -2457,7 +2462,7 @@ fn resolvePathInput(
2457 },2462 },
2458 .res => {2463 .res => {
2459 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|2464 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 });
2461 errdefer file.close(io);2466 errdefer file.close(io);
2462 try resolved_inputs.append(gpa, .{ .res = .{2467 try resolved_inputs.append(gpa, .{ .res = .{
2463 .path = pq.path,2468 .path = pq.path,
...@@ -2498,14 +2503,14 @@ fn resolvePathInputLib(...@@ -2498,14 +2503,14 @@ fn resolvePathInputLib(
2498 }) {2503 }) {
2499 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {2504 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2500 error.FileNotFound => return .no_match,2505 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}", .{
2502 link_mode, std.fmt.alt(test_path, .formatEscapeChar), e,2507 link_mode, std.fmt.alt(test_path, .formatEscapeChar), e,
2503 }),2508 }),
2504 };2509 };
2505 errdefer file.close(io);2510 errdefer file.close(io);
2506 try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len));2511 try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len));
2507 const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err|2512 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 });
2509 const buf = ld_script_bytes.items[0..n];2514 const buf = ld_script_bytes.items[0..n];
2510 if (mem.startsWith(u8, buf, std.elf.MAGIC) or2515 if (mem.startsWith(u8, buf, std.elf.MAGIC) or
2511 mem.startsWith(u8, buf, std.elf.ARMAG) or2516 mem.startsWith(u8, buf, std.elf.ARMAG) or
...@@ -2577,9 +2582,7 @@ fn resolvePathInputLib(...@@ -2577,9 +2582,7 @@ fn resolvePathInputLib(
25772582
2578 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {2583 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2579 error.FileNotFound => return .no_match,2584 error.FileNotFound => return .no_match,
2580 else => |e| fatal("unable to search for {s} library {f}: {s}", .{2585 else => |e| fatal("unable to search for {t} library {f}: {t}", .{ link_mode, test_path, e }),
2581 @tagName(link_mode), test_path, @errorName(e),
2582 }),
2583 };2586 };
2584 errdefer file.close(io);2587 errdefer file.close(io);
2585 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query);2588 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;...@@ -70,10 +70,6 @@ var stdin_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
70/// This can be global since stdout is a singleton.70/// This can be global since stdout is a singleton.
71var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;71var 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
77const normal_usage =73const normal_usage =
78 \\Usage: zig [command] [options]74 \\Usage: zig [command] [options]
79 \\75 \\
...@@ -2083,12 +2079,24 @@ fn buildOutputType(...@@ -2083,12 +2079,24 @@ fn buildOutputType(
2083 // We don't know whether this library is part of libc or libc++ until2079 // We don't know whether this library is part of libc or libc++ until
2084 // we resolve the target, so we simply append to the list for now.2080 // we resolve the target, so we simply append to the list for now.
2085 if (mem.startsWith(u8, it.only_arg, ":")) {2081 if (mem.startsWith(u8, it.only_arg, ":")) {
2086 // -l :path/to/filename is used when callers need2082 // -l :path/to/filename indicates:
2087 // more control over what's in the resulting2083 // * No extra rpaths.
2088 // binary: no extra rpaths and DSO filename exactly2084 // * NEEDED entry should be exactly the string
2089 // as provided. CGo compilation depends on this.2085 // after the colon. No file system paths prepended.
2090 try create_module.cli_link_inputs.append(arena, .{ .dso_exact = .{2086 // * The DSO still must be found at compile/link
2091 .name = it.only_arg,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 },
2092 } });2100 } });
2093 } else {2101 } else {
2094 const compiler_rt_classification = target_util.classifyCompilerRtLibName(it.only_arg);2102 const compiler_rt_classification = target_util.classifyCompilerRtLibName(it.only_arg);