authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-12-06 08:32:25+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-12-06 08:32:25+01:00
logd41c16930df6fad85f2a63fec2e8e02b128cb1ed
treec677266726928e512a2fd782d27f92d92d737100
parent1e616096d42fc793f463acd44b81489987a69934
parentd22231c039ce8f087b2499a97eb769fb800f2000

Merge pull request 'link: support `--dependency-file` linker option' (#30073) from alexrp/zig:elf-depfile into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30073 Reviewed-by: mlugg <mlugg@noreply.codeberg.org>

6 files changed, 189 insertions(+), 14 deletions(-)

ci/x86_64-linux-debug-llvm.sh+4-1
...@@ -34,7 +34,10 @@ cmake .. \...@@ -34,7 +34,10 @@ cmake .. \
34 -DZIG_STATIC=ON \34 -DZIG_STATIC=ON \
35 -DZIG_NO_LIB=ON \35 -DZIG_NO_LIB=ON \
36 -DZIG_EXTRA_BUILD_ARGS="-Duse-llvm=true" \36 -DZIG_EXTRA_BUILD_ARGS="-Duse-llvm=true" \
37 -GNinja37 -GNinja \
38 -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \
39 -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE
40# https://github.com/ziglang/zig/issues/22213
3841
39# Now cmake will use zig as the C/C++ compiler. We reset the environment variables42# Now cmake will use zig as the C/C++ compiler. We reset the environment variables
40# so that installation and testing do not get affected by them.43# so that installation and testing do not get affected by them.
ci/x86_64-linux-debug.sh+4-1
...@@ -33,7 +33,10 @@ cmake .. \...@@ -33,7 +33,10 @@ cmake .. \
33 -DZIG_TARGET_MCPU="$MCPU" \33 -DZIG_TARGET_MCPU="$MCPU" \
34 -DZIG_STATIC=ON \34 -DZIG_STATIC=ON \
35 -DZIG_NO_LIB=ON \35 -DZIG_NO_LIB=ON \
36 -GNinja36 -GNinja \
37 -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \
38 -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE
39# https://github.com/ziglang/zig/issues/22213
3740
38# Now cmake will use zig as the C/C++ compiler. We reset the environment variables41# Now cmake will use zig as the C/C++ compiler. We reset the environment variables
39# so that installation and testing do not get affected by them.42# so that installation and testing do not get affected by them.
ci/x86_64-linux-release.sh+8-2
...@@ -39,7 +39,10 @@ cmake .. \...@@ -39,7 +39,10 @@ cmake .. \
39 -DZIG_TARGET_MCPU="$MCPU" \39 -DZIG_TARGET_MCPU="$MCPU" \
40 -DZIG_STATIC=ON \40 -DZIG_STATIC=ON \
41 -DZIG_NO_LIB=ON \41 -DZIG_NO_LIB=ON \
42 -GNinja42 -GNinja \
43 -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \
44 -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE
45# https://github.com/ziglang/zig/issues/22213
4346
44# Now cmake will use zig as the C/C++ compiler. We reset the environment variables47# Now cmake will use zig as the C/C++ compiler. We reset the environment variables
45# so that installation and testing do not get affected by them.48# so that installation and testing do not get affected by them.
...@@ -97,7 +100,10 @@ cmake .. \...@@ -97,7 +100,10 @@ cmake .. \
97 -DZIG_TARGET_MCPU="$MCPU" \100 -DZIG_TARGET_MCPU="$MCPU" \
98 -DZIG_STATIC=ON \101 -DZIG_STATIC=ON \
99 -DZIG_NO_LIB=ON \102 -DZIG_NO_LIB=ON \
100 -GNinja103 -GNinja \
104 -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \
105 -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE
106# https://github.com/ziglang/zig/issues/22213
101107
102unset CC108unset CC
103unset CXX109unset CXX
lib/std/Build/Cache.zig+1-1
...@@ -123,7 +123,7 @@ pub const HexDigest = [hex_digest_len]u8;...@@ -123,7 +123,7 @@ pub const HexDigest = [hex_digest_len]u8;
123123
124/// This is currently just an arbitrary non-empty string that can't match another manifest line.124/// This is currently just an arbitrary non-empty string that can't match another manifest line.
125const manifest_header = "0";125const manifest_header = "0";
126const manifest_file_size_max = 100 * 1024 * 1024;126pub const manifest_file_size_max = 100 * 1024 * 1024;
127127
128/// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it128/// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it
129/// provides enough collision resistance for the Manifest use cases, while being one of our129/// provides enough collision resistance for the Manifest use cases, while being one of our
src/Compilation.zig+82
...@@ -173,6 +173,7 @@ verbose_llvm_bc: ?[]const u8,...@@ -173,6 +173,7 @@ verbose_llvm_bc: ?[]const u8,
173verbose_cimport: bool,173verbose_cimport: bool,
174verbose_llvm_cpu_features: bool,174verbose_llvm_cpu_features: bool,
175verbose_link: bool,175verbose_link: bool,
176link_depfile: ?[]const u8,
176disable_c_depfile: bool,177disable_c_depfile: bool,
177stack_report: bool,178stack_report: bool,
178debug_compiler_runtime_libs: bool,179debug_compiler_runtime_libs: bool,
...@@ -1403,6 +1404,7 @@ pub const MiscTask = enum {...@@ -1403,6 +1404,7 @@ pub const MiscTask = enum {
1403 compiler_rt,1404 compiler_rt,
1404 libzigc,1405 libzigc,
1405 analyze_mod,1406 analyze_mod,
1407 link_depfile,
1406 docs_copy,1408 docs_copy,
1407 docs_wasm,1409 docs_wasm,
14081410
...@@ -1732,6 +1734,7 @@ pub const CreateOptions = struct {...@@ -1732,6 +1734,7 @@ pub const CreateOptions = struct {
1732 verbose_generic_instances: bool = false,1734 verbose_generic_instances: bool = false,
1733 verbose_llvm_ir: ?[]const u8 = null,1735 verbose_llvm_ir: ?[]const u8 = null,
1734 verbose_llvm_bc: ?[]const u8 = null,1736 verbose_llvm_bc: ?[]const u8 = null,
1737 link_depfile: ?[]const u8 = null,
1735 verbose_cimport: bool = false,1738 verbose_cimport: bool = false,
1736 verbose_llvm_cpu_features: bool = false,1739 verbose_llvm_cpu_features: bool = false,
1737 debug_compiler_runtime_libs: bool = false,1740 debug_compiler_runtime_libs: bool = false,
...@@ -2247,6 +2250,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,...@@ -2247,6 +2250,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
2247 .verbose_generic_instances = options.verbose_generic_instances,2250 .verbose_generic_instances = options.verbose_generic_instances,
2248 .verbose_llvm_ir = options.verbose_llvm_ir,2251 .verbose_llvm_ir = options.verbose_llvm_ir,
2249 .verbose_llvm_bc = options.verbose_llvm_bc,2252 .verbose_llvm_bc = options.verbose_llvm_bc,
2253 .link_depfile = options.link_depfile,
2250 .verbose_cimport = options.verbose_cimport,2254 .verbose_cimport = options.verbose_cimport,
2251 .verbose_llvm_cpu_features = options.verbose_llvm_cpu_features,2255 .verbose_llvm_cpu_features = options.verbose_llvm_cpu_features,
2252 .verbose_link = options.verbose_link,2256 .verbose_link = options.verbose_link,
...@@ -3099,6 +3103,15 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE...@@ -3099,6 +3103,15 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
3099 }3103 }
3100 }3104 }
31013105
3106 if (comp.link_depfile) |depfile_path| if (comp.bin_file) |lf| {
3107 assert(comp.file_system_inputs != null);
3108 comp.createDepFile(depfile_path, lf.emit) catch |err| comp.setMiscFailure(
3109 .link_depfile,
3110 "unable to write linker dependency file: {t}",
3111 .{err},
3112 );
3113 };
3114
3102 if (anyErrors(comp)) {3115 if (anyErrors(comp)) {
3103 // Skip flushing and keep source files loaded for error reporting.3116 // Skip flushing and keep source files loaded for error reporting.
3104 return;3117 return;
...@@ -5208,6 +5221,43 @@ pub fn separateCodegenThreadOk(comp: *const Compilation) bool {...@@ -5208,6 +5221,43 @@ pub fn separateCodegenThreadOk(comp: *const Compilation) bool {
5208 return zcu.backendSupportsFeature(.separate_thread);5221 return zcu.backendSupportsFeature(.separate_thread);
5209}5222}
52105223
5224fn createDepFile(
5225 comp: *Compilation,
5226 depfile: []const u8,
5227 binfile: Cache.Path,
5228) anyerror!void {
5229 var buf: [4096]u8 = undefined;
5230 var af = try std.fs.cwd().atomicFile(depfile, .{ .write_buffer = &buf });
5231 defer af.deinit();
5232
5233 comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?;
5234
5235 try af.finish();
5236}
5237
5238fn writeDepFile(
5239 comp: *Compilation,
5240 binfile: Cache.Path,
5241 w: *std.Io.Writer,
5242) std.Io.Writer.Error!void {
5243 const prefixes = comp.cache_parent.prefixes();
5244 const fsi = comp.file_system_inputs.?.items;
5245
5246 try w.print("{f}:", .{binfile});
5247
5248 {
5249 var it = std.mem.splitScalar(u8, fsi, 0);
5250 while (it.next()) |input| try w.print(" \\\n {f}{s}", .{ prefixes[input[0] - 1], input[1..] });
5251 }
5252
5253 {
5254 var it = std.mem.splitScalar(u8, fsi, 0);
5255 while (it.next()) |input| try w.print("\n\n{f}{s}:", .{ prefixes[input[0] - 1], input[1..] });
5256 }
5257
5258 try w.writeByte('\n');
5259}
5260
5211fn workerDocsCopy(comp: *Compilation) void {5261fn workerDocsCopy(comp: *Compilation) void {
5212 docsCopyFallible(comp) catch |err| return comp.lockAndSetMiscFailure(5262 docsCopyFallible(comp) catch |err| return comp.lockAndSetMiscFailure(
5213 .docs_copy,5263 .docs_copy,
...@@ -6369,6 +6419,38 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr...@@ -6369,6 +6419,38 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
63696419
6370 if (out_dep_path) |dep_file_path| {6420 if (out_dep_path) |dep_file_path| {
6371 const dep_basename = fs.path.basename(dep_file_path);6421 const dep_basename = fs.path.basename(dep_file_path);
6422
6423 if (comp.file_system_inputs != null) {
6424 // Use the same file size limit as the cache code does for dependency files.
6425 const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(dep_basename, gpa, .limited(Cache.manifest_file_size_max));
6426 defer gpa.free(dep_file_contents);
6427
6428 var str_buf: std.ArrayList(u8) = .empty;
6429 defer str_buf.deinit(gpa);
6430
6431 var it: std.Build.Cache.DepTokenizer = .{ .bytes = dep_file_contents };
6432 while (it.next()) |token| {
6433 const input_path: Compilation.Path = switch (token) {
6434 .target, .target_must_resolve => continue,
6435 .prereq => |file_path| try .fromUnresolved(arena, comp.dirs, &.{file_path}),
6436 .prereq_must_resolve => p: {
6437 try token.resolve(gpa, &str_buf);
6438 break :p try .fromUnresolved(arena, comp.dirs, &.{str_buf.items});
6439 },
6440 else => |err| {
6441 try err.printError(gpa, &str_buf);
6442 log.err("failed parsing {s}: {s}", .{ dep_basename, str_buf.items });
6443 return error.InvalidDepFile;
6444 },
6445 };
6446
6447 // There may be concurrent calls to `appendFileSystemInput` from other C objects.
6448 comp.mutex.lock();
6449 defer comp.mutex.unlock();
6450 try comp.appendFileSystemInput(input_path);
6451 }
6452 }
6453
6372 // Add the files depended on to the cache system.6454 // Add the files depended on to the cache system.
6373 try man.addDepFilePost(zig_cache_tmp_dir, dep_basename);6455 try man.addDepFilePost(zig_cache_tmp_dir, dep_basename);
6374 switch (comp.cache_use) {6456 switch (comp.cache_use) {
src/main.zig+90-9
...@@ -814,6 +814,7 @@ fn buildOutputType(...@@ -814,6 +814,7 @@ fn buildOutputType(
814 var verbose_generic_instances = false;814 var verbose_generic_instances = false;
815 var verbose_llvm_ir: ?[]const u8 = null;815 var verbose_llvm_ir: ?[]const u8 = null;
816 var verbose_llvm_bc: ?[]const u8 = null;816 var verbose_llvm_bc: ?[]const u8 = null;
817 var link_depfile: ?[]const u8 = null;
817 var verbose_cimport = false;818 var verbose_cimport = false;
818 var verbose_llvm_cpu_features = false;819 var verbose_llvm_cpu_features = false;
819 var time_report = false;820 var time_report = false;
...@@ -2063,6 +2064,8 @@ fn buildOutputType(...@@ -2063,6 +2064,8 @@ fn buildOutputType(
2063 .wl => {2064 .wl => {
2064 var split_it = mem.splitScalar(u8, it.only_arg, ',');2065 var split_it = mem.splitScalar(u8, it.only_arg, ',');
2065 while (split_it.next()) |linker_arg| {2066 while (split_it.next()) |linker_arg| {
2067 // Unfortunately duplicated with the `for_linker` handling below.
2068
2066 // Handle nested-joined args like `-Wl,-rpath=foo`.2069 // Handle nested-joined args like `-Wl,-rpath=foo`.
2067 // Must be prefixed with 1 or 2 dashes.2070 // Must be prefixed with 1 or 2 dashes.
2068 if (linker_arg.len >= 3 and2071 if (linker_arg.len >= 3 and
...@@ -2072,6 +2075,10 @@ fn buildOutputType(...@@ -2072,6 +2075,10 @@ fn buildOutputType(
2072 if (mem.indexOfScalar(u8, linker_arg, '=')) |equals_pos| {2075 if (mem.indexOfScalar(u8, linker_arg, '=')) |equals_pos| {
2073 const key = linker_arg[0..equals_pos];2076 const key = linker_arg[0..equals_pos];
2074 const value = linker_arg[equals_pos + 1 ..];2077 const value = linker_arg[equals_pos + 1 ..];
2078
2079 // We have to handle these here because they would be ambiguous
2080 // if split and added to `linker_args`, as there are argument-less
2081 // variants of them.
2075 if (mem.eql(u8, key, "--build-id")) {2082 if (mem.eql(u8, key, "--build-id")) {
2076 build_id = std.zig.BuildId.parse(value) catch |err| {2083 build_id = std.zig.BuildId.parse(value) catch |err| {
2077 fatal("unable to parse --build-id style '{s}': {s}", .{2084 fatal("unable to parse --build-id style '{s}': {s}", .{
...@@ -2084,22 +2091,19 @@ fn buildOutputType(...@@ -2084,22 +2091,19 @@ fn buildOutputType(
2084 // is done below.2091 // is done below.
2085 continue;2092 continue;
2086 }2093 }
2094
2087 try linker_args.append(key);2095 try linker_args.append(key);
2088 try linker_args.append(value);2096 try linker_args.append(value);
2089 continue;2097 continue;
2090 }2098 }
2091 }2099 }
2092 if (mem.eql(u8, linker_arg, "--build-id")) {2100
2093 build_id = .fast;2101 // These options are handled inline because their order matters for
2094 } else if (mem.eql(u8, linker_arg, "--as-needed")) {2102 // other non-linker options.
2103 if (mem.eql(u8, linker_arg, "--as-needed")) {
2095 needed = false;2104 needed = false;
2096 } else if (mem.eql(u8, linker_arg, "--no-as-needed")) {2105 } else if (mem.eql(u8, linker_arg, "--no-as-needed")) {
2097 needed = true;2106 needed = true;
2098 } else if (mem.eql(u8, linker_arg, "-no-pie")) {
2099 create_module.opts.pie = false;
2100 } else if (mem.eql(u8, linker_arg, "--sort-common")) {
2101 // from ld.lld(1): --sort-common is ignored for GNU compatibility,
2102 // this ignores plain --sort-common
2103 } else if (mem.eql(u8, linker_arg, "--whole-archive") or2107 } else if (mem.eql(u8, linker_arg, "--whole-archive") or
2104 mem.eql(u8, linker_arg, "-whole-archive"))2108 mem.eql(u8, linker_arg, "-whole-archive"))
2105 {2109 {
...@@ -2272,7 +2276,74 @@ fn buildOutputType(...@@ -2272,7 +2276,74 @@ fn buildOutputType(
2272 disable_c_depfile = true;2276 disable_c_depfile = true;
2273 try cc_argv.append(arena, "-###");2277 try cc_argv.append(arena, "-###");
2274 },2278 },
2275 .for_linker => try linker_args.append(it.only_arg),2279 .for_linker => blk: {
2280 // Unfortunately duplicated with the `wl` handling above.
2281
2282 // Handle joined args like `--dependency-file=foo.d`.
2283 // Must be prefixed with 1 or 2 dashes.
2284 if (it.only_arg.len >= 3 and it.only_arg[0] == '-' and it.only_arg[2] != '-') {
2285 if (mem.indexOfScalar(u8, it.only_arg, '=')) |equals_pos| {
2286 const key = it.only_arg[0..equals_pos];
2287 const value = it.only_arg[equals_pos + 1 ..];
2288
2289 // We have to handle these here because they would be ambiguous
2290 // if split and added to `linker_args`, as there are argument-less
2291 // variants of them.
2292 if (mem.eql(u8, key, "--build-id")) {
2293 build_id = std.zig.BuildId.parse(value) catch |err| {
2294 fatal("unable to parse --build-id style '{s}': {s}", .{
2295 value, @errorName(err),
2296 });
2297 };
2298 continue;
2299 } else if (mem.eql(u8, key, "--sort-common")) {
2300 // this ignores --sort-common=<anything>
2301 continue;
2302 }
2303
2304 try linker_args.append(key);
2305 try linker_args.append(value);
2306 break :blk;
2307 }
2308 }
2309
2310 // These options are handled inline because their order matters for
2311 // other non-linker options.
2312 if (mem.eql(u8, it.only_arg, "--as-needed")) {
2313 needed = false;
2314 } else if (mem.eql(u8, it.only_arg, "--no-as-needed")) {
2315 needed = true;
2316 } else if (mem.eql(u8, it.only_arg, "--whole-archive") or
2317 mem.eql(u8, it.only_arg, "-whole-archive"))
2318 {
2319 must_link = true;
2320 } else if (mem.eql(u8, it.only_arg, "--no-whole-archive") or
2321 mem.eql(u8, it.only_arg, "-no-whole-archive"))
2322 {
2323 must_link = false;
2324 } else if (mem.eql(u8, it.only_arg, "-Bdynamic") or
2325 mem.eql(u8, it.only_arg, "-dy") or
2326 mem.eql(u8, it.only_arg, "-call_shared"))
2327 {
2328 lib_search_strategy = .no_fallback;
2329 lib_preferred_mode = .dynamic;
2330 } else if (mem.eql(u8, it.only_arg, "-Bstatic") or
2331 mem.eql(u8, it.only_arg, "-dn") or
2332 mem.eql(u8, it.only_arg, "-non_shared") or
2333 mem.eql(u8, it.only_arg, "-static"))
2334 {
2335 lib_search_strategy = .no_fallback;
2336 lib_preferred_mode = .static;
2337 } else if (mem.eql(u8, it.only_arg, "-search_paths_first")) {
2338 lib_search_strategy = .paths_first;
2339 lib_preferred_mode = .dynamic;
2340 } else if (mem.eql(u8, it.only_arg, "-search_dylibs_first")) {
2341 lib_search_strategy = .mode_first;
2342 lib_preferred_mode = .dynamic;
2343 } else {
2344 try linker_args.append(it.only_arg);
2345 }
2346 },
2276 .linker_input_z => {2347 .linker_input_z => {
2277 try linker_args.append("-z");2348 try linker_args.append("-z");
2278 try linker_args.append(it.only_arg);2349 try linker_args.append(it.only_arg);
...@@ -2402,6 +2473,13 @@ fn buildOutputType(...@@ -2402,6 +2473,13 @@ fn buildOutputType(
2402 }2473 }
2403 }2474 }
2404 provided_name = name[prefix..end];2475 provided_name = name[prefix..end];
2476 } else if (mem.eql(u8, arg, "--build-id")) {
2477 build_id = .fast;
2478 } else if (mem.eql(u8, arg, "-no-pie")) {
2479 create_module.opts.pie = false;
2480 } else if (mem.eql(u8, arg, "--sort-common")) {
2481 // from ld.lld(1): --sort-common is ignored for GNU compatibility,
2482 // this ignores plain --sort-common
2405 } else if (mem.eql(u8, arg, "-rpath") or mem.eql(u8, arg, "--rpath") or mem.eql(u8, arg, "-R")) {2483 } else if (mem.eql(u8, arg, "-rpath") or mem.eql(u8, arg, "--rpath") or mem.eql(u8, arg, "-R")) {
2406 try create_module.rpath_list.append(arena, linker_args_it.nextOrFatal());2484 try create_module.rpath_list.append(arena, linker_args_it.nextOrFatal());
2407 } else if (mem.eql(u8, arg, "--subsystem")) {2485 } else if (mem.eql(u8, arg, "--subsystem")) {
...@@ -2697,6 +2775,8 @@ fn buildOutputType(...@@ -2697,6 +2775,8 @@ fn buildOutputType(
2697 {2775 {
2698 emit_implib = .{ .yes = linker_args_it.nextOrFatal() };2776 emit_implib = .{ .yes = linker_args_it.nextOrFatal() };
2699 emit_implib_arg_provided = true;2777 emit_implib_arg_provided = true;
2778 } else if (mem.eql(u8, arg, "--dependency-file")) {
2779 link_depfile = linker_args_it.nextOrFatal();
2700 } else if (mem.eql(u8, arg, "-Brepro") or mem.eql(u8, arg, "/Brepro")) {2780 } else if (mem.eql(u8, arg, "-Brepro") or mem.eql(u8, arg, "/Brepro")) {
2701 linker_repro = true;2781 linker_repro = true;
2702 } else if (mem.eql(u8, arg, "-undefined")) {2782 } else if (mem.eql(u8, arg, "-undefined")) {
...@@ -3472,6 +3552,7 @@ fn buildOutputType(...@@ -3472,6 +3552,7 @@ fn buildOutputType(
3472 .verbose_generic_instances = verbose_generic_instances,3552 .verbose_generic_instances = verbose_generic_instances,
3473 .verbose_llvm_ir = verbose_llvm_ir,3553 .verbose_llvm_ir = verbose_llvm_ir,
3474 .verbose_llvm_bc = verbose_llvm_bc,3554 .verbose_llvm_bc = verbose_llvm_bc,
3555 .link_depfile = link_depfile,
3475 .verbose_cimport = verbose_cimport,3556 .verbose_cimport = verbose_cimport,
3476 .verbose_llvm_cpu_features = verbose_llvm_cpu_features,3557 .verbose_llvm_cpu_features = verbose_llvm_cpu_features,
3477 .time_report = time_report,3558 .time_report = time_report,