From 9989f72c61e4c4e98c69f6191a63c201336a6a19 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 23 May 2026 00:40:44 -0700 Subject: [PATCH] Maker: implement ConfigHeader --- lib/compiler/Maker/Step/ConfigHeader.zig | 782 +++++++++++------------ lib/std/Build/Configuration.zig | 32 + test/standalone/cmakedefine/build.zig | 1 - 3 files changed, 416 insertions(+), 399 deletions(-) diff --git a/lib/compiler/Maker/Step/ConfigHeader.zig b/lib/compiler/Maker/Step/ConfigHeader.zig index b86548b561a65686adf73a50402dcd86d9eede42..762b0ff483856df1aaa2a5ddad3d19b5c47199e7 100644 --- a/lib/compiler/Maker/Step/ConfigHeader.zig +++ b/lib/compiler/Maker/Step/ConfigHeader.zig @@ -10,9 +10,13 @@ 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"; +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"; + +/// Table value is whether the value is used. +const ValueMap = std.array_hash_map.String(bool); +const Value = Configuration.Step.ConfigHeader.Value; pub fn make( config_header: *ConfigHeader, @@ -23,7 +27,6 @@ pub fn make( _ = config_header; _ = progress_node; const graph = maker.graph; - 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 @@ -32,8 +35,20 @@ pub fn make( const conf_ch = conf_step.extended.get(conf.extra).config_header; const cache_root = graph.local_cache_root; - if (conf_ch.style.getPath()) |lp| - try step.singleUnchangingWatchInput(maker, arena, lp); + const input_size_limit: Io.Limit = if (conf_ch.input_size_limit.value) |x| .limited64(x) else .unlimited; + const include_guard_override: ?[]const u8 = if (conf_ch.include_guard.value) |s| s.slice(conf) else null; + const include_path: []const u8 = conf_ch.include_path.slice(conf); + const template_file = if (conf_ch.template_file.value) |lp| + try maker.resolveLazyPathIndex(arena, lp, step_index) + else + null; + const value_pairs = conf_ch.values.slice; + + if (conf_ch.template_file.value) |lp| try step.singleUnchangingWatchInput(maker, arena, lp.get(conf)); + + var value_map: ValueMap = .empty; + try value_map.ensureTotalCapacity(arena, value_pairs.len); + for (value_pairs) |pair| value_map.putAssumeCapacityNoClobber(pair.key.slice(conf), false); var man = graph.cache.obtain(); defer man.deinit(); @@ -42,48 +57,49 @@ 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(conf_ch.include_path); - man.hash.addOptionalBytes(conf_ch.include_guard_override); + man.hash.add(@as(u32, @bitCast(conf_ch.flags))); + man.hash.addBytes(include_path); + man.hash.addOptionalBytes(include_guard_override); var aw: Writer.Allocating = .init(arena); defer aw.deinit(); 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) { + const tf = template_file.?; + const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err| + return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err }); + renderAutoConfUndef(maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) { error.WriteFailed => return error.OutOfMemory, else => |e| return e, }; }, .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) { + const tf = template_file.?; + const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err| + return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err }); + renderAutoconfAt(maker, step, contents, &aw, value_pairs, &value_map, tf) 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) { + const tf = template_file.?; + const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err| + return step.fail(maker, "unable to read cmake input file {f}: {t}", .{ tf, err }); + renderCmake(arena, maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) { error.WriteFailed => return error.OutOfMemory, else => |e| return e, }; }, .blank => { - renderBlank(gpa, &aw.writer, conf_ch.values, conf_ch.include_path, conf_ch.include_guard_override) catch |err| switch (err) { + renderBlank(conf, &aw.writer, value_pairs, &value_map, include_path, include_guard_override) catch |err| switch (err) { error.WriteFailed => return error.OutOfMemory, else => |e| return e, }; }, .nasm => { - renderNasm(&aw.writer, conf_ch.values) catch |err| switch (err) { + renderNasm(conf, &aw.writer, value_pairs, &value_map) catch |err| switch (err) { error.WriteFailed => return error.OutOfMemory, else => |e| return e, }; @@ -93,9 +109,9 @@ pub fn make( const output = aw.written(); man.hash.addBytes(output); - if (try step.cacheHit(&man)) { + if (try step.cacheHit(maker, &man)) { const digest = man.final(); - maker.generatedPath().* = .{ + maker.generatedPath(conf_ch.generated_dir).* = .{ .root_dir = cache_root, .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), }; @@ -116,12 +132,12 @@ pub fn make( const out_path_dirname = out_path.dirname().?; 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 }); + return step.fail(maker, "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 }); + return step.fail(maker, "unable to write file {f}: {t}", .{ out_path, err }); - maker.generatedPath().* = .{ + maker.generatedPath(conf_ch.generated_dir).* = .{ .root_dir = cache_root, .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), }; @@ -129,21 +145,34 @@ pub fn make( try step.writeManifest(maker, &man); } +fn ensureAllValuesUsed( + maker: *Maker, + step: *Step, + value_map: *const ValueMap, + src_path: Path, +) Step.ExtendedMakeError!void { + var any_errors = false; + for (value_map.keys(), value_map.values()) |name, used| { + if (used) continue; + try step.addError(maker, "{f}: config header value unused: {s}", .{ src_path, name }); + any_errors = true; + } + if (any_errors) return error.MakeFailed; +} + fn renderAutoConfUndef( + maker: *Maker, step: *Step, contents: []const u8, w: *Writer, - values: *const std.array_hash_map.String(Value), - src_path: []const u8, + value_pairs: []const Value.Pair, + value_map: *ValueMap, + src_path: Path, ) !void { - const build = step.owner; - const allocator = build.allocator; + const conf = &maker.scanned_config.configuration; try w.writeAll(c_generated_line); - var is_used: std.bit_set.Dynamic = try .initEmpty(allocator, values.count()); - defer is_used.deinit(allocator); - var any_errors = false; var line_index: u32 = 0; var line_it = std.mem.splitScalar(u8, contents, '\n'); @@ -161,45 +190,35 @@ fn renderAutoConfUndef( continue; } const name = it.next().?; - const index = values.getIndex(name) orelse { - try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ + const index = value_map.getIndex(name) orelse { + try step.addError(maker, "{f}:{d}: unspecified config header value: {s}", .{ src_path, line_index + 1, name, }); any_errors = true; continue; }; - is_used.set(index); - try renderValueC(w, name, values.values()[index]); + value_map.values()[index] = true; // Set to used. + try renderValueC(conf, w, name, value_pairs[index].index); } - var unused_value_it = is_used.iterator(.{ .kind = .unset }); - while (unused_value_it.next()) |index| { - try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, values.keys()[index] }); - any_errors = true; - } - - if (any_errors) { - return error.MakeFailed; - } + try ensureAllValuesUsed(maker, step, value_map, src_path); + if (any_errors) return error.MakeFailed; } fn renderAutoconfAt( + maker: *Maker, step: *Step, contents: []const u8, aw: *Writer.Allocating, - values: *const std.array_hash_map.String(Value), - src_path: []const u8, + value_pairs: []const Value.Pair, + value_map: *const ValueMap, + src_path: Path, ) !void { - const build = step.owner; - const allocator = build.allocator; const w = &aw.writer; + const conf = &maker.scanned_config.configuration; try w.writeAll(c_generated_line); - const used = allocator.alloc(bool, values.count()) catch @panic("OOM"); - for (used) |*u| u.* = false; - defer allocator.free(used); - var any_errors = false; var line_index: u32 = 0; var line_it = std.mem.splitScalar(u8, contents, '\n'); @@ -207,19 +226,19 @@ fn renderAutoconfAt( const last_line = line_it.index == line_it.buffer.len; const old_len = aw.written().len; - expandVariablesAutoconfAt(w, line, values, used) catch |err| switch (err) { + expandVariablesAutoconfAt(w, line, conf, value_pairs, value_map) catch |err| switch (err) { error.MissingValue => { const name = aw.written()[old_len..]; defer aw.shrinkRetainingCapacity(old_len); - try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ + try step.addError(maker, "{f}:{d}: error: unspecified config header value: {s}", .{ src_path, line_index + 1, name, }); any_errors = true; continue; }, else => { - try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{ - src_path, line_index + 1, @errorName(err), + try step.addError(maker, "{f}:{d}: unable to substitute variable: error: {t}", .{ + src_path, line_index + 1, err, }); any_errors = true; continue; @@ -228,30 +247,23 @@ fn renderAutoconfAt( if (!last_line) try w.writeByte('\n'); } - for (values.entries.slice().items(.key), used) |name, u| { - if (!u) { - try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); - any_errors = true; - } - } - + try ensureAllValuesUsed(maker, step, value_map, src_path); if (any_errors) return error.MakeFailed; } fn renderCmake( + arena: Allocator, + maker: *Maker, step: *Step, contents: []const u8, w: *Writer, - values: std.array_hash_map.String(Value), - src_path: []const u8, + value_pairs: []const Value.Pair, + value_map: *ValueMap, + src_path: Path, ) !void { - const build = step.owner; - const allocator = build.allocator; + const conf = &maker.scanned_config.configuration; - try w.writeAll(c_generated_line); - - var values_copy = try values.clone(allocator); - defer values_copy.deinit(allocator); + try w.writeAll(c_generated_line); var any_errors = false; var line_index: u32 = 0; @@ -259,23 +271,22 @@ fn renderCmake( while (line_it.next()) |raw_line| : (line_index += 1) { const last_line = line_it.index == line_it.buffer.len; - const line = expandVariablesCmake(allocator, raw_line, values) catch |err| switch (err) { + const line = expandVariablesCmake(arena, raw_line, conf, value_pairs, value_map) catch |err| switch (err) { error.InvalidCharacter => { - try step.addError("{s}:{d}: error: invalid character in a variable name", .{ + try step.addError(maker, "{f}:{d}: invalid character in a variable name", .{ src_path, line_index + 1, }); any_errors = true; continue; }, else => { - try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{ - src_path, line_index + 1, @errorName(err), + try step.addError(maker, "{f}:{d}: failed substituting variable: {t}", .{ + src_path, line_index + 1, err, }); any_errors = true; continue; }, }; - defer allocator.free(line); const line_start = std.mem.findNone(u8, line, " \t\r") orelse { try w.writeAll(line); @@ -293,152 +304,134 @@ fn renderCmake( var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r"); const cmakedefine = it.next().?; - if (!std.mem.eql(u8, cmakedefine, "cmakedefine") and - !std.mem.eql(u8, cmakedefine, "cmakedefine01")) - { + + const booldefine = if (std.mem.eql(u8, cmakedefine, "cmakedefine01")) + true + else if (std.mem.eql(u8, cmakedefine, "cmakedefine")) + false + else { try w.writeAll(line); if (!last_line) try w.writeByte('\n'); continue; - } - - const booldefine = std.mem.eql(u8, cmakedefine, "cmakedefine01"); + }; const name = it.next() orelse { - try step.addError("{s}:{d}: error: missing define name", .{ - src_path, line_index + 1, - }); + try step.addError(maker, "{f}:{d}: error: missing define name", .{ src_path, line_index + 1 }); any_errors = true; continue; }; - var value = values_copy.get(name) orelse blk: { - if (booldefine) { - break :blk Value{ .int = 0 }; - } - break :blk Value.undef; + const orig_value: Value.Index = v: { + const index = value_map.getIndex(name) orelse break :v if (booldefine) .int_0 else .undef; + value_map.values()[index] = true; // Mark as used. + break :v value_pairs[index].index; }; - - value = blk: { - switch (value) { - .boolean => |b| { - if (!b) { - break :blk Value.undef; - } - }, - .int => |i| { - if (i == 0) { - break :blk Value.undef; - } - }, - .string => |string| { - if (string.len == 0) { - break :blk Value.undef; - } - }, - - else => {}, - } - break :blk value; + const value = switch (orig_value.unpack(conf)) { + .bool => |b| if (!b) .undef else orig_value, + inline .i64, .u64 => |i| if (i == 0) .undef else orig_value, + .string => |s| if (s.len == 0) .undef else orig_value, + else => orig_value, }; - if (booldefine) { - value = blk: { - switch (value) { - .undef => { - break :blk Value{ .boolean = false }; - }, - .defined => { - break :blk Value{ .boolean = false }; - }, - .boolean => |b| { - break :blk Value{ .boolean = b }; - }, - .int => |i| { - break :blk Value{ .boolean = i != 0 }; - }, - .string => |string| { - break :blk Value{ .boolean = string.len != 0 }; - }, - - else => { - break :blk Value{ .boolean = false }; - }, - } - }; - } else if (value != Value.undef) { - value = Value{ .ident = it.rest() }; - } - try w.writeAll(whitespace_prefix); - try renderValueC(w, name, value); - } - if (any_errors) { - return error.HeaderConfigFailed; + if (booldefine) { + try renderValueCBool(w, name, switch (value.unpack(conf)) { + .undef, .defined => false, + .bool => |b| b, + inline .u64, .i64 => |i| i != 0, + .string => |s| s.len != 0, + .ident => false, + }); + } else if (value != .undef) { + try renderValueCIdent(w, name, it.rest()); + } else { + try renderValueC(conf, w, name, value); + } } + + try ensureAllValuesUsed(maker, step, value_map, src_path); + if (any_errors) return error.MakeFailed; } fn renderBlank( - gpa: std.mem.Allocator, + conf: *const Configuration, w: *Writer, - defines: std.array_hash_map.String(Value), + value_pairs: []const Value.Pair, + value_map: *const ValueMap, 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| { - switch (byte.*) { - 'a'...'z' => byte.* = byte.* - 'a' + 'A', - 'A'...'Z', '0'...'9' => continue, - else => byte.* = '_', - } - } - break :blk name; + const include_guard_fmt: IncludeGuardFmt = .{ + .include_path = include_path, + .override = include_guard_override, }; - defer if (include_guard_override == null) gpa.free(include_guard_name); try w.print( - \\#ifndef {[0]s} - \\#define {[0]s} + \\#ifndef {[0]f} + \\#define {[0]f} \\ - , .{include_guard_name}); + , .{include_guard_fmt}); - const values = defines.values(); - for (defines.keys(), 0..) |name, i| try renderValueC(w, name, values[i]); + for (value_map.keys(), value_pairs) |name, pair| try renderValueC(conf, w, name, pair.index); try w.print( - \\#endif /* {s} */ + \\#endif /* {f} */ \\ - , .{include_guard_name}); + , .{include_guard_fmt}); } -fn renderNasm(w: *Writer, defines: std.array_hash_map.String(Value)) !void { +const IncludeGuardFmt = struct { + include_path: []const u8, + override: ?[]const u8, + + pub fn format(this: @This(), w: *Writer) Writer.Error!void { + if (this.override) |s| return w.writeAll(s); + for (this.include_path) |byte| switch (byte) { + 'a'...'z' => try w.writeByte(byte - 'a' + 'A'), + 'A'...'Z', '0'...'9' => continue, + else => try w.writeByte('_'), + }; + } +}; + +fn renderNasm( + conf: *const Configuration, + w: *Writer, + value_pairs: []const Value.Pair, + value_map: *const ValueMap, +) !void { try w.writeAll(asm_generated_line); - for (defines.keys(), defines.values()) |name, value| try renderValueNasm(w, name, value); + for (value_map.keys(), value_pairs) |name, pair| try renderValueNasm(conf, w, name, pair.index); } -fn renderValueC(w: *Writer, name: []const u8, value: Value) !void { - switch (value) { +fn renderValueC(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void { + switch (value.unpack(conf)) { .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 + .bool => |b| return renderValueCBool(w, name, b), + inline .u64, .i64 => |i| try w.print("#define {s} {d}\n", .{ name, i }), + .ident => |ident| return renderValueCIdent(w, name, ident), .string => |string| try w.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }), } } -fn renderValueNasm(w: *Writer, name: []const u8, value: Value) !void { - switch (value) { +fn renderValueCIdent(w: *Writer, name: []const u8, ident: []const u8) Writer.Error!void { + return w.print("#define {s} {s}\n", .{ name, ident }); +} + +fn renderValueCBool(w: *Writer, name: []const u8, b: bool) Writer.Error!void { + return w.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }); +} + +fn renderValueNasm(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void { + switch (value.unpack(conf)) { .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 }), + .bool => |b| try w.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }), + inline .u64, .i64 => |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 w.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }), } } @@ -446,8 +439,9 @@ fn renderValueNasm(w: *Writer, name: []const u8, value: Value) !void { fn expandVariablesAutoconfAt( w: *Writer, contents: []const u8, - values: *const std.array_hash_map.String(Value), - used: []bool, + conf: *const Configuration, + value_pairs: []const Value.Pair, + value_map: *const ValueMap, ) !void { const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; @@ -467,18 +461,18 @@ fn expandVariablesAutoconfAt( } const key = contents[curr + 1 .. close_pos]; - const index = values.getIndex(key) orelse { + const index = value_map.getIndex(key) orelse { // Report the missing key to the caller. try w.writeAll(key); return error.MissingValue; }; - const value = values.entries.slice().items(.value)[index]; - used[index] = true; + const value = value_pairs[index].index; + value_map.values()[index] = true; // Mark as used. try w.writeAll(contents[source_offset..curr]); - switch (value) { + switch (value.unpack(conf)) { .undef, .defined => {}, - .boolean => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)), - .int => |i| try w.print("{d}", .{i}), + .bool => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)), + inline .u64, .i64 => |i| try w.print("{d}", .{i}), .ident, .string => |s| try w.writeAll(s), } @@ -491,12 +485,13 @@ fn expandVariablesAutoconfAt( } fn expandVariablesCmake( - allocator: Allocator, + arena: Allocator, contents: []const u8, - values: std.array_hash_map.String(Value), + conf: *const Configuration, + value_pairs: []const Value.Pair, + value_map: *const ValueMap, ) ![]const u8 { - var result: std.array_list.Managed(u8) = .init(allocator); - errdefer result.deinit(); + var result: std.ArrayList(u8) = .empty; const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-"; const open_var = "${"; @@ -507,8 +502,7 @@ fn expandVariablesCmake( source: usize, target: usize, }; - var var_stack: std.array_list.Managed(Position) = .init(allocator); - defer var_stack.deinit(); + var var_stack: std.ArrayList(Position) = .empty; loop: while (curr < contents.len) : (curr += 1) { switch (contents[curr]) { '@' => blk: { @@ -524,20 +518,16 @@ fn expandVariablesCmake( } const key = contents[curr + 1 .. close_pos]; - const value = values.get(key) orelse return error.MissingValue; + const index = value_map.getIndex(key) orelse return error.MissingValue; + value_map.values()[index] = true; // Mark as used. + const value = value_pairs[index].index; const missing = contents[source_offset..curr]; - try result.appendSlice(missing); - switch (value) { + try result.appendSlice(arena, missing); + switch (value.unpack(conf)) { .undef, .defined => {}, - .boolean => |b| { - try result.append(if (b) '1' else '0'); - }, - .int => |i| { - try result.print("{d}", .{i}); - }, - .ident, .string => |s| { - try result.appendSlice(s); - }, + .bool => |b| try result.append(arena, if (b) '1' else '0'), + inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}), + .ident, .string => |s| try result.appendSlice(arena, s), } curr = close_pos; @@ -553,12 +543,12 @@ fn expandVariablesCmake( break :blk; } const missing = contents[source_offset..curr]; - try result.appendSlice(missing); - try result.appendSlice(open_var); + try result.appendSlice(arena, missing); + try result.appendSlice(arena, open_var); source_offset = curr + open_var.len; curr = next; - try var_stack.append(Position{ + try var_stack.append(arena, .{ .source = curr, .target = result.items.len - open_var.len, }); @@ -575,26 +565,22 @@ fn expandVariablesCmake( source_offset += open_var.len; } const missing = contents[source_offset..curr]; - try result.appendSlice(missing); + try result.appendSlice(arena, missing); const key_start = open_pos.target + open_var.len; const key = result.items[key_start..]; if (key.len == 0) { return error.MissingKey; } - const value = values.get(key) orelse return error.MissingValue; + const index = value_map.getIndex(key) orelse return error.MissingValue; + value_map.values()[index] = true; // Mark as used. + const value = value_pairs[index].index; result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len); - switch (value) { + switch (value.unpack(conf)) { .undef, .defined => {}, - .boolean => |b| { - try result.append(if (b) '1' else '0'); - }, - .int => |i| { - try result.print("{d}", .{i}); - }, - .ident, .string => |s| { - try result.appendSlice(s); - }, + .bool => |b| try result.append(arena, if (b) '1' else '0'), + inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}), + .ident, .string => |s| try result.appendSlice(arena, s), } source_offset = curr + 1; @@ -615,320 +601,320 @@ fn expandVariablesCmake( if (source_offset != contents.len) { const missing = contents[source_offset..]; - try result.appendSlice(missing); + try result.appendSlice(arena, missing); } - return result.toOwnedSlice(); + try result.shrinkToLen(arena); + + return result.toOwnedSliceAssert(); } fn testReplaceVariablesAutoconfAt( - allocator: Allocator, + arena: Allocator, contents: []const u8, expected: []const u8, - values: std.array_hash_map.String(Value), + value_map: *const ValueMap, ) !void { - var aw: Writer.Allocating = .init(allocator); + var aw: Writer.Allocating = .init(arena); defer aw.deinit(); - const used = try allocator.alloc(bool, values.count()); + const used = try arena.alloc(bool, value_map.count()); for (used) |*u| u.* = false; - defer allocator.free(used); - try expandVariablesAutoconfAt(&aw.writer, contents, values, used); + try expandVariablesAutoconfAt(&aw.writer, contents, value_map, used); for (used) |u| if (!u) return error.UnusedValue; try std.testing.expectEqualStrings(expected, aw.written()); } fn testReplaceVariablesCMake( - allocator: Allocator, + arena: Allocator, contents: []const u8, expected: []const u8, - values: std.array_hash_map.String(Value), + value_map: *const ValueMap, ) !void { - const actual = try expandVariablesCmake(allocator, contents, values); - defer allocator.free(actual); + const actual = try expandVariablesCmake(arena, contents, value_map); try std.testing.expectEqualStrings(expected, actual); } test "expandVariablesAutoconfAt simple cases" { const allocator = std.testing.allocator; - var values: std.array_hash_map.String(Value) = .init(allocator); - defer values.deinit(); + var value_map: ValueMap = .empty; + defer value_map.deinit(); // empty strings are preserved - try testReplaceVariablesAutoconfAt(allocator, "", "", values); + try testReplaceVariablesAutoconfAt(allocator, "", "", value_map); // line with misc content is preserved - try testReplaceVariablesAutoconfAt(allocator, "no substitution", "no substitution", values); + try testReplaceVariablesAutoconfAt(allocator, "no substitution", "no substitution", value_map); // empty @ sigils are preserved - try testReplaceVariablesAutoconfAt(allocator, "@", "@", values); - try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", values); - try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", values); - try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", values); + try testReplaceVariablesAutoconfAt(allocator, "@", "@", value_map); + try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", value_map); + try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", value_map); + try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", value_map); // simple substitution - try values.putNoClobber("undef", .undef); - try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("undef", .undef); + try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", value_map); + value_map.clearRetainingCapacity(); - try values.putNoClobber("defined", .defined); - try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("defined", .defined); + try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", value_map); + value_map.clearRetainingCapacity(); - try values.putNoClobber("true", Value{ .boolean = true }); - try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("true", Value{ .boolean = true }); + try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", value_map); + value_map.clearRetainingCapacity(); - try values.putNoClobber("false", Value{ .boolean = false }); - try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("false", Value{ .boolean = false }); + try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", value_map); + value_map.clearRetainingCapacity(); - try values.putNoClobber("int", Value{ .int = 42 }); - try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("int", Value{ .int = 42 }); + try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", value_map); + value_map.clearRetainingCapacity(); - try values.putNoClobber("ident", Value{ .string = "value" }); - try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("ident", Value{ .string = "value" }); + try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", value_map); + value_map.clearRetainingCapacity(); - try values.putNoClobber("string", Value{ .string = "text" }); - try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("string", Value{ .string = "text" }); + try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", value_map); + value_map.clearRetainingCapacity(); // double packed substitution - try values.putNoClobber("string", Value{ .string = "text" }); - try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("string", Value{ .string = "text" }); + try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", value_map); + value_map.clearRetainingCapacity(); // triple packed substitution - try values.putNoClobber("int", Value{ .int = 42 }); - try values.putNoClobber("string", Value{ .string = "text" }); - try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("int", Value{ .int = 42 }); + try value_map.putNoClobber("string", Value{ .string = "text" }); + try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", value_map); + value_map.clearRetainingCapacity(); // double separated substitution - try values.putNoClobber("int", Value{ .int = 42 }); - try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("int", Value{ .int = 42 }); + try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", value_map); + value_map.clearRetainingCapacity(); // triple separated substitution - try values.putNoClobber("true", Value{ .boolean = true }); - try values.putNoClobber("int", Value{ .int = 42 }); - try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("true", Value{ .boolean = true }); + try value_map.putNoClobber("int", Value{ .int = 42 }); + try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", value_map); + value_map.clearRetainingCapacity(); // misc prefix is preserved - try values.putNoClobber("false", Value{ .boolean = false }); - try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("false", Value{ .boolean = false }); + try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", value_map); + value_map.clearRetainingCapacity(); // misc suffix is preserved - try values.putNoClobber("true", Value{ .boolean = true }); - try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("true", Value{ .boolean = true }); + try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", value_map); + value_map.clearRetainingCapacity(); // surrounding content is preserved - try values.putNoClobber("int", Value{ .int = 42 }); - try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("int", Value{ .int = 42 }); + try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", value_map); + value_map.clearRetainingCapacity(); // incomplete key is preserved - try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", values); + try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", value_map); // unknown key leads to an error - try std.testing.expectError(error.MissingValue, testReplaceVariablesAutoconfAt(allocator, "@bad@", "", values)); + try std.testing.expectError(error.MissingValue, testReplaceVariablesAutoconfAt(allocator, "@bad@", "", value_map)); // unused key leads to an error - try values.putNoClobber("int", Value{ .int = 42 }); - try values.putNoClobber("false", Value{ .boolean = false }); - try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", values)); - values.clearRetainingCapacity(); + try value_map.putNoClobber("int", Value{ .int = 42 }); + try value_map.putNoClobber("false", Value{ .boolean = false }); + try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", value_map)); + value_map.clearRetainingCapacity(); } test "expandVariablesAutoconfAt edge cases" { const allocator = std.testing.allocator; - var values: std.array_hash_map.String(Value) = .init(allocator); - defer values.deinit(); + var value_map: std.array_hash_map.String(Value) = .init(allocator); + defer value_map.deinit(); // @-vars resolved only when they wrap valid characters, otherwise considered literals - try values.putNoClobber("string", Value{ .string = "text" }); - try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("string", Value{ .string = "text" }); + try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", value_map); + value_map.clearRetainingCapacity(); // expanded variables are considered strings after expansion - try values.putNoClobber("string_at", Value{ .string = "@string@" }); - try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", values); - values.clearRetainingCapacity(); + try value_map.putNoClobber("string_at", Value{ .string = "@string@" }); + try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", value_map); + value_map.clearRetainingCapacity(); } test "expandVariablesCmake simple cases" { const allocator = std.testing.allocator; - var values: std.array_hash_map.String(Value) = .init(allocator); - defer values.deinit(); + var value_map: std.array_hash_map.String(Value) = .init(allocator); + defer value_map.deinit(); - try values.putNoClobber("undef", .undef); - try values.putNoClobber("defined", .defined); - try values.putNoClobber("true", Value{ .boolean = true }); - try values.putNoClobber("false", Value{ .boolean = false }); - try values.putNoClobber("int", Value{ .int = 42 }); - try values.putNoClobber("ident", Value{ .string = "value" }); - try values.putNoClobber("string", Value{ .string = "text" }); + try value_map.putNoClobber("undef", .undef); + try value_map.putNoClobber("defined", .defined); + try value_map.putNoClobber("true", Value{ .boolean = true }); + try value_map.putNoClobber("false", Value{ .boolean = false }); + try value_map.putNoClobber("int", Value{ .int = 42 }); + try value_map.putNoClobber("ident", Value{ .string = "value" }); + try value_map.putNoClobber("string", Value{ .string = "text" }); // empty strings are preserved - try testReplaceVariablesCMake(allocator, "", "", values); + try testReplaceVariablesCMake(allocator, "", "", value_map); // line with misc content is preserved - try testReplaceVariablesCMake(allocator, "no substitution", "no substitution", values); + try testReplaceVariablesCMake(allocator, "no substitution", "no substitution", value_map); // empty ${} wrapper leads to an error - try std.testing.expectError(error.MissingKey, testReplaceVariablesCMake(allocator, "${}", "", values)); + try std.testing.expectError(error.MissingKey, testReplaceVariablesCMake(allocator, "${}", "", value_map)); // empty @ sigils are preserved - try testReplaceVariablesCMake(allocator, "@", "@", values); - try testReplaceVariablesCMake(allocator, "@@", "@@", values); - try testReplaceVariablesCMake(allocator, "@@@", "@@@", values); - try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", values); + try testReplaceVariablesCMake(allocator, "@", "@", value_map); + try testReplaceVariablesCMake(allocator, "@@", "@@", value_map); + try testReplaceVariablesCMake(allocator, "@@@", "@@@", value_map); + try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", value_map); // simple substitution - try testReplaceVariablesCMake(allocator, "@undef@", "", values); - try testReplaceVariablesCMake(allocator, "${undef}", "", values); - try testReplaceVariablesCMake(allocator, "@defined@", "", values); - try testReplaceVariablesCMake(allocator, "${defined}", "", values); - try testReplaceVariablesCMake(allocator, "@true@", "1", values); - try testReplaceVariablesCMake(allocator, "${true}", "1", values); - try testReplaceVariablesCMake(allocator, "@false@", "0", values); - try testReplaceVariablesCMake(allocator, "${false}", "0", values); - try testReplaceVariablesCMake(allocator, "@int@", "42", values); - try testReplaceVariablesCMake(allocator, "${int}", "42", values); - try testReplaceVariablesCMake(allocator, "@ident@", "value", values); - try testReplaceVariablesCMake(allocator, "${ident}", "value", values); - try testReplaceVariablesCMake(allocator, "@string@", "text", values); - try testReplaceVariablesCMake(allocator, "${string}", "text", values); + try testReplaceVariablesCMake(allocator, "@undef@", "", value_map); + try testReplaceVariablesCMake(allocator, "${undef}", "", value_map); + try testReplaceVariablesCMake(allocator, "@defined@", "", value_map); + try testReplaceVariablesCMake(allocator, "${defined}", "", value_map); + try testReplaceVariablesCMake(allocator, "@true@", "1", value_map); + try testReplaceVariablesCMake(allocator, "${true}", "1", value_map); + try testReplaceVariablesCMake(allocator, "@false@", "0", value_map); + try testReplaceVariablesCMake(allocator, "${false}", "0", value_map); + try testReplaceVariablesCMake(allocator, "@int@", "42", value_map); + try testReplaceVariablesCMake(allocator, "${int}", "42", value_map); + try testReplaceVariablesCMake(allocator, "@ident@", "value", value_map); + try testReplaceVariablesCMake(allocator, "${ident}", "value", value_map); + try testReplaceVariablesCMake(allocator, "@string@", "text", value_map); + try testReplaceVariablesCMake(allocator, "${string}", "text", value_map); // double packed substitution - try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", values); - try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", values); + try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", value_map); + try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", value_map); // triple packed substitution - try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", values); - try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", values); - try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", values); - try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", values); + try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", value_map); + try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", value_map); + try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", value_map); + try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", value_map); // double separated substitution - try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", values); - try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", values); + try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", value_map); + try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", value_map); // triple separated substitution - try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", values); - try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", values); - try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", values); - try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", values); + try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", value_map); + try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", value_map); + try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", value_map); + try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", value_map); // misc prefix is preserved - try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", values); - try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", values); + try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", value_map); + try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", value_map); // misc suffix is preserved - try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", values); - try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", values); + try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", value_map); + try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", value_map); // surrounding content is preserved - try testReplaceVariablesCMake(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values); - try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", values); + try testReplaceVariablesCMake(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", value_map); + try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", value_map); // incomplete key is preserved - try testReplaceVariablesCMake(allocator, "@undef", "@undef", values); - try testReplaceVariablesCMake(allocator, "${undef", "${undef", values); - try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", values); - try testReplaceVariablesCMake(allocator, "undef@", "undef@", values); - try testReplaceVariablesCMake(allocator, "undef}", "undef}", values); + try testReplaceVariablesCMake(allocator, "@undef", "@undef", value_map); + try testReplaceVariablesCMake(allocator, "${undef", "${undef", value_map); + try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", value_map); + try testReplaceVariablesCMake(allocator, "undef@", "undef@", value_map); + try testReplaceVariablesCMake(allocator, "undef}", "undef}", value_map); // unknown key leads to an error - try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", values)); - try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", values)); + try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", value_map)); + try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", value_map)); } test "expandVariablesCmake edge cases" { const allocator = std.testing.allocator; - var values: std.array_hash_map.String(Value) = .init(allocator); - defer values.deinit(); + var value_map: std.array_hash_map.String(Value) = .init(allocator); + defer value_map.deinit(); // special symbols - try values.putNoClobber("at", Value{ .string = "@" }); - try values.putNoClobber("dollar", Value{ .string = "$" }); - try values.putNoClobber("underscore", Value{ .string = "_" }); + try value_map.putNoClobber("at", Value{ .string = "@" }); + try value_map.putNoClobber("dollar", Value{ .string = "$" }); + try value_map.putNoClobber("underscore", Value{ .string = "_" }); // basic value - try values.putNoClobber("string", Value{ .string = "text" }); + try value_map.putNoClobber("string", Value{ .string = "text" }); - // proxy case values - try values.putNoClobber("string_proxy", Value{ .string = "string" }); - try values.putNoClobber("string_at", Value{ .string = "@string@" }); - try values.putNoClobber("string_curly", Value{ .string = "{string}" }); - try values.putNoClobber("string_var", Value{ .string = "${string}" }); + // proxy case value_map + try value_map.putNoClobber("string_proxy", Value{ .string = "string" }); + try value_map.putNoClobber("string_at", Value{ .string = "@string@" }); + try value_map.putNoClobber("string_curly", Value{ .string = "{string}" }); + try value_map.putNoClobber("string_var", Value{ .string = "${string}" }); - // stack case values - try values.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" }); - try values.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" }); + // stack case value_map + try value_map.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" }); + try value_map.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" }); // @-vars resolved only when they wrap valid characters, otherwise considered literals - try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", values); - try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", values); + try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", value_map); + try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", value_map); // @-vars are resolved inside ${}-vars - try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", values); + try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", value_map); // expanded variables are considered strings after expansion - try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", values); - try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", values); - try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", values); - try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", values); - try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", values); - try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", values); - try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", values); - try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", values); - try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", values); + try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", value_map); + try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", value_map); + try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", value_map); + try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", value_map); + try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", value_map); + try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", value_map); + try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", value_map); + try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", value_map); + try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", value_map); // when expanded variables contain invalid characters, they prevent further expansion - try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", values)); - try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", values)); + try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", value_map)); + try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", value_map)); // nested expanded variables are expanded from the inside out - try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", values); - try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", values); + try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", value_map); + try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", value_map); // nested vars are only expanded when ${} is closed - try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", values)); - try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", values); - try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", values)); - try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", values); + try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", value_map)); + try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", value_map); + try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", value_map)); + try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", value_map); // invalid characters lead to an error - try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", values)); - try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", values)); - try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", values)); + try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", value_map)); + try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", value_map)); + try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", value_map)); } test "expandVariablesCmake escaped characters" { const allocator = std.testing.allocator; - var values: std.array_hash_map.String(Value) = .init(allocator); - defer values.deinit(); + var value_map: std.array_hash_map.String(Value) = .init(allocator); + defer value_map.deinit(); - try values.putNoClobber("string", Value{ .string = "text" }); + try value_map.putNoClobber("string", Value{ .string = "text" }); // backslash is an invalid character for @ lookup - try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", values); + try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", value_map); // backslash is preserved, but doesn't affect ${} variable expansion - try testReplaceVariablesCMake(allocator, "\\${string}", "\\text", values); + try testReplaceVariablesCMake(allocator, "\\${string}", "\\text", value_map); // backslash breaks ${} opening bracket identification - try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", values); + try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", value_map); // backslash is skipped when checking for invalid characters, yet it mangles the key - try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${string\\}", "", values)); + try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${string\\}", "", value_map)); } diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index d19a357b50571ceb93b455e38a590241957414a7..5040a5f619f8ca868b648a99cc33c34d25e7b9ed 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -1117,6 +1117,38 @@ pub const Step = extern struct { undef = max_u32 - 1, defined = max_u32, _, + + pub fn unpack(this: @This(), c: *const Configuration) Unpacked { + return switch (this) { + .int_0 => .{ .u64 = 0 }, + .int_1 => .{ .u64 = 1 }, + .bool_false => .{ .bool = false }, + .bool_true => .{ .bool = true }, + .undef => .undef, + .defined => .defined, + _ => { + const value = extraData(c, Value, @intFromEnum(this)); + return switch (value.flags.tag) { + .ident => .{ .ident = value.ident.value.?.slice(c) }, + .string => .{ .string = value.string.value.?.slice(c) }, + .small_unsigned => .{ .u64 = value.flags.small }, + .small_signed => .{ .i64 = @as(i29, @bitCast(value.flags.small)) }, + .i64 => .{ .i64 = value.i64.value.? }, + .u64 => .{ .u64 = value.u64.value.? }, + }; + }, + }; + } + }; + + pub const Unpacked = union(enum) { + bool: bool, + undef, + defined, + i64: i64, + u64: u64, + ident: []const u8, + string: []const u8, }; pub fn initSigned(x: i64) @This() { diff --git a/test/standalone/cmakedefine/build.zig b/test/standalone/cmakedefine/build.zig index a7a395b00c1a3c17495161185cd8118b4c839636..78fe1f1cbfc9b2ec07dba6dea6fe808190e37c7d 100644 --- a/test/standalone/cmakedefine/build.zig +++ b/test/standalone/cmakedefine/build.zig @@ -48,7 +48,6 @@ pub fn build(b: *std.Build) void { .include_path = "stack.h", }, .{ - .AT = "@", .UNDERSCORE = "_", .NEST_UNDERSCORE_PROXY = "UNDERSCORE", .NEST_PROXY = "NEST_UNDERSCORE_PROXY", -- 2.54.0