Commit 4bca5faca6

Andrew Kelley <andrew@ziglang.org>
2025-07-01 18:06:54
std.Build.Cache: write manifest without heap allocating
Now that the buffered writing interface is not generic.
1 parent d6ac04c
Changed files (2)
lib
std
lib/std/Build/Cache.zig
@@ -2,6 +2,18 @@
 //! This is not a general-purpose cache. It is designed to be fast and simple,
 //! not to withstand attacks using specially-crafted input.
 
+const Cache = @This();
+const std = @import("std");
+const builtin = @import("builtin");
+const crypto = std.crypto;
+const fs = std.fs;
+const assert = std.debug.assert;
+const testing = std.testing;
+const mem = std.mem;
+const fmt = std.fmt;
+const Allocator = std.mem.Allocator;
+const log = std.log.scoped(.cache);
+
 gpa: Allocator,
 manifest_dir: fs.Dir,
 hash: HashHelper = .{},
@@ -21,18 +33,6 @@ pub const Path = @import("Cache/Path.zig");
 pub const Directory = @import("Cache/Directory.zig");
 pub const DepTokenizer = @import("Cache/DepTokenizer.zig");
 
-const Cache = @This();
-const std = @import("std");
-const builtin = @import("builtin");
-const crypto = std.crypto;
-const fs = std.fs;
-const assert = std.debug.assert;
-const testing = std.testing;
-const mem = std.mem;
-const fmt = std.fmt;
-const Allocator = std.mem.Allocator;
-const log = std.log.scoped(.cache);
-
 pub fn addPrefix(cache: *Cache, directory: Directory) void {
     cache.prefixes_buffer[cache.prefixes_len] = directory;
     cache.prefixes_len += 1;
@@ -1118,25 +1118,12 @@ pub const Manifest = struct {
         if (self.manifest_dirty) {
             self.manifest_dirty = false;
 
-            const gpa = self.cache.gpa;
-            var contents: std.ArrayListUnmanaged(u8) = .empty;
-            defer contents.deinit(gpa);
-
-            try contents.appendSlice(gpa, manifest_header ++ "\n");
-            for (self.files.keys()) |file| {
-                try contents.print(gpa, "{d} {d} {d} {x} {d} {s}\n", .{
-                    file.stat.size,
-                    file.stat.inode,
-                    file.stat.mtime,
-                    &file.bin_digest,
-                    file.prefixed_path.prefix,
-                    file.prefixed_path.sub_path,
-                });
-            }
-
-            try manifest_file.setEndPos(contents.items.len);
-            var pos: usize = 0;
-            while (pos < contents.items.len) pos += try manifest_file.pwrite(contents.items[pos..], pos);
+            var buffer: [4000]u8 = undefined;
+            var fw = manifest_file.writer(&buffer);
+            writeDirtyManifestToStream(self, &fw) catch |err| switch (err) {
+                error.WriteFailed => return fw.err.?,
+                else => |e| return e,
+            };
         }
 
         if (self.want_shared_lock) {
@@ -1144,6 +1131,21 @@ pub const Manifest = struct {
         }
     }
 
+    fn writeDirtyManifestToStream(self: *Manifest, fw: *fs.File.Writer) !void {
+        try fw.interface.writeAll(manifest_header ++ "\n");
+        for (self.files.keys()) |file| {
+            try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{
+                file.stat.size,
+                file.stat.inode,
+                file.stat.mtime,
+                &file.bin_digest,
+                file.prefixed_path.prefix,
+                file.prefixed_path.sub_path,
+            });
+        }
+        try fw.end();
+    }
+
     fn downgradeToSharedLock(self: *Manifest) !void {
         if (!self.have_exclusive_lock) return;
 
lib/std/fs/File.zig
@@ -1894,6 +1894,20 @@ pub const Writer = struct {
             },
         }
     }
+
+    pub const EndError = SetEndPosError || std.io.Writer.Error;
+
+    /// Flushes any buffered data and sets the end position of the file.
+    ///
+    /// If not overwriting existing contents, then calling `interface.flush`
+    /// directly is sufficient.
+    ///
+    /// Flush failure is handled by setting `err` so that it can be handled
+    /// along with other write failures.
+    pub fn end(w: *Writer) EndError!void {
+        try w.interface.flush();
+        return w.file.setEndPos(w.pos);
+    }
 };
 
 /// Defaults to positional reading; falls back to streaming.