authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 09:06:54-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-07 22:43:52-07:00
log4bca5faca60df971712e538758315a0e0afe89a0
treefe0b8bac0ec070c00b46706edc84ee4c7b997e37
parentd6ac04c47811f162ca662f43d3c93a5dc0dcce86

std.Build.Cache: write manifest without heap allocating

Now that the buffered writing interface is not generic.

2 files changed, 47 insertions(+), 31 deletions(-)

lib/std/Build/Cache.zig+33-31
...@@ -2,6 +2,18 @@...@@ -2,6 +2,18 @@
2//! This is not a general-purpose cache. It is designed to be fast and simple,2//! This is not a general-purpose cache. It is designed to be fast and simple,
3//! not to withstand attacks using specially-crafted input.3//! not to withstand attacks using specially-crafted input.
44
5const Cache = @This();
6const std = @import("std");
7const builtin = @import("builtin");
8const crypto = std.crypto;
9const fs = std.fs;
10const assert = std.debug.assert;
11const testing = std.testing;
12const mem = std.mem;
13const fmt = std.fmt;
14const Allocator = std.mem.Allocator;
15const log = std.log.scoped(.cache);
16
5gpa: Allocator,17gpa: Allocator,
6manifest_dir: fs.Dir,18manifest_dir: fs.Dir,
7hash: HashHelper = .{},19hash: HashHelper = .{},
...@@ -21,18 +33,6 @@ pub const Path = @import("Cache/Path.zig");...@@ -21,18 +33,6 @@ pub const Path = @import("Cache/Path.zig");
21pub const Directory = @import("Cache/Directory.zig");33pub const Directory = @import("Cache/Directory.zig");
22pub const DepTokenizer = @import("Cache/DepTokenizer.zig");34pub const DepTokenizer = @import("Cache/DepTokenizer.zig");
2335
24const Cache = @This();
25const std = @import("std");
26const builtin = @import("builtin");
27const crypto = std.crypto;
28const fs = std.fs;
29const assert = std.debug.assert;
30const testing = std.testing;
31const mem = std.mem;
32const fmt = std.fmt;
33const Allocator = std.mem.Allocator;
34const log = std.log.scoped(.cache);
35
36pub fn addPrefix(cache: *Cache, directory: Directory) void {36pub fn addPrefix(cache: *Cache, directory: Directory) void {
37 cache.prefixes_buffer[cache.prefixes_len] = directory;37 cache.prefixes_buffer[cache.prefixes_len] = directory;
38 cache.prefixes_len += 1;38 cache.prefixes_len += 1;
...@@ -1118,25 +1118,12 @@ pub const Manifest = struct {...@@ -1118,25 +1118,12 @@ pub const Manifest = struct {
1118 if (self.manifest_dirty) {1118 if (self.manifest_dirty) {
1119 self.manifest_dirty = false;1119 self.manifest_dirty = false;
11201120
1121 const gpa = self.cache.gpa;1121 var buffer: [4000]u8 = undefined;
1122 var contents: std.ArrayListUnmanaged(u8) = .empty;1122 var fw = manifest_file.writer(&buffer);
1123 defer contents.deinit(gpa);1123 writeDirtyManifestToStream(self, &fw) catch |err| switch (err) {
11241124 error.WriteFailed => return fw.err.?,
1125 try contents.appendSlice(gpa, manifest_header ++ "\n");1125 else => |e| return e,
1126 for (self.files.keys()) |file| {1126 };
1127 try contents.print(gpa, "{d} {d} {d} {x} {d} {s}\n", .{
1128 file.stat.size,
1129 file.stat.inode,
1130 file.stat.mtime,
1131 &file.bin_digest,
1132 file.prefixed_path.prefix,
1133 file.prefixed_path.sub_path,
1134 });
1135 }
1136
1137 try manifest_file.setEndPos(contents.items.len);
1138 var pos: usize = 0;
1139 while (pos < contents.items.len) pos += try manifest_file.pwrite(contents.items[pos..], pos);
1140 }1127 }
11411128
1142 if (self.want_shared_lock) {1129 if (self.want_shared_lock) {
...@@ -1144,6 +1131,21 @@ pub const Manifest = struct {...@@ -1144,6 +1131,21 @@ pub const Manifest = struct {
1144 }1131 }
1145 }1132 }
11461133
1134 fn writeDirtyManifestToStream(self: *Manifest, fw: *fs.File.Writer) !void {
1135 try fw.interface.writeAll(manifest_header ++ "\n");
1136 for (self.files.keys()) |file| {
1137 try fw.interface.print("{d} {d} {d} {x} {d} {s}\n", .{
1138 file.stat.size,
1139 file.stat.inode,
1140 file.stat.mtime,
1141 &file.bin_digest,
1142 file.prefixed_path.prefix,
1143 file.prefixed_path.sub_path,
1144 });
1145 }
1146 try fw.end();
1147 }
1148
1147 fn downgradeToSharedLock(self: *Manifest) !void {1149 fn downgradeToSharedLock(self: *Manifest) !void {
1148 if (!self.have_exclusive_lock) return;1150 if (!self.have_exclusive_lock) return;
11491151
lib/std/fs/File.zig+14
...@@ -1894,6 +1894,20 @@ pub const Writer = struct {...@@ -1894,6 +1894,20 @@ pub const Writer = struct {
1894 },1894 },
1895 }1895 }
1896 }1896 }
1897
1898 pub const EndError = SetEndPosError || std.io.Writer.Error;
1899
1900 /// Flushes any buffered data and sets the end position of the file.
1901 ///
1902 /// If not overwriting existing contents, then calling `interface.flush`
1903 /// directly is sufficient.
1904 ///
1905 /// Flush failure is handled by setting `err` so that it can be handled
1906 /// along with other write failures.
1907 pub fn end(w: *Writer) EndError!void {
1908 try w.interface.flush();
1909 return w.file.setEndPos(w.pos);
1910 }
1897};1911};
18981912
1899/// Defaults to positional reading; falls back to streaming.1913/// Defaults to positional reading; falls back to streaming.