authorgravatar for cbilz@posteo.netConstantin Bilz <cbilz@posteo.net> 2025-02-06 14:33:41+01:00
committergravatar for cbilz@posteo.netConstantin Bilz <cbilz@posteo.net> 2025-02-21 15:22:39+01:00
log62e388e73e71ed70bb5f059796035be6f59e72b3
tree1eb2cf53d9241593434bffd677e22830475802c0
parent05d8b565ad1a1d6c0c1e93dc47f1d828043fcafc

std.Build.Step.ConfigHeader: Add support for Autoconf `@FOO@` variables

Add the new style `Style.autoconf_at`. Rename the existing `Style.autoconf` (which uses `#undef` directives) to `Style.autoconf_undef`.

1 files changed, 121 insertions(+), 6 deletions(-)

lib/std/Build/Step/ConfigHeader.zig+121-6
...@@ -4,9 +4,11 @@ const Step = std.Build.Step;...@@ -4,9 +4,11 @@ const Step = std.Build.Step;
4const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
55
6pub const Style = union(enum) {6pub const Style = union(enum) {
7 /// The configure format supported by autotools. It uses `#undef foo` to7 /// A configure format supported by autotools that uses `#undef foo` to
8 /// mark lines that can be substituted with different values.8 /// mark lines that can be substituted with different values.
9 autoconf: std.Build.LazyPath,9 autoconf_undef: std.Build.LazyPath,
10 /// A configure format supported by autotools that uses `@FOO@` output variables.
11 autoconf_at: std.Build.LazyPath,
10 /// The configure format supported by CMake. It uses `@FOO@`, `${}` and12 /// The configure format supported by CMake. It uses `@FOO@`, `${}` and
11 /// `#cmakedefine` for template substitution.13 /// `#cmakedefine` for template substitution.
12 cmake: std.Build.LazyPath,14 cmake: std.Build.LazyPath,
...@@ -17,7 +19,7 @@ pub const Style = union(enum) {...@@ -17,7 +19,7 @@ pub const Style = union(enum) {
1719
18 pub fn getPath(style: Style) ?std.Build.LazyPath {20 pub fn getPath(style: Style) ?std.Build.LazyPath {
19 switch (style) {21 switch (style) {
20 .autoconf, .cmake => |s| return s,22 .autoconf_undef, .autoconf_at, .cmake => |s| return s,
21 .blank, .nasm => return null,23 .blank, .nasm => return null,
22 }24 }
23 }25 }
...@@ -191,7 +193,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -191,7 +193,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
191 const asm_generated_line = "; " ++ header_text ++ "\n";193 const asm_generated_line = "; " ++ header_text ++ "\n";
192194
193 switch (config_header.style) {195 switch (config_header.style) {
194 .autoconf => |file_source| {196 .autoconf_undef, .autoconf_at => |file_source| {
195 try output.appendSlice(c_generated_line);197 try output.appendSlice(c_generated_line);
196 const src_path = file_source.getPath2(b, step);198 const src_path = file_source.getPath2(b, step);
197 const contents = std.fs.cwd().readFileAlloc(arena, src_path, config_header.max_bytes) catch |err| {199 const contents = std.fs.cwd().readFileAlloc(arena, src_path, config_header.max_bytes) catch |err| {
...@@ -199,7 +201,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -199,7 +201,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
199 src_path, @errorName(err),201 src_path, @errorName(err),
200 });202 });
201 };203 };
202 try render_autoconf(step, contents, &output, config_header.values, src_path);204 switch (config_header.style) {
205 .autoconf_undef => try render_autoconf_undef(step, contents, &output, config_header.values, src_path),
206 .autoconf_at => try render_autoconf_at(step, contents, &output, config_header.values, src_path),
207 else => unreachable,
208 }
203 },209 },
204 .cmake => |file_source| {210 .cmake => |file_source| {
205 try output.appendSlice(c_generated_line);211 try output.appendSlice(c_generated_line);
...@@ -257,7 +263,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -257,7 +263,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
257 try man.writeManifest();263 try man.writeManifest();
258}264}
259265
260fn render_autoconf(266fn render_autoconf_undef(
261 step: *Step,267 step: *Step,
262 contents: []const u8,268 contents: []const u8,
263 output: *std.ArrayList(u8),269 output: *std.ArrayList(u8),
...@@ -304,6 +310,62 @@ fn render_autoconf(...@@ -304,6 +310,62 @@ fn render_autoconf(
304 }310 }
305}311}
306312
313fn render_autoconf_at(
314 step: *Step,
315 contents: []const u8,
316 output: *std.ArrayList(u8),
317 values: std.StringArrayHashMap(Value),
318 src_path: []const u8,
319) !void {
320 const build = step.owner;
321 const allocator = build.allocator;
322
323 const used = allocator.alloc(bool, values.count()) catch @panic("OOM");
324 for (used) |*u| u.* = false;
325 defer allocator.free(used);
326
327 var any_errors = false;
328 var line_index: u32 = 0;
329 var line_it = std.mem.splitScalar(u8, contents, '\n');
330 while (line_it.next()) |line| : (line_index += 1) {
331 const last_line = line_it.index == line_it.buffer.len;
332
333 const old_len = output.items.len;
334 expand_variables_autoconf_at(output, line, values, used) catch |err| switch (err) {
335 error.MissingValue => {
336 const name = output.items[old_len..];
337 defer output.shrinkRetainingCapacity(old_len);
338 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
339 src_path, line_index + 1, name,
340 });
341 any_errors = true;
342 continue;
343 },
344 else => {
345 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{
346 src_path, line_index + 1, @errorName(err),
347 });
348 any_errors = true;
349 continue;
350 },
351 };
352 if (!last_line) {
353 try output.append('\n');
354 }
355 }
356
357 for (values.unmanaged.entries.slice().items(.key), used) |name, u| {
358 if (!u) {
359 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
360 any_errors = true;
361 }
362 }
363
364 if (any_errors) {
365 return error.MakeFailed;
366 }
367}
368
307fn render_cmake(369fn render_cmake(
308 step: *Step,370 step: *Step,
309 contents: []const u8,371 contents: []const u8,
...@@ -536,6 +598,59 @@ fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !...@@ -536,6 +598,59 @@ fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !
536 }598 }
537}599}
538600
601fn expand_variables_autoconf_at(
602 output: *std.ArrayList(u8),
603 contents: []const u8,
604 values: std.StringArrayHashMap(Value),
605 used: []bool,
606) !void {
607 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
608
609 var curr: usize = 0;
610 var source_offset: usize = 0;
611 while (curr < contents.len) : (curr += 1) {
612 if (contents[curr] != '@') continue;
613 if (std.mem.indexOfScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
614 if (close_pos == curr + 1) {
615 // closed immediately, preserve as a literal
616 continue;
617 }
618 const valid_varname_end = std.mem.indexOfNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
619 if (valid_varname_end != close_pos) {
620 // contains invalid characters, preserve as a literal
621 continue;
622 }
623
624 const key = contents[curr + 1 .. close_pos];
625 const index = values.getIndex(key) orelse {
626 // Report the missing key to the caller.
627 try output.appendSlice(key);
628 return error.MissingValue;
629 };
630 const value = values.unmanaged.entries.slice().items(.value)[index];
631 used[index] = true;
632 try output.appendSlice(contents[source_offset..curr]);
633 switch (value) {
634 .undef, .defined => {},
635 .boolean => |b| {
636 try output.append(if (b) '1' else '0');
637 },
638 .int => |i| {
639 try output.writer().print("{d}", .{i});
640 },
641 .ident, .string => |s| {
642 try output.appendSlice(s);
643 },
644 }
645
646 curr = close_pos;
647 source_offset = close_pos + 1;
648 }
649 }
650
651 try output.appendSlice(contents[source_offset..]);
652}
653
539fn expand_variables_cmake(654fn expand_variables_cmake(
540 allocator: Allocator,655 allocator: Allocator,
541 contents: []const u8,656 contents: []const u8,