authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-08-15 14:37:30+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-08-16 21:56:24+01:00
log0096c0806c0f939140ef61216d967b00e571018c
tree15d7b6b8669d537e67a544610680e597893843d3
parent4fcdb08390dc1496b74a3e652498f9a0b27fada5

Compilation: retain ZCU object when emitting unstripped Mach-O binary

On macOS, when using the LLVM backend, the output binary retains a reference to this object file's debug info (as opposed to self-hosted backends which instead emit a dSYM bundle). As such, we need to retain this object file in such cases. This object does unfortunately "leak", in that it won't be reused and will just sit in the cache forever (or until GC'd in the future). But that's no worse than the cache behavior prior to the rework that caused this, and it will become less of a problem over time as the self-hosted backend gains usability for debug builds and eventually becomes the default. Resolves: #24369

1 files changed, 15 insertions(+), 3 deletions(-)

src/Compilation.zig+15-3
......@@ -1549,7 +1549,8 @@ pub const SystemLib = link.SystemLib;
15491549pub const CacheMode = enum {
15501550 /// The results of this compilation are not cached. The compilation is always performed, and the
15511551 /// results are emitted directly to their output locations. Temporary files will be placed in a
1552 /// temporary directory in the cache, but deleted after the compilation is done.
1552 /// temporary directory in the cache, but deleted after the compilation is done, unless they are
1553 /// needed for the output binary to work correctly.
15531554 ///
15541555 /// This mode is typically used for direct CLI invocations like `zig build-exe`, because such
15551556 /// processes are typically low-level usages which would not make efficient use of the cache.
......@@ -1593,8 +1594,8 @@ const CacheUse = union(CacheMode) {
15931594 const None = struct {
15941595 /// User-requested artifacts are written directly to their output path in this cache mode.
15951596 /// However, if we need to emit any temporary files, they are placed in this directory.
1596 /// We will recursively delete this directory at the end of this update. This field is
1597 /// non-`null` only inside `update`.
1597 /// We will recursively delete this directory at the end of this update if possible. This
1598 /// field is non-`null` only inside `update`.
15981599 tmp_artifact_directory: ?Cache.Directory,
15991600 };
16001601
......@@ -2807,6 +2808,17 @@ fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {
28072808 // temporary directories; it doesn't have a real cache directory anyway.
28082809 return;
28092810 }
2811 // Usually, we want to delete the temporary directory. However, if we are emitting
2812 // an unstripped Mach-O binary with the LLVM backend, then the temporary directory
2813 // contains the ZCU object file emitted by LLVM, which contains debug symbols not
2814 // replicated in the output binary (the output instead contains a reference to that
2815 // file which debug tooling can look through). So, in that particular case, we need
2816 // to keep this directory around so that the output binary can be debugged.
2817 if (comp.bin_file != null and comp.getTarget().ofmt == .macho and comp.config.debug_format != .strip) {
2818 // We are emitting an unstripped Mach-O binary with the LLVM backend: the ZCU
2819 // object file must remain on-disk for its debug info.
2820 return;
2821 }
28102822 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
28112823 comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| {
28122824 log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{