commit d41c16930df6fad85f2a63fec2e8e02b128cb1ed (tree)
parent 1e616096d42fc793f463acd44b81489987a69934
Author: Alex Rønne Petersen <alex@alexrp.com>
Date: Sat, 6 Dec 2025 08:32:25 +0100
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>
Diffstat:
6 files changed, 189 insertions(+), 14 deletions(-)
diff --git a/ci/x86_64-linux-debug-llvm.sh b/ci/x86_64-linux-debug-llvm.sh
@@ -34,7 +34,10 @@ cmake .. \
-DZIG_STATIC=ON \
-DZIG_NO_LIB=ON \
-DZIG_EXTRA_BUILD_ARGS="-Duse-llvm=true" \
- -GNinja
+ -GNinja \
+ -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \
+ -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE
+# https://github.com/ziglang/zig/issues/22213
# Now cmake will use zig as the C/C++ compiler. We reset the environment variables
# so that installation and testing do not get affected by them.
diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh
@@ -33,7 +33,10 @@ cmake .. \
-DZIG_TARGET_MCPU="$MCPU" \
-DZIG_STATIC=ON \
-DZIG_NO_LIB=ON \
- -GNinja
+ -GNinja \
+ -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \
+ -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE
+# https://github.com/ziglang/zig/issues/22213
# Now cmake will use zig as the C/C++ compiler. We reset the environment variables
# so that installation and testing do not get affected by them.
diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh
@@ -39,7 +39,10 @@ cmake .. \
-DZIG_TARGET_MCPU="$MCPU" \
-DZIG_STATIC=ON \
-DZIG_NO_LIB=ON \
- -GNinja
+ -GNinja \
+ -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \
+ -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE
+# https://github.com/ziglang/zig/issues/22213
# Now cmake will use zig as the C/C++ compiler. We reset the environment variables
# so that installation and testing do not get affected by them.
@@ -97,7 +100,10 @@ cmake .. \
-DZIG_TARGET_MCPU="$MCPU" \
-DZIG_STATIC=ON \
-DZIG_NO_LIB=ON \
- -GNinja
+ -GNinja \
+ -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \
+ -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE
+# https://github.com/ziglang/zig/issues/22213
unset CC
unset CXX
diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig
@@ -123,7 +123,7 @@ pub const HexDigest = [hex_digest_len]u8;
/// This is currently just an arbitrary non-empty string that can't match another manifest line.
const manifest_header = "0";
-const manifest_file_size_max = 100 * 1024 * 1024;
+pub const manifest_file_size_max = 100 * 1024 * 1024;
/// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it
/// provides enough collision resistance for the Manifest use cases, while being one of our
diff --git a/src/Compilation.zig b/src/Compilation.zig
@@ -173,6 +173,7 @@ verbose_llvm_bc: ?[]const u8,
verbose_cimport: bool,
verbose_llvm_cpu_features: bool,
verbose_link: bool,
+link_depfile: ?[]const u8,
disable_c_depfile: bool,
stack_report: bool,
debug_compiler_runtime_libs: bool,
@@ -1403,6 +1404,7 @@ pub const MiscTask = enum {
compiler_rt,
libzigc,
analyze_mod,
+ link_depfile,
docs_copy,
docs_wasm,
@@ -1732,6 +1734,7 @@ pub const CreateOptions = struct {
verbose_generic_instances: bool = false,
verbose_llvm_ir: ?[]const u8 = null,
verbose_llvm_bc: ?[]const u8 = null,
+ link_depfile: ?[]const u8 = null,
verbose_cimport: bool = false,
verbose_llvm_cpu_features: bool = false,
debug_compiler_runtime_libs: bool = false,
@@ -2247,6 +2250,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
.verbose_generic_instances = options.verbose_generic_instances,
.verbose_llvm_ir = options.verbose_llvm_ir,
.verbose_llvm_bc = options.verbose_llvm_bc,
+ .link_depfile = options.link_depfile,
.verbose_cimport = options.verbose_cimport,
.verbose_llvm_cpu_features = options.verbose_llvm_cpu_features,
.verbose_link = options.verbose_link,
@@ -3099,6 +3103,15 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
}
}
+ if (comp.link_depfile) |depfile_path| if (comp.bin_file) |lf| {
+ assert(comp.file_system_inputs != null);
+ comp.createDepFile(depfile_path, lf.emit) catch |err| comp.setMiscFailure(
+ .link_depfile,
+ "unable to write linker dependency file: {t}",
+ .{err},
+ );
+ };
+
if (anyErrors(comp)) {
// Skip flushing and keep source files loaded for error reporting.
return;
@@ -5208,6 +5221,43 @@ pub fn separateCodegenThreadOk(comp: *const Compilation) bool {
return zcu.backendSupportsFeature(.separate_thread);
}
+fn createDepFile(
+ comp: *Compilation,
+ depfile: []const u8,
+ binfile: Cache.Path,
+) anyerror!void {
+ var buf: [4096]u8 = undefined;
+ var af = try std.fs.cwd().atomicFile(depfile, .{ .write_buffer = &buf });
+ defer af.deinit();
+
+ comp.writeDepFile(binfile, &af.file_writer.interface) catch return af.file_writer.err.?;
+
+ try af.finish();
+}
+
+fn writeDepFile(
+ comp: *Compilation,
+ binfile: Cache.Path,
+ w: *std.Io.Writer,
+) std.Io.Writer.Error!void {
+ const prefixes = comp.cache_parent.prefixes();
+ const fsi = comp.file_system_inputs.?.items;
+
+ try w.print("{f}:", .{binfile});
+
+ {
+ var it = std.mem.splitScalar(u8, fsi, 0);
+ while (it.next()) |input| try w.print(" \\\n {f}{s}", .{ prefixes[input[0] - 1], input[1..] });
+ }
+
+ {
+ var it = std.mem.splitScalar(u8, fsi, 0);
+ while (it.next()) |input| try w.print("\n\n{f}{s}:", .{ prefixes[input[0] - 1], input[1..] });
+ }
+
+ try w.writeByte('\n');
+}
+
fn workerDocsCopy(comp: *Compilation) void {
docsCopyFallible(comp) catch |err| return comp.lockAndSetMiscFailure(
.docs_copy,
@@ -6369,6 +6419,38 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
if (out_dep_path) |dep_file_path| {
const dep_basename = fs.path.basename(dep_file_path);
+
+ if (comp.file_system_inputs != null) {
+ // Use the same file size limit as the cache code does for dependency files.
+ const dep_file_contents = try zig_cache_tmp_dir.readFileAlloc(dep_basename, gpa, .limited(Cache.manifest_file_size_max));
+ defer gpa.free(dep_file_contents);
+
+ var str_buf: std.ArrayList(u8) = .empty;
+ defer str_buf.deinit(gpa);
+
+ var it: std.Build.Cache.DepTokenizer = .{ .bytes = dep_file_contents };
+ while (it.next()) |token| {
+ const input_path: Compilation.Path = switch (token) {
+ .target, .target_must_resolve => continue,
+ .prereq => |file_path| try .fromUnresolved(arena, comp.dirs, &.{file_path}),
+ .prereq_must_resolve => p: {
+ try token.resolve(gpa, &str_buf);
+ break :p try .fromUnresolved(arena, comp.dirs, &.{str_buf.items});
+ },
+ else => |err| {
+ try err.printError(gpa, &str_buf);
+ log.err("failed parsing {s}: {s}", .{ dep_basename, str_buf.items });
+ return error.InvalidDepFile;
+ },
+ };
+
+ // There may be concurrent calls to `appendFileSystemInput` from other C objects.
+ comp.mutex.lock();
+ defer comp.mutex.unlock();
+ try comp.appendFileSystemInput(input_path);
+ }
+ }
+
// Add the files depended on to the cache system.
try man.addDepFilePost(zig_cache_tmp_dir, dep_basename);
switch (comp.cache_use) {
diff --git a/src/main.zig b/src/main.zig
@@ -814,6 +814,7 @@ fn buildOutputType(
var verbose_generic_instances = false;
var verbose_llvm_ir: ?[]const u8 = null;
var verbose_llvm_bc: ?[]const u8 = null;
+ var link_depfile: ?[]const u8 = null;
var verbose_cimport = false;
var verbose_llvm_cpu_features = false;
var time_report = false;
@@ -2063,6 +2064,8 @@ fn buildOutputType(
.wl => {
var split_it = mem.splitScalar(u8, it.only_arg, ',');
while (split_it.next()) |linker_arg| {
+ // Unfortunately duplicated with the `for_linker` handling below.
+
// Handle nested-joined args like `-Wl,-rpath=foo`.
// Must be prefixed with 1 or 2 dashes.
if (linker_arg.len >= 3 and
@@ -2072,6 +2075,10 @@ fn buildOutputType(
if (mem.indexOfScalar(u8, linker_arg, '=')) |equals_pos| {
const key = linker_arg[0..equals_pos];
const value = linker_arg[equals_pos + 1 ..];
+
+ // We have to handle these here because they would be ambiguous
+ // if split and added to `linker_args`, as there are argument-less
+ // variants of them.
if (mem.eql(u8, key, "--build-id")) {
build_id = std.zig.BuildId.parse(value) catch |err| {
fatal("unable to parse --build-id style '{s}': {s}", .{
@@ -2084,22 +2091,19 @@ fn buildOutputType(
// is done below.
continue;
}
+
try linker_args.append(key);
try linker_args.append(value);
continue;
}
}
- if (mem.eql(u8, linker_arg, "--build-id")) {
- build_id = .fast;
- } else if (mem.eql(u8, linker_arg, "--as-needed")) {
+
+ // These options are handled inline because their order matters for
+ // other non-linker options.
+ if (mem.eql(u8, linker_arg, "--as-needed")) {
needed = false;
} else if (mem.eql(u8, linker_arg, "--no-as-needed")) {
needed = true;
- } else if (mem.eql(u8, linker_arg, "-no-pie")) {
- create_module.opts.pie = false;
- } else if (mem.eql(u8, linker_arg, "--sort-common")) {
- // from ld.lld(1): --sort-common is ignored for GNU compatibility,
- // this ignores plain --sort-common
} else if (mem.eql(u8, linker_arg, "--whole-archive") or
mem.eql(u8, linker_arg, "-whole-archive"))
{
@@ -2272,7 +2276,74 @@ fn buildOutputType(
disable_c_depfile = true;
try cc_argv.append(arena, "-###");
},
- .for_linker => try linker_args.append(it.only_arg),
+ .for_linker => blk: {
+ // Unfortunately duplicated with the `wl` handling above.
+
+ // Handle joined args like `--dependency-file=foo.d`.
+ // Must be prefixed with 1 or 2 dashes.
+ if (it.only_arg.len >= 3 and it.only_arg[0] == '-' and it.only_arg[2] != '-') {
+ if (mem.indexOfScalar(u8, it.only_arg, '=')) |equals_pos| {
+ const key = it.only_arg[0..equals_pos];
+ const value = it.only_arg[equals_pos + 1 ..];
+
+ // We have to handle these here because they would be ambiguous
+ // if split and added to `linker_args`, as there are argument-less
+ // variants of them.
+ if (mem.eql(u8, key, "--build-id")) {
+ build_id = std.zig.BuildId.parse(value) catch |err| {
+ fatal("unable to parse --build-id style '{s}': {s}", .{
+ value, @errorName(err),
+ });
+ };
+ continue;
+ } else if (mem.eql(u8, key, "--sort-common")) {
+ // this ignores --sort-common=<anything>
+ continue;
+ }
+
+ try linker_args.append(key);
+ try linker_args.append(value);
+ break :blk;
+ }
+ }
+
+ // These options are handled inline because their order matters for
+ // other non-linker options.
+ if (mem.eql(u8, it.only_arg, "--as-needed")) {
+ needed = false;
+ } else if (mem.eql(u8, it.only_arg, "--no-as-needed")) {
+ needed = true;
+ } else if (mem.eql(u8, it.only_arg, "--whole-archive") or
+ mem.eql(u8, it.only_arg, "-whole-archive"))
+ {
+ must_link = true;
+ } else if (mem.eql(u8, it.only_arg, "--no-whole-archive") or
+ mem.eql(u8, it.only_arg, "-no-whole-archive"))
+ {
+ must_link = false;
+ } else if (mem.eql(u8, it.only_arg, "-Bdynamic") or
+ mem.eql(u8, it.only_arg, "-dy") or
+ mem.eql(u8, it.only_arg, "-call_shared"))
+ {
+ lib_search_strategy = .no_fallback;
+ lib_preferred_mode = .dynamic;
+ } else if (mem.eql(u8, it.only_arg, "-Bstatic") or
+ mem.eql(u8, it.only_arg, "-dn") or
+ mem.eql(u8, it.only_arg, "-non_shared") or
+ mem.eql(u8, it.only_arg, "-static"))
+ {
+ lib_search_strategy = .no_fallback;
+ lib_preferred_mode = .static;
+ } else if (mem.eql(u8, it.only_arg, "-search_paths_first")) {
+ lib_search_strategy = .paths_first;
+ lib_preferred_mode = .dynamic;
+ } else if (mem.eql(u8, it.only_arg, "-search_dylibs_first")) {
+ lib_search_strategy = .mode_first;
+ lib_preferred_mode = .dynamic;
+ } else {
+ try linker_args.append(it.only_arg);
+ }
+ },
.linker_input_z => {
try linker_args.append("-z");
try linker_args.append(it.only_arg);
@@ -2402,6 +2473,13 @@ fn buildOutputType(
}
}
provided_name = name[prefix..end];
+ } else if (mem.eql(u8, arg, "--build-id")) {
+ build_id = .fast;
+ } else if (mem.eql(u8, arg, "-no-pie")) {
+ create_module.opts.pie = false;
+ } else if (mem.eql(u8, arg, "--sort-common")) {
+ // from ld.lld(1): --sort-common is ignored for GNU compatibility,
+ // this ignores plain --sort-common
} else if (mem.eql(u8, arg, "-rpath") or mem.eql(u8, arg, "--rpath") or mem.eql(u8, arg, "-R")) {
try create_module.rpath_list.append(arena, linker_args_it.nextOrFatal());
} else if (mem.eql(u8, arg, "--subsystem")) {
@@ -2697,6 +2775,8 @@ fn buildOutputType(
{
emit_implib = .{ .yes = linker_args_it.nextOrFatal() };
emit_implib_arg_provided = true;
+ } else if (mem.eql(u8, arg, "--dependency-file")) {
+ link_depfile = linker_args_it.nextOrFatal();
} else if (mem.eql(u8, arg, "-Brepro") or mem.eql(u8, arg, "/Brepro")) {
linker_repro = true;
} else if (mem.eql(u8, arg, "-undefined")) {
@@ -3472,6 +3552,7 @@ fn buildOutputType(
.verbose_generic_instances = verbose_generic_instances,
.verbose_llvm_ir = verbose_llvm_ir,
.verbose_llvm_bc = verbose_llvm_bc,
+ .link_depfile = link_depfile,
.verbose_cimport = verbose_cimport,
.verbose_llvm_cpu_features = verbose_llvm_cpu_features,
.time_report = time_report,