fix std.Build.OptionsStep

* use the same hash function as the rest of the steps
 * fix race condition due to a macOS oddity.
 * fix race condition due to file truncation (rename into place instead)
 * integrate with marking Step.result_cached. check if the file already
   exists with fs.access before doing anything else.
 * use a directory so that the file basename can be "options.zig"
   instead of a hash digest.
 * better error reporting in case of file system failures.
This commit is contained in:
Andrew Kelley 2023-03-18 18:32:43 -07:00
parent 2ac8d90df0
commit 8f481dfc3c
4 changed files with 71 additions and 27 deletions

View File

@ -1737,7 +1737,7 @@ pub fn makeTempPath(b: *Build) []const u8 {
const rand_int = std.crypto.random.int(u64);
const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ hex64(rand_int);
const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");
fs.cwd().makePath(result_path) catch |err| {
b.cache_root.handle.makePath(tmp_dir_sub_path) catch |err| {
std.debug.print("unable to make tmp path '{s}': {s}\n", .{
result_path, @errorName(err),
});
@ -1747,7 +1747,7 @@ pub fn makeTempPath(b: *Build) []const u8 {
/// There are a few copies of this function in miscellaneous places. Would be nice to find
/// a home for them.
fn hex64(x: u64) [16]u8 {
pub fn hex64(x: u64) [16]u8 {
const hex_charset = "0123456789abcdef";
var result: [16]u8 = undefined;
var i: usize = 0;

View File

@ -241,33 +241,75 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
);
}
var options_dir = try b.cache_root.handle.makeOpenPath("options", .{});
defer options_dir.close();
const basename = "options.zig";
const basename = self.hashContentsToFileName();
// Hash contents to file name.
var hash = b.cache.hash;
// Random bytes to make unique. Refresh this with new random bytes when
// implementation is modified in a non-backwards-compatible way.
hash.add(@as(u32, 0x38845ef8));
hash.addBytes(self.contents.items);
const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename;
try options_dir.writeFile(&basename, self.contents.items);
self.generated_file.path = try b.cache_root.join(b.allocator, &.{sub_path});
self.generated_file.path = try b.cache_root.join(b.allocator, &.{ "options", &basename });
}
// Optimize for the hot path. Stat the file, and if it already exists,
// cache hit.
if (b.cache_root.handle.access(sub_path, .{})) |_| {
// This is the hot path, success.
step.result_cached = true;
return;
} else |outer_err| switch (outer_err) {
error.FileNotFound => {
const sub_dirname = fs.path.dirname(sub_path).?;
b.cache_root.handle.makePath(sub_dirname) catch |e| {
return step.fail("unable to make path '{}{s}': {s}", .{
b.cache_root, sub_dirname, @errorName(e),
});
};
fn hashContentsToFileName(self: *OptionsStep) [64]u8 {
// TODO update to use the cache system instead of this
// This implementation is copied from `WriteFileStep.make`
const rand_int = std.crypto.random.int(u64);
const tmp_sub_path = "tmp" ++ fs.path.sep_str ++
std.Build.hex64(rand_int) ++ fs.path.sep_str ++
basename;
const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;
var hash = std.crypto.hash.blake2.Blake2b384.init(.{});
b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| {
return step.fail("unable to make temporary directory '{}{s}': {s}", .{
b.cache_root, tmp_sub_path_dirname, @errorName(err),
});
};
// Random bytes to make OptionsStep unique. Refresh this with
// new random bytes when OptionsStep implementation is modified
// in a non-backwards-compatible way.
hash.update("yL0Ya4KkmcCjBlP8");
hash.update(self.contents.items);
b.cache_root.handle.writeFile(tmp_sub_path, self.contents.items) catch |err| {
return step.fail("unable to write options to '{}{s}': {s}", .{
b.cache_root, tmp_sub_path, @errorName(err),
});
};
var digest: [48]u8 = undefined;
hash.final(&digest);
var hash_basename: [64]u8 = undefined;
_ = fs.base64_encoder.encode(&hash_basename, &digest);
return hash_basename;
b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) {
error.PathAlreadyExists => {
// Other process beat us to it. Clean up the temp file.
b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| {
try step.addError("warning: unable to delete temp file '{}{s}': {s}", .{
b.cache_root, tmp_sub_path, @errorName(e),
});
};
step.result_cached = true;
return;
},
else => {
return step.fail("unable to rename options from '{}{s}' to '{}{s}': {s}", .{
b.cache_root, tmp_sub_path,
b.cache_root, sub_path,
@errorName(err),
});
},
};
},
else => |e| return step.fail("unable to access options file '{}{s}': {s}", .{
b.cache_root, sub_path, @errorName(e),
}),
}
}
const OptionArtifactArg = struct {

View File

@ -213,6 +213,11 @@ pub const build_cases = [_]BuildCase{
.build_root = "test/standalone/issue_13030",
.import = @import("standalone/issue_13030/build.zig"),
},
// TODO restore this test
//.{
// .build_root = "test/standalone/options",
// .import = @import("standalone/options/build.zig"),
//},
};
const std = @import("std");

View File

@ -1,13 +1,10 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const main = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
.target = .{},
.optimize = .Debug,
});
const options = b.addOptions();