zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

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:
Mci/x86_64-linux-debug-llvm.sh | 5++++-
Mci/x86_64-linux-debug.sh | 5++++-
Mci/x86_64-linux-release.sh | 10++++++++--
Mlib/std/Build/Cache.zig | 2+-
Msrc/Compilation.zig | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.zig | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
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,