Maker: progress towards ConfigHeader

however... why is this done in the make phase anyway? making a header
like this is typically done by the configure phase...
This commit is contained in:
Andrew Kelley
2026-05-22 21:51:11 -07:00
parent ed1f005826
commit e435299cfa
3 changed files with 141 additions and 122 deletions
+5 -18
View File
@@ -20,6 +20,7 @@ const Maker = @import("../Maker.zig");
pub const CheckFile = @import("Step/CheckFile.zig");
pub const Compile = @import("Step/Compile.zig");
pub const ConfigHeader = @import("Step/ConfigHeader.zig");
pub const FindProgram = @import("Step/FindProgram.zig");
pub const Fmt = @import("Step/Fmt.zig");
pub const InstallArtifact = @import("Step/InstallArtifact.zig");
@@ -78,7 +79,7 @@ comptime {
pub const Extended = union(enum) {
check_file: CheckFile,
compile: Compile,
config_header: Todo,
config_header: ConfigHeader,
fail: Fail,
find_program: FindProgram,
fmt: Fmt,
@@ -114,21 +115,6 @@ pub const Extended = union(enum) {
};
}
pub const Todo = struct {
pub fn make(
todo: *Todo,
step_index: Configuration.Step.Index,
maker: *Maker,
progress_node: std.Progress.Node,
) Step.ExtendedMakeError!void {
_ = todo;
_ = progress_node;
const conf = &maker.scanned_config.configuration;
const conf_step = step_index.ptr(conf);
std.debug.panic("TODO implement another step type: {s}", .{conf_step.name.slice(conf)});
}
};
pub const TopLevel = struct {
pub fn make(
top_level: *TopLevel,
@@ -750,8 +736,9 @@ fn failWithCacheError(
/// separately from using the cache system.
pub fn writeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void {
if (s.test_results.isSuccess()) {
man.writeManifest() catch |err| {
try s.addError(maker, "failed writing cache manifest: {t}", .{err});
man.writeManifest() catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| try s.addError(maker, "failed writing cache manifest: {t}", .{e}),
};
}
}
+135 -103
View File
@@ -4,22 +4,35 @@ const std = @import("std");
const Io = std.Io;
const Configuration = std.Build.Configuration;
const Writer = std.Io.Writer;
const Path = std.Build.Cache.Path;
const Allocator = std.mem.Allocator;
const Step = @import("../Step.zig");
const Maker = @import("../../Maker.zig");
const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
const c_generated_line = "/* " ++ header_text ++ " */\n";
const asm_generated_line = "; " ++ header_text ++ "\n";
pub fn make(
config_header: *ConfigHeader,
step_index: Configuration.Step.Index,
maker: *Maker,
progress_node: std.Progress.Node,
) Step.ExtendedMakeError!void {
_ = config_header;
_ = progress_node;
const graph = maker.graph;
const arena = maker.graph.arena; // TODO don't leak into process arena
const gpa = maker.gpa;
const step = maker.stepByIndex(step_index);
const io = graph.io;
const arena = graph.arena; // TODO don't leak into the process arena
const conf = &maker.scanned_config.configuration;
const conf_step = step_index.ptr(conf);
const conf_ch = conf_step.extended.get(conf.extra).config_header;
const cache_root = graph.local_cache_root;
if (config_header.style.getPath()) |lp|
if (conf_ch.style.getPath()) |lp|
try step.singleUnchangingWatchInput(maker, arena, lp);
var man = graph.cache.obtain();
@@ -29,45 +42,51 @@ pub fn make(
// random bytes when ConfigHeader implementation is modified in a
// non-backwards-compatible way.
man.hash.add(@as(u32, 0xdef08d23));
man.hash.addBytes(config_header.include_path);
man.hash.addOptionalBytes(config_header.include_guard_override);
man.hash.addBytes(conf_ch.include_path);
man.hash.addOptionalBytes(conf_ch.include_guard_override);
var aw: Writer.Allocating = .init(arena);
defer aw.deinit();
const bw = &aw.writer;
const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
const c_generated_line = "/* " ++ header_text ++ " */\n";
const asm_generated_line = "; " ++ header_text ++ "\n";
switch (config_header.style) {
.autoconf_undef, .autoconf_at => |file_source| {
try bw.writeAll(c_generated_line);
const src_path = file_source.getPath2(b, step);
const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
switch (conf_ch.flags.style) {
.autoconf_undef => {
const src_path = try maker.resolveLazyPathIndex(arena, conf_ch.template_file.value.?, step_index);
const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(conf_ch.max_bytes)) catch |err|
return step.fail("unable to read autoconf input file {s}: {t}", .{ src_path, err });
renderAutoConfUndef(step, contents, &aw.writer, &conf_ch.values, src_path) catch |err| switch (err) {
error.WriteFailed => return error.OutOfMemory,
else => |e| return e,
};
switch (config_header.style) {
.autoconf_undef => try render_autoconf_undef(step, contents, bw, &config_header.values, src_path),
.autoconf_at => try render_autoconf_at(step, contents, &aw, &config_header.values, src_path),
else => unreachable,
}
},
.cmake => |file_source| {
try bw.writeAll(c_generated_line);
const src_path = file_source.getPath2(b, step);
const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
return step.fail("unable to read cmake input file {s}: {t}", .{ src_path, err });
.autoconf_at => {
const src_path = try maker.resolveLazyPathIndex(arena, conf_ch.template_file.value.?, step_index);
const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(conf_ch.max_bytes)) catch |err|
return step.fail("unable to read autoconf input file {s}: {t}", .{ src_path, err });
renderAutoconfAt(step, contents, &aw, &conf_ch.values, src_path) catch |err| switch (err) {
error.WriteFailed => return error.OutOfMemory,
else => |e| return e,
};
},
.cmake => {
const src_path = try maker.resolveLazyPathIndex(arena, conf_ch.template_file.value.?, step_index);
const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(conf_ch.max_bytes)) catch |err|
return step.fail("unable to read cmake input file {s}: {t}", .{ src_path, err });
renderCmake(step, contents, &aw.writer, conf_ch.values, src_path) catch |err| switch (err) {
error.WriteFailed => return error.OutOfMemory,
else => |e| return e,
};
try render_cmake(step, contents, bw, config_header.values, src_path);
},
.blank => {
try bw.writeAll(c_generated_line);
try render_blank(gpa, bw, config_header.values, config_header.include_path, config_header.include_guard_override);
renderBlank(gpa, &aw.writer, conf_ch.values, conf_ch.include_path, conf_ch.include_guard_override) catch |err| switch (err) {
error.WriteFailed => return error.OutOfMemory,
else => |e| return e,
};
},
.nasm => {
try bw.writeAll(asm_generated_line);
try render_nasm(bw, config_header.values);
renderNasm(&aw.writer, conf_ch.values) catch |err| switch (err) {
error.WriteFailed => return error.OutOfMemory,
else => |e| return e,
};
},
}
@@ -76,7 +95,10 @@ pub fn make(
if (try step.cacheHit(&man)) {
const digest = man.final();
config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest });
maker.generatedPath().* = .{
.root_dir = cache_root,
.sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
};
return;
}
@@ -87,35 +109,38 @@ pub fn make(
// output_path is libavutil/avconfig.h
// We want to open directory zig-cache/o/HASH/libavutil/
// but keep output_dir as zig-cache/o/HASH for -I include
const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path });
const sub_path_dirname = std.fs.path.dirname(sub_path).?;
const out_path: Path = .{
.root_dir = cache_root,
.sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, conf_ch.include_path.slice(conf) }),
};
const out_path_dirname = out_path.dirname().?;
b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| {
return step.fail("unable to make path '{f}{s}': {s}", .{
b.cache_root, sub_path_dirname, @errorName(err),
});
out_path_dirname.root_dir.handle.createDirPath(io, out_path_dirname.sub_path) catch |err|
return step.fail("unable to make path {f}: {t}", .{ out_path_dirname, err });
out_path.root_dir.handle.writeFile(io, .{ .sub_path = out_path.sub_path, .data = output }) catch |err|
return step.fail("unable to write file {f}: {t}", .{ out_path, err });
maker.generatedPath().* = .{
.root_dir = cache_root,
.sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
};
b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = output }) catch |err| {
return step.fail("unable to write file '{f}{s}': {s}", .{
b.cache_root, sub_path, @errorName(err),
});
};
config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest });
try man.writeManifest();
try step.writeManifest(maker, &man);
}
fn render_autoconf_undef(
fn renderAutoConfUndef(
step: *Step,
contents: []const u8,
bw: *Writer,
w: *Writer,
values: *const std.array_hash_map.String(Value),
src_path: []const u8,
) !void {
const build = step.owner;
const allocator = build.allocator;
try w.writeAll(c_generated_line);
var is_used: std.bit_set.Dynamic = try .initEmpty(allocator, values.count());
defer is_used.deinit(allocator);
@@ -124,15 +149,15 @@ fn render_autoconf_undef(
var line_it = std.mem.splitScalar(u8, contents, '\n');
while (line_it.next()) |line| : (line_index += 1) {
if (!std.mem.startsWith(u8, line, "#")) {
try bw.writeAll(line);
try bw.writeByte('\n');
try w.writeAll(line);
try w.writeByte('\n');
continue;
}
var it = std.mem.tokenizeAny(u8, line[1..], " \t\r");
const undef = it.next().?;
if (!std.mem.eql(u8, undef, "undef")) {
try bw.writeAll(line);
try bw.writeByte('\n');
try w.writeAll(line);
try w.writeByte('\n');
continue;
}
const name = it.next().?;
@@ -144,7 +169,7 @@ fn render_autoconf_undef(
continue;
};
is_used.set(index);
try renderValueC(bw, name, values.values()[index]);
try renderValueC(w, name, values.values()[index]);
}
var unused_value_it = is_used.iterator(.{ .kind = .unset });
@@ -158,7 +183,7 @@ fn render_autoconf_undef(
}
}
fn render_autoconf_at(
fn renderAutoconfAt(
step: *Step,
contents: []const u8,
aw: *Writer.Allocating,
@@ -167,7 +192,9 @@ fn render_autoconf_at(
) !void {
const build = step.owner;
const allocator = build.allocator;
const bw = &aw.writer;
const w = &aw.writer;
try w.writeAll(c_generated_line);
const used = allocator.alloc(bool, values.count()) catch @panic("OOM");
for (used) |*u| u.* = false;
@@ -180,7 +207,7 @@ fn render_autoconf_at(
const last_line = line_it.index == line_it.buffer.len;
const old_len = aw.written().len;
expand_variables_autoconf_at(bw, line, values, used) catch |err| switch (err) {
expandVariablesAutoconfAt(w, line, values, used) catch |err| switch (err) {
error.MissingValue => {
const name = aw.written()[old_len..];
defer aw.shrinkRetainingCapacity(old_len);
@@ -198,7 +225,7 @@ fn render_autoconf_at(
continue;
},
};
if (!last_line) try bw.writeByte('\n');
if (!last_line) try w.writeByte('\n');
}
for (values.entries.slice().items(.key), used) |name, u| {
@@ -211,16 +238,18 @@ fn render_autoconf_at(
if (any_errors) return error.MakeFailed;
}
fn render_cmake(
fn renderCmake(
step: *Step,
contents: []const u8,
bw: *Writer,
w: *Writer,
values: std.array_hash_map.String(Value),
src_path: []const u8,
) !void {
const build = step.owner;
const allocator = build.allocator;
try w.writeAll(c_generated_line);
var values_copy = try values.clone(allocator);
defer values_copy.deinit(allocator);
@@ -230,7 +259,7 @@ fn render_cmake(
while (line_it.next()) |raw_line| : (line_index += 1) {
const last_line = line_it.index == line_it.buffer.len;
const line = expand_variables_cmake(allocator, raw_line, values) catch |err| switch (err) {
const line = expandVariablesCmake(allocator, raw_line, values) catch |err| switch (err) {
error.InvalidCharacter => {
try step.addError("{s}:{d}: error: invalid character in a variable name", .{
src_path, line_index + 1,
@@ -249,16 +278,16 @@ fn render_cmake(
defer allocator.free(line);
const line_start = std.mem.findNone(u8, line, " \t\r") orelse {
try bw.writeAll(line);
if (!last_line) try bw.writeByte('\n');
try w.writeAll(line);
if (!last_line) try w.writeByte('\n');
continue;
};
const whitespace_prefix = line[0..line_start];
const trimmed_line = line[line_start..];
if (!std.mem.startsWith(u8, trimmed_line, "#")) {
try bw.writeAll(line);
if (!last_line) try bw.writeByte('\n');
try w.writeAll(line);
if (!last_line) try w.writeByte('\n');
continue;
}
@@ -267,8 +296,8 @@ fn render_cmake(
if (!std.mem.eql(u8, cmakedefine, "cmakedefine") and
!std.mem.eql(u8, cmakedefine, "cmakedefine01"))
{
try bw.writeAll(line);
if (!last_line) try bw.writeByte('\n');
try w.writeAll(line);
if (!last_line) try w.writeByte('\n');
continue;
}
@@ -339,8 +368,8 @@ fn render_cmake(
value = Value{ .ident = it.rest() };
}
try bw.writeAll(whitespace_prefix);
try renderValueC(bw, name, value);
try w.writeAll(whitespace_prefix);
try renderValueC(w, name, value);
}
if (any_errors) {
@@ -348,13 +377,15 @@ fn render_cmake(
}
}
fn render_blank(
fn renderBlank(
gpa: std.mem.Allocator,
bw: *Writer,
w: *Writer,
defines: std.array_hash_map.String(Value),
include_path: []const u8,
include_guard_override: ?[]const u8,
) !void {
try w.writeAll(c_generated_line);
const include_guard_name = include_guard_override orelse blk: {
const name = try gpa.dupe(u8, include_path);
for (name) |*byte| {
@@ -368,51 +399,52 @@ fn render_blank(
};
defer if (include_guard_override == null) gpa.free(include_guard_name);
try bw.print(
try w.print(
\\#ifndef {[0]s}
\\#define {[0]s}
\\
, .{include_guard_name});
const values = defines.values();
for (defines.keys(), 0..) |name, i| try renderValueC(bw, name, values[i]);
for (defines.keys(), 0..) |name, i| try renderValueC(w, name, values[i]);
try bw.print(
try w.print(
\\#endif /* {s} */
\\
, .{include_guard_name});
}
fn render_nasm(bw: *Writer, defines: std.array_hash_map.String(Value)) !void {
for (defines.keys(), defines.values()) |name, value| try renderValueNasm(bw, name, value);
fn renderNasm(w: *Writer, defines: std.array_hash_map.String(Value)) !void {
try w.writeAll(asm_generated_line);
for (defines.keys(), defines.values()) |name, value| try renderValueNasm(w, name, value);
}
fn renderValueC(bw: *Writer, name: []const u8, value: Value) !void {
fn renderValueC(w: *Writer, name: []const u8, value: Value) !void {
switch (value) {
.undef => try bw.print("/* #undef {s} */\n", .{name}),
.defined => try bw.print("#define {s}\n", .{name}),
.boolean => |b| try bw.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
.int => |i| try bw.print("#define {s} {d}\n", .{ name, i }),
.ident => |ident| try bw.print("#define {s} {s}\n", .{ name, ident }),
.undef => try w.print("/* #undef {s} */\n", .{name}),
.defined => try w.print("#define {s}\n", .{name}),
.boolean => |b| try w.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
.int => |i| try w.print("#define {s} {d}\n", .{ name, i }),
.ident => |ident| try w.print("#define {s} {s}\n", .{ name, ident }),
// TODO: use C-specific escaping instead of zig string literals
.string => |string| try bw.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
.string => |string| try w.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
}
}
fn renderValueNasm(bw: *Writer, name: []const u8, value: Value) !void {
fn renderValueNasm(w: *Writer, name: []const u8, value: Value) !void {
switch (value) {
.undef => try bw.print("; %undef {s}\n", .{name}),
.defined => try bw.print("%define {s}\n", .{name}),
.boolean => |b| try bw.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
.int => |i| try bw.print("%define {s} {d}\n", .{ name, i }),
.ident => |ident| try bw.print("%define {s} {s}\n", .{ name, ident }),
.undef => try w.print("; %undef {s}\n", .{name}),
.defined => try w.print("%define {s}\n", .{name}),
.boolean => |b| try w.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
.int => |i| try w.print("%define {s} {d}\n", .{ name, i }),
.ident => |ident| try w.print("%define {s} {s}\n", .{ name, ident }),
// TODO: use nasm-specific escaping instead of zig string literals
.string => |string| try bw.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
.string => |string| try w.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
}
}
fn expand_variables_autoconf_at(
bw: *Writer,
fn expandVariablesAutoconfAt(
w: *Writer,
contents: []const u8,
values: *const std.array_hash_map.String(Value),
used: []bool,
@@ -437,17 +469,17 @@ fn expand_variables_autoconf_at(
const key = contents[curr + 1 .. close_pos];
const index = values.getIndex(key) orelse {
// Report the missing key to the caller.
try bw.writeAll(key);
try w.writeAll(key);
return error.MissingValue;
};
const value = values.entries.slice().items(.value)[index];
used[index] = true;
try bw.writeAll(contents[source_offset..curr]);
try w.writeAll(contents[source_offset..curr]);
switch (value) {
.undef, .defined => {},
.boolean => |b| try bw.writeByte(@as(u8, '0') + @intFromBool(b)),
.int => |i| try bw.print("{d}", .{i}),
.ident, .string => |s| try bw.writeAll(s),
.boolean => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)),
.int => |i| try w.print("{d}", .{i}),
.ident, .string => |s| try w.writeAll(s),
}
curr = close_pos;
@@ -455,10 +487,10 @@ fn expand_variables_autoconf_at(
}
}
try bw.writeAll(contents[source_offset..]);
try w.writeAll(contents[source_offset..]);
}
fn expand_variables_cmake(
fn expandVariablesCmake(
allocator: Allocator,
contents: []const u8,
values: std.array_hash_map.String(Value),
@@ -602,7 +634,7 @@ fn testReplaceVariablesAutoconfAt(
for (used) |*u| u.* = false;
defer allocator.free(used);
try expand_variables_autoconf_at(&aw.writer, contents, values, used);
try expandVariablesAutoconfAt(&aw.writer, contents, values, used);
for (used) |u| if (!u) return error.UnusedValue;
try std.testing.expectEqualStrings(expected, aw.written());
@@ -614,13 +646,13 @@ fn testReplaceVariablesCMake(
expected: []const u8,
values: std.array_hash_map.String(Value),
) !void {
const actual = try expand_variables_cmake(allocator, contents, values);
const actual = try expandVariablesCmake(allocator, contents, values);
defer allocator.free(actual);
try std.testing.expectEqualStrings(expected, actual);
}
test "expand_variables_autoconf_at simple cases" {
test "expandVariablesAutoconfAt simple cases" {
const allocator = std.testing.allocator;
var values: std.array_hash_map.String(Value) = .init(allocator);
defer values.deinit();
@@ -716,7 +748,7 @@ test "expand_variables_autoconf_at simple cases" {
values.clearRetainingCapacity();
}
test "expand_variables_autoconf_at edge cases" {
test "expandVariablesAutoconfAt edge cases" {
const allocator = std.testing.allocator;
var values: std.array_hash_map.String(Value) = .init(allocator);
defer values.deinit();
@@ -732,7 +764,7 @@ test "expand_variables_autoconf_at edge cases" {
values.clearRetainingCapacity();
}
test "expand_variables_cmake simple cases" {
test "expandVariablesCmake simple cases" {
const allocator = std.testing.allocator;
var values: std.array_hash_map.String(Value) = .init(allocator);
defer values.deinit();
@@ -820,7 +852,7 @@ test "expand_variables_cmake simple cases" {
try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", values));
}
test "expand_variables_cmake edge cases" {
test "expandVariablesCmake edge cases" {
const allocator = std.testing.allocator;
var values: std.array_hash_map.String(Value) = .init(allocator);
defer values.deinit();
@@ -881,7 +913,7 @@ test "expand_variables_cmake edge cases" {
try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", values));
}
test "expand_variables_cmake escaped characters" {
test "expandVariablesCmake escaped characters" {
const allocator = std.testing.allocator;
var values: std.array_hash_map.String(Value) = .init(allocator);
defer values.deinit();
+1 -1
View File
@@ -166,7 +166,7 @@ pub fn make(
maker.generatedPath(conf_oc.output_file).* = dest_path;
man.writeManifest() catch |err| switch (err) {
step.writeManifest(maker, &man) catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| try step.addError(maker, "failed writing cache manifest: {t}", .{e}),
};