authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-23 00:40:44-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:36-07:00
log9989f72c61e4c4e98c69f6191a63c201336a6a19
tree05aedeee0e0fca225b54b473b87ee782508ce70c
parente435299cfa6a6bd446483e6c95a56bf1cdca8cfd

Maker: implement ConfigHeader


3 files changed, 415 insertions(+), 398 deletions(-)

lib/compiler/Maker/Step/ConfigHeader.zig+383-397
......@@ -10,9 +10,13 @@ const Allocator = std.mem.Allocator;
1010const Step = @import("../Step.zig");
1111const Maker = @import("../../Maker.zig");
1212
13 const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
14 const c_generated_line = "/* " ++ header_text ++ " */\n";
15 const asm_generated_line = "; " ++ header_text ++ "\n";
13const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
14const c_generated_line = "/* " ++ header_text ++ " */\n";
15const asm_generated_line = "; " ++ header_text ++ "\n";
16
17/// Table value is whether the value is used.
18const ValueMap = std.array_hash_map.String(bool);
19const Value = Configuration.Step.ConfigHeader.Value;
1620
1721pub fn make(
1822 config_header: *ConfigHeader,
......@@ -23,7 +27,6 @@ pub fn make(
2327 _ = config_header;
2428 _ = progress_node;
2529 const graph = maker.graph;
26 const gpa = maker.gpa;
2730 const step = maker.stepByIndex(step_index);
2831 const io = graph.io;
2932 const arena = graph.arena; // TODO don't leak into the process arena
......@@ -32,8 +35,20 @@ pub fn make(
3235 const conf_ch = conf_step.extended.get(conf.extra).config_header;
3336 const cache_root = graph.local_cache_root;
3437
35 if (conf_ch.style.getPath()) |lp|
36 try step.singleUnchangingWatchInput(maker, arena, lp);
38 const input_size_limit: Io.Limit = if (conf_ch.input_size_limit.value) |x| .limited64(x) else .unlimited;
39 const include_guard_override: ?[]const u8 = if (conf_ch.include_guard.value) |s| s.slice(conf) else null;
40 const include_path: []const u8 = conf_ch.include_path.slice(conf);
41 const template_file = if (conf_ch.template_file.value) |lp|
42 try maker.resolveLazyPathIndex(arena, lp, step_index)
43 else
44 null;
45 const value_pairs = conf_ch.values.slice;
46
47 if (conf_ch.template_file.value) |lp| try step.singleUnchangingWatchInput(maker, arena, lp.get(conf));
48
49 var value_map: ValueMap = .empty;
50 try value_map.ensureTotalCapacity(arena, value_pairs.len);
51 for (value_pairs) |pair| value_map.putAssumeCapacityNoClobber(pair.key.slice(conf), false);
3752
3853 var man = graph.cache.obtain();
3954 defer man.deinit();
......@@ -42,48 +57,49 @@ pub fn make(
4257 // random bytes when ConfigHeader implementation is modified in a
4358 // non-backwards-compatible way.
4459 man.hash.add(@as(u32, 0xdef08d23));
45 man.hash.addBytes(conf_ch.include_path);
46 man.hash.addOptionalBytes(conf_ch.include_guard_override);
60 man.hash.add(@as(u32, @bitCast(conf_ch.flags)));
61 man.hash.addBytes(include_path);
62 man.hash.addOptionalBytes(include_guard_override);
4763
4864 var aw: Writer.Allocating = .init(arena);
4965 defer aw.deinit();
5066
5167 switch (conf_ch.flags.style) {
5268 .autoconf_undef => {
53 const src_path = try maker.resolveLazyPathIndex(arena, conf_ch.template_file.value.?, step_index);
54 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(conf_ch.max_bytes)) catch |err|
55 return step.fail("unable to read autoconf input file {s}: {t}", .{ src_path, err });
56 renderAutoConfUndef(step, contents, &aw.writer, &conf_ch.values, src_path) catch |err| switch (err) {
69 const tf = template_file.?;
70 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
71 return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
72 renderAutoConfUndef(maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
5773 error.WriteFailed => return error.OutOfMemory,
5874 else => |e| return e,
5975 };
6076 },
6177 .autoconf_at => {
62 const src_path = try maker.resolveLazyPathIndex(arena, conf_ch.template_file.value.?, step_index);
63 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(conf_ch.max_bytes)) catch |err|
64 return step.fail("unable to read autoconf input file {s}: {t}", .{ src_path, err });
65 renderAutoconfAt(step, contents, &aw, &conf_ch.values, src_path) catch |err| switch (err) {
78 const tf = template_file.?;
79 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
80 return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
81 renderAutoconfAt(maker, step, contents, &aw, value_pairs, &value_map, tf) catch |err| switch (err) {
6682 error.WriteFailed => return error.OutOfMemory,
6783 else => |e| return e,
6884 };
6985 },
7086 .cmake => {
71 const src_path = try maker.resolveLazyPathIndex(arena, conf_ch.template_file.value.?, step_index);
72 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(conf_ch.max_bytes)) catch |err|
73 return step.fail("unable to read cmake input file {s}: {t}", .{ src_path, err });
74 renderCmake(step, contents, &aw.writer, conf_ch.values, src_path) catch |err| switch (err) {
87 const tf = template_file.?;
88 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
89 return step.fail(maker, "unable to read cmake input file {f}: {t}", .{ tf, err });
90 renderCmake(arena, maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
7591 error.WriteFailed => return error.OutOfMemory,
7692 else => |e| return e,
7793 };
7894 },
7995 .blank => {
80 renderBlank(gpa, &aw.writer, conf_ch.values, conf_ch.include_path, conf_ch.include_guard_override) catch |err| switch (err) {
96 renderBlank(conf, &aw.writer, value_pairs, &value_map, include_path, include_guard_override) catch |err| switch (err) {
8197 error.WriteFailed => return error.OutOfMemory,
8298 else => |e| return e,
8399 };
84100 },
85101 .nasm => {
86 renderNasm(&aw.writer, conf_ch.values) catch |err| switch (err) {
102 renderNasm(conf, &aw.writer, value_pairs, &value_map) catch |err| switch (err) {
87103 error.WriteFailed => return error.OutOfMemory,
88104 else => |e| return e,
89105 };
......@@ -93,9 +109,9 @@ pub fn make(
93109 const output = aw.written();
94110 man.hash.addBytes(output);
95111
96 if (try step.cacheHit(&man)) {
112 if (try step.cacheHit(maker, &man)) {
97113 const digest = man.final();
98 maker.generatedPath().* = .{
114 maker.generatedPath(conf_ch.generated_dir).* = .{
99115 .root_dir = cache_root,
100116 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
101117 };
......@@ -116,12 +132,12 @@ pub fn make(
116132 const out_path_dirname = out_path.dirname().?;
117133
118134 out_path_dirname.root_dir.handle.createDirPath(io, out_path_dirname.sub_path) catch |err|
119 return step.fail("unable to make path {f}: {t}", .{ out_path_dirname, err });
135 return step.fail(maker, "unable to make path {f}: {t}", .{ out_path_dirname, err });
120136
121137 out_path.root_dir.handle.writeFile(io, .{ .sub_path = out_path.sub_path, .data = output }) catch |err|
122 return step.fail("unable to write file {f}: {t}", .{ out_path, err });
138 return step.fail(maker, "unable to write file {f}: {t}", .{ out_path, err });
123139
124 maker.generatedPath().* = .{
140 maker.generatedPath(conf_ch.generated_dir).* = .{
125141 .root_dir = cache_root,
126142 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
127143 };
......@@ -129,21 +145,34 @@ pub fn make(
129145 try step.writeManifest(maker, &man);
130146}
131147
148fn ensureAllValuesUsed(
149 maker: *Maker,
150 step: *Step,
151 value_map: *const ValueMap,
152 src_path: Path,
153) Step.ExtendedMakeError!void {
154 var any_errors = false;
155 for (value_map.keys(), value_map.values()) |name, used| {
156 if (used) continue;
157 try step.addError(maker, "{f}: config header value unused: {s}", .{ src_path, name });
158 any_errors = true;
159 }
160 if (any_errors) return error.MakeFailed;
161}
162
132163fn renderAutoConfUndef(
164 maker: *Maker,
133165 step: *Step,
134166 contents: []const u8,
135167 w: *Writer,
136 values: *const std.array_hash_map.String(Value),
137 src_path: []const u8,
168 value_pairs: []const Value.Pair,
169 value_map: *ValueMap,
170 src_path: Path,
138171) !void {
139 const build = step.owner;
140 const allocator = build.allocator;
172 const conf = &maker.scanned_config.configuration;
141173
142174 try w.writeAll(c_generated_line);
143175
144 var is_used: std.bit_set.Dynamic = try .initEmpty(allocator, values.count());
145 defer is_used.deinit(allocator);
146
147176 var any_errors = false;
148177 var line_index: u32 = 0;
149178 var line_it = std.mem.splitScalar(u8, contents, '\n');
......@@ -161,45 +190,35 @@ fn renderAutoConfUndef(
161190 continue;
162191 }
163192 const name = it.next().?;
164 const index = values.getIndex(name) orelse {
165 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
193 const index = value_map.getIndex(name) orelse {
194 try step.addError(maker, "{f}:{d}: unspecified config header value: {s}", .{
166195 src_path, line_index + 1, name,
167196 });
168197 any_errors = true;
169198 continue;
170199 };
171 is_used.set(index);
172 try renderValueC(w, name, values.values()[index]);
173 }
174
175 var unused_value_it = is_used.iterator(.{ .kind = .unset });
176 while (unused_value_it.next()) |index| {
177 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, values.keys()[index] });
178 any_errors = true;
200 value_map.values()[index] = true; // Set to used.
201 try renderValueC(conf, w, name, value_pairs[index].index);
179202 }
180203
181 if (any_errors) {
182 return error.MakeFailed;
183 }
204 try ensureAllValuesUsed(maker, step, value_map, src_path);
205 if (any_errors) return error.MakeFailed;
184206}
185207
186208fn renderAutoconfAt(
209 maker: *Maker,
187210 step: *Step,
188211 contents: []const u8,
189212 aw: *Writer.Allocating,
190 values: *const std.array_hash_map.String(Value),
191 src_path: []const u8,
213 value_pairs: []const Value.Pair,
214 value_map: *const ValueMap,
215 src_path: Path,
192216) !void {
193 const build = step.owner;
194 const allocator = build.allocator;
195217 const w = &aw.writer;
218 const conf = &maker.scanned_config.configuration;
196219
197220 try w.writeAll(c_generated_line);
198221
199 const used = allocator.alloc(bool, values.count()) catch @panic("OOM");
200 for (used) |*u| u.* = false;
201 defer allocator.free(used);
202
203222 var any_errors = false;
204223 var line_index: u32 = 0;
205224 var line_it = std.mem.splitScalar(u8, contents, '\n');
......@@ -207,19 +226,19 @@ fn renderAutoconfAt(
207226 const last_line = line_it.index == line_it.buffer.len;
208227
209228 const old_len = aw.written().len;
210 expandVariablesAutoconfAt(w, line, values, used) catch |err| switch (err) {
229 expandVariablesAutoconfAt(w, line, conf, value_pairs, value_map) catch |err| switch (err) {
211230 error.MissingValue => {
212231 const name = aw.written()[old_len..];
213232 defer aw.shrinkRetainingCapacity(old_len);
214 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
233 try step.addError(maker, "{f}:{d}: error: unspecified config header value: {s}", .{
215234 src_path, line_index + 1, name,
216235 });
217236 any_errors = true;
218237 continue;
219238 },
220239 else => {
221 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{
222 src_path, line_index + 1, @errorName(err),
240 try step.addError(maker, "{f}:{d}: unable to substitute variable: error: {t}", .{
241 src_path, line_index + 1, err,
223242 });
224243 any_errors = true;
225244 continue;
......@@ -228,30 +247,23 @@ fn renderAutoconfAt(
228247 if (!last_line) try w.writeByte('\n');
229248 }
230249
231 for (values.entries.slice().items(.key), used) |name, u| {
232 if (!u) {
233 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
234 any_errors = true;
235 }
236 }
237
250 try ensureAllValuesUsed(maker, step, value_map, src_path);
238251 if (any_errors) return error.MakeFailed;
239252}
240253
241254fn renderCmake(
255 arena: Allocator,
256 maker: *Maker,
242257 step: *Step,
243258 contents: []const u8,
244259 w: *Writer,
245 values: std.array_hash_map.String(Value),
246 src_path: []const u8,
260 value_pairs: []const Value.Pair,
261 value_map: *ValueMap,
262 src_path: Path,
247263) !void {
248 const build = step.owner;
249 const allocator = build.allocator;
250
251 try w.writeAll(c_generated_line);
264 const conf = &maker.scanned_config.configuration;
252265
253 var values_copy = try values.clone(allocator);
254 defer values_copy.deinit(allocator);
266 try w.writeAll(c_generated_line);
255267
256268 var any_errors = false;
257269 var line_index: u32 = 0;
......@@ -259,23 +271,22 @@ fn renderCmake(
259271 while (line_it.next()) |raw_line| : (line_index += 1) {
260272 const last_line = line_it.index == line_it.buffer.len;
261273
262 const line = expandVariablesCmake(allocator, raw_line, values) catch |err| switch (err) {
274 const line = expandVariablesCmake(arena, raw_line, conf, value_pairs, value_map) catch |err| switch (err) {
263275 error.InvalidCharacter => {
264 try step.addError("{s}:{d}: error: invalid character in a variable name", .{
276 try step.addError(maker, "{f}:{d}: invalid character in a variable name", .{
265277 src_path, line_index + 1,
266278 });
267279 any_errors = true;
268280 continue;
269281 },
270282 else => {
271 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{
272 src_path, line_index + 1, @errorName(err),
283 try step.addError(maker, "{f}:{d}: failed substituting variable: {t}", .{
284 src_path, line_index + 1, err,
273285 });
274286 any_errors = true;
275287 continue;
276288 },
277289 };
278 defer allocator.free(line);
279290
280291 const line_start = std.mem.findNone(u8, line, " \t\r") orelse {
281292 try w.writeAll(line);
......@@ -293,152 +304,134 @@ fn renderCmake(
293304
294305 var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r");
295306 const cmakedefine = it.next().?;
296 if (!std.mem.eql(u8, cmakedefine, "cmakedefine") and
297 !std.mem.eql(u8, cmakedefine, "cmakedefine01"))
298 {
307
308 const booldefine = if (std.mem.eql(u8, cmakedefine, "cmakedefine01"))
309 true
310 else if (std.mem.eql(u8, cmakedefine, "cmakedefine"))
311 false
312 else {
299313 try w.writeAll(line);
300314 if (!last_line) try w.writeByte('\n');
301315 continue;
302 }
303
304 const booldefine = std.mem.eql(u8, cmakedefine, "cmakedefine01");
316 };
305317
306318 const name = it.next() orelse {
307 try step.addError("{s}:{d}: error: missing define name", .{
308 src_path, line_index + 1,
309 });
319 try step.addError(maker, "{f}:{d}: error: missing define name", .{ src_path, line_index + 1 });
310320 any_errors = true;
311321 continue;
312322 };
313 var value = values_copy.get(name) orelse blk: {
314 if (booldefine) {
315 break :blk Value{ .int = 0 };
316 }
317 break :blk Value.undef;
323 const orig_value: Value.Index = v: {
324 const index = value_map.getIndex(name) orelse break :v if (booldefine) .int_0 else .undef;
325 value_map.values()[index] = true; // Mark as used.
326 break :v value_pairs[index].index;
318327 };
319
320 value = blk: {
321 switch (value) {
322 .boolean => |b| {
323 if (!b) {
324 break :blk Value.undef;
325 }
326 },
327 .int => |i| {
328 if (i == 0) {
329 break :blk Value.undef;
330 }
331 },
332 .string => |string| {
333 if (string.len == 0) {
334 break :blk Value.undef;
335 }
336 },
337
338 else => {},
339 }
340 break :blk value;
328 const value = switch (orig_value.unpack(conf)) {
329 .bool => |b| if (!b) .undef else orig_value,
330 inline .i64, .u64 => |i| if (i == 0) .undef else orig_value,
331 .string => |s| if (s.len == 0) .undef else orig_value,
332 else => orig_value,
341333 };
342334
335 try w.writeAll(whitespace_prefix);
336
343337 if (booldefine) {
344 value = blk: {
345 switch (value) {
346 .undef => {
347 break :blk Value{ .boolean = false };
348 },
349 .defined => {
350 break :blk Value{ .boolean = false };
351 },
352 .boolean => |b| {
353 break :blk Value{ .boolean = b };
354 },
355 .int => |i| {
356 break :blk Value{ .boolean = i != 0 };
357 },
358 .string => |string| {
359 break :blk Value{ .boolean = string.len != 0 };
360 },
361
362 else => {
363 break :blk Value{ .boolean = false };
364 },
365 }
366 };
367 } else if (value != Value.undef) {
368 value = Value{ .ident = it.rest() };
338 try renderValueCBool(w, name, switch (value.unpack(conf)) {
339 .undef, .defined => false,
340 .bool => |b| b,
341 inline .u64, .i64 => |i| i != 0,
342 .string => |s| s.len != 0,
343 .ident => false,
344 });
345 } else if (value != .undef) {
346 try renderValueCIdent(w, name, it.rest());
347 } else {
348 try renderValueC(conf, w, name, value);
369349 }
370
371 try w.writeAll(whitespace_prefix);
372 try renderValueC(w, name, value);
373350 }
374351
375 if (any_errors) {
376 return error.HeaderConfigFailed;
377 }
352 try ensureAllValuesUsed(maker, step, value_map, src_path);
353 if (any_errors) return error.MakeFailed;
378354}
379355
380356fn renderBlank(
381 gpa: std.mem.Allocator,
357 conf: *const Configuration,
382358 w: *Writer,
383 defines: std.array_hash_map.String(Value),
359 value_pairs: []const Value.Pair,
360 value_map: *const ValueMap,
384361 include_path: []const u8,
385362 include_guard_override: ?[]const u8,
386363) !void {
387364 try w.writeAll(c_generated_line);
388365
389 const include_guard_name = include_guard_override orelse blk: {
390 const name = try gpa.dupe(u8, include_path);
391 for (name) |*byte| {
392 switch (byte.*) {
393 'a'...'z' => byte.* = byte.* - 'a' + 'A',
394 'A'...'Z', '0'...'9' => continue,
395 else => byte.* = '_',
396 }
397 }
398 break :blk name;
366 const include_guard_fmt: IncludeGuardFmt = .{
367 .include_path = include_path,
368 .override = include_guard_override,
399369 };
400 defer if (include_guard_override == null) gpa.free(include_guard_name);
401370
402371 try w.print(
403 \\#ifndef {[0]s}
404 \\#define {[0]s}
372 \\#ifndef {[0]f}
373 \\#define {[0]f}
405374 \\
406 , .{include_guard_name});
375 , .{include_guard_fmt});
407376
408 const values = defines.values();
409 for (defines.keys(), 0..) |name, i| try renderValueC(w, name, values[i]);
377 for (value_map.keys(), value_pairs) |name, pair| try renderValueC(conf, w, name, pair.index);
410378
411379 try w.print(
412 \\#endif /* {s} */
380 \\#endif /* {f} */
413381 \\
414 , .{include_guard_name});
382 , .{include_guard_fmt});
415383}
416384
417fn renderNasm(w: *Writer, defines: std.array_hash_map.String(Value)) !void {
385const IncludeGuardFmt = struct {
386 include_path: []const u8,
387 override: ?[]const u8,
388
389 pub fn format(this: @This(), w: *Writer) Writer.Error!void {
390 if (this.override) |s| return w.writeAll(s);
391 for (this.include_path) |byte| switch (byte) {
392 'a'...'z' => try w.writeByte(byte - 'a' + 'A'),
393 'A'...'Z', '0'...'9' => continue,
394 else => try w.writeByte('_'),
395 };
396 }
397};
398
399fn renderNasm(
400 conf: *const Configuration,
401 w: *Writer,
402 value_pairs: []const Value.Pair,
403 value_map: *const ValueMap,
404) !void {
418405 try w.writeAll(asm_generated_line);
419 for (defines.keys(), defines.values()) |name, value| try renderValueNasm(w, name, value);
406 for (value_map.keys(), value_pairs) |name, pair| try renderValueNasm(conf, w, name, pair.index);
420407}
421408
422fn renderValueC(w: *Writer, name: []const u8, value: Value) !void {
423 switch (value) {
409fn renderValueC(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void {
410 switch (value.unpack(conf)) {
424411 .undef => try w.print("/* #undef {s} */\n", .{name}),
425412 .defined => try w.print("#define {s}\n", .{name}),
426 .boolean => |b| try w.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
427 .int => |i| try w.print("#define {s} {d}\n", .{ name, i }),
428 .ident => |ident| try w.print("#define {s} {s}\n", .{ name, ident }),
429 // TODO: use C-specific escaping instead of zig string literals
413 .bool => |b| return renderValueCBool(w, name, b),
414 inline .u64, .i64 => |i| try w.print("#define {s} {d}\n", .{ name, i }),
415 .ident => |ident| return renderValueCIdent(w, name, ident),
430416 .string => |string| try w.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
431417 }
432418}
433419
434fn renderValueNasm(w: *Writer, name: []const u8, value: Value) !void {
435 switch (value) {
420fn renderValueCIdent(w: *Writer, name: []const u8, ident: []const u8) Writer.Error!void {
421 return w.print("#define {s} {s}\n", .{ name, ident });
422}
423
424fn renderValueCBool(w: *Writer, name: []const u8, b: bool) Writer.Error!void {
425 return w.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) });
426}
427
428fn renderValueNasm(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void {
429 switch (value.unpack(conf)) {
436430 .undef => try w.print("; %undef {s}\n", .{name}),
437431 .defined => try w.print("%define {s}\n", .{name}),
438 .boolean => |b| try w.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
439 .int => |i| try w.print("%define {s} {d}\n", .{ name, i }),
432 .bool => |b| try w.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
433 inline .u64, .i64 => |i| try w.print("%define {s} {d}\n", .{ name, i }),
440434 .ident => |ident| try w.print("%define {s} {s}\n", .{ name, ident }),
441 // TODO: use nasm-specific escaping instead of zig string literals
442435 .string => |string| try w.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
443436 }
444437}
......@@ -446,8 +439,9 @@ fn renderValueNasm(w: *Writer, name: []const u8, value: Value) !void {
446439fn expandVariablesAutoconfAt(
447440 w: *Writer,
448441 contents: []const u8,
449 values: *const std.array_hash_map.String(Value),
450 used: []bool,
442 conf: *const Configuration,
443 value_pairs: []const Value.Pair,
444 value_map: *const ValueMap,
451445) !void {
452446 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
453447
......@@ -467,18 +461,18 @@ fn expandVariablesAutoconfAt(
467461 }
468462
469463 const key = contents[curr + 1 .. close_pos];
470 const index = values.getIndex(key) orelse {
464 const index = value_map.getIndex(key) orelse {
471465 // Report the missing key to the caller.
472466 try w.writeAll(key);
473467 return error.MissingValue;
474468 };
475 const value = values.entries.slice().items(.value)[index];
476 used[index] = true;
469 const value = value_pairs[index].index;
470 value_map.values()[index] = true; // Mark as used.
477471 try w.writeAll(contents[source_offset..curr]);
478 switch (value) {
472 switch (value.unpack(conf)) {
479473 .undef, .defined => {},
480 .boolean => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)),
481 .int => |i| try w.print("{d}", .{i}),
474 .bool => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)),
475 inline .u64, .i64 => |i| try w.print("{d}", .{i}),
482476 .ident, .string => |s| try w.writeAll(s),
483477 }
484478
......@@ -491,12 +485,13 @@ fn expandVariablesAutoconfAt(
491485}
492486
493487fn expandVariablesCmake(
494 allocator: Allocator,
488 arena: Allocator,
495489 contents: []const u8,
496 values: std.array_hash_map.String(Value),
490 conf: *const Configuration,
491 value_pairs: []const Value.Pair,
492 value_map: *const ValueMap,
497493) ![]const u8 {
498 var result: std.array_list.Managed(u8) = .init(allocator);
499 errdefer result.deinit();
494 var result: std.ArrayList(u8) = .empty;
500495
501496 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";
502497 const open_var = "${";
......@@ -507,8 +502,7 @@ fn expandVariablesCmake(
507502 source: usize,
508503 target: usize,
509504 };
510 var var_stack: std.array_list.Managed(Position) = .init(allocator);
511 defer var_stack.deinit();
505 var var_stack: std.ArrayList(Position) = .empty;
512506 loop: while (curr < contents.len) : (curr += 1) {
513507 switch (contents[curr]) {
514508 '@' => blk: {
......@@ -524,20 +518,16 @@ fn expandVariablesCmake(
524518 }
525519
526520 const key = contents[curr + 1 .. close_pos];
527 const value = values.get(key) orelse return error.MissingValue;
521 const index = value_map.getIndex(key) orelse return error.MissingValue;
522 value_map.values()[index] = true; // Mark as used.
523 const value = value_pairs[index].index;
528524 const missing = contents[source_offset..curr];
529 try result.appendSlice(missing);
530 switch (value) {
525 try result.appendSlice(arena, missing);
526 switch (value.unpack(conf)) {
531527 .undef, .defined => {},
532 .boolean => |b| {
533 try result.append(if (b) '1' else '0');
534 },
535 .int => |i| {
536 try result.print("{d}", .{i});
537 },
538 .ident, .string => |s| {
539 try result.appendSlice(s);
540 },
528 .bool => |b| try result.append(arena, if (b) '1' else '0'),
529 inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
530 .ident, .string => |s| try result.appendSlice(arena, s),
541531 }
542532
543533 curr = close_pos;
......@@ -553,12 +543,12 @@ fn expandVariablesCmake(
553543 break :blk;
554544 }
555545 const missing = contents[source_offset..curr];
556 try result.appendSlice(missing);
557 try result.appendSlice(open_var);
546 try result.appendSlice(arena, missing);
547 try result.appendSlice(arena, open_var);
558548
559549 source_offset = curr + open_var.len;
560550 curr = next;
561 try var_stack.append(Position{
551 try var_stack.append(arena, .{
562552 .source = curr,
563553 .target = result.items.len - open_var.len,
564554 });
......@@ -575,26 +565,22 @@ fn expandVariablesCmake(
575565 source_offset += open_var.len;
576566 }
577567 const missing = contents[source_offset..curr];
578 try result.appendSlice(missing);
568 try result.appendSlice(arena, missing);
579569
580570 const key_start = open_pos.target + open_var.len;
581571 const key = result.items[key_start..];
582572 if (key.len == 0) {
583573 return error.MissingKey;
584574 }
585 const value = values.get(key) orelse return error.MissingValue;
575 const index = value_map.getIndex(key) orelse return error.MissingValue;
576 value_map.values()[index] = true; // Mark as used.
577 const value = value_pairs[index].index;
586578 result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len);
587 switch (value) {
579 switch (value.unpack(conf)) {
588580 .undef, .defined => {},
589 .boolean => |b| {
590 try result.append(if (b) '1' else '0');
591 },
592 .int => |i| {
593 try result.print("{d}", .{i});
594 },
595 .ident, .string => |s| {
596 try result.appendSlice(s);
597 },
581 .bool => |b| try result.append(arena, if (b) '1' else '0'),
582 inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
583 .ident, .string => |s| try result.appendSlice(arena, s),
598584 }
599585
600586 source_offset = curr + 1;
......@@ -615,320 +601,320 @@ fn expandVariablesCmake(
615601
616602 if (source_offset != contents.len) {
617603 const missing = contents[source_offset..];
618 try result.appendSlice(missing);
604 try result.appendSlice(arena, missing);
619605 }
620606
621 return result.toOwnedSlice();
607 try result.shrinkToLen(arena);
608
609 return result.toOwnedSliceAssert();
622610}
623611
624612fn testReplaceVariablesAutoconfAt(
625 allocator: Allocator,
613 arena: Allocator,
626614 contents: []const u8,
627615 expected: []const u8,
628 values: std.array_hash_map.String(Value),
616 value_map: *const ValueMap,
629617) !void {
630 var aw: Writer.Allocating = .init(allocator);
618 var aw: Writer.Allocating = .init(arena);
631619 defer aw.deinit();
632620
633 const used = try allocator.alloc(bool, values.count());
621 const used = try arena.alloc(bool, value_map.count());
634622 for (used) |*u| u.* = false;
635 defer allocator.free(used);
636623
637 try expandVariablesAutoconfAt(&aw.writer, contents, values, used);
624 try expandVariablesAutoconfAt(&aw.writer, contents, value_map, used);
638625
639626 for (used) |u| if (!u) return error.UnusedValue;
640627 try std.testing.expectEqualStrings(expected, aw.written());
641628}
642629
643630fn testReplaceVariablesCMake(
644 allocator: Allocator,
631 arena: Allocator,
645632 contents: []const u8,
646633 expected: []const u8,
647 values: std.array_hash_map.String(Value),
634 value_map: *const ValueMap,
648635) !void {
649 const actual = try expandVariablesCmake(allocator, contents, values);
650 defer allocator.free(actual);
636 const actual = try expandVariablesCmake(arena, contents, value_map);
651637
652638 try std.testing.expectEqualStrings(expected, actual);
653639}
654640
655641test "expandVariablesAutoconfAt simple cases" {
656642 const allocator = std.testing.allocator;
657 var values: std.array_hash_map.String(Value) = .init(allocator);
658 defer values.deinit();
643 var value_map: ValueMap = .empty;
644 defer value_map.deinit();
659645
660646 // empty strings are preserved
661 try testReplaceVariablesAutoconfAt(allocator, "", "", values);
647 try testReplaceVariablesAutoconfAt(allocator, "", "", value_map);
662648
663649 // line with misc content is preserved
664 try testReplaceVariablesAutoconfAt(allocator, "no substitution", "no substitution", values);
650 try testReplaceVariablesAutoconfAt(allocator, "no substitution", "no substitution", value_map);
665651
666652 // empty @ sigils are preserved
667 try testReplaceVariablesAutoconfAt(allocator, "@", "@", values);
668 try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", values);
669 try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", values);
670 try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", values);
653 try testReplaceVariablesAutoconfAt(allocator, "@", "@", value_map);
654 try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", value_map);
655 try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", value_map);
656 try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", value_map);
671657
672658 // simple substitution
673 try values.putNoClobber("undef", .undef);
674 try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", values);
675 values.clearRetainingCapacity();
659 try value_map.putNoClobber("undef", .undef);
660 try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", value_map);
661 value_map.clearRetainingCapacity();
676662
677 try values.putNoClobber("defined", .defined);
678 try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", values);
679 values.clearRetainingCapacity();
663 try value_map.putNoClobber("defined", .defined);
664 try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", value_map);
665 value_map.clearRetainingCapacity();
680666
681 try values.putNoClobber("true", Value{ .boolean = true });
682 try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", values);
683 values.clearRetainingCapacity();
667 try value_map.putNoClobber("true", Value{ .boolean = true });
668 try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", value_map);
669 value_map.clearRetainingCapacity();
684670
685 try values.putNoClobber("false", Value{ .boolean = false });
686 try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", values);
687 values.clearRetainingCapacity();
671 try value_map.putNoClobber("false", Value{ .boolean = false });
672 try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", value_map);
673 value_map.clearRetainingCapacity();
688674
689 try values.putNoClobber("int", Value{ .int = 42 });
690 try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", values);
691 values.clearRetainingCapacity();
675 try value_map.putNoClobber("int", Value{ .int = 42 });
676 try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", value_map);
677 value_map.clearRetainingCapacity();
692678
693 try values.putNoClobber("ident", Value{ .string = "value" });
694 try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", values);
695 values.clearRetainingCapacity();
679 try value_map.putNoClobber("ident", Value{ .string = "value" });
680 try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", value_map);
681 value_map.clearRetainingCapacity();
696682
697 try values.putNoClobber("string", Value{ .string = "text" });
698 try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", values);
699 values.clearRetainingCapacity();
683 try value_map.putNoClobber("string", Value{ .string = "text" });
684 try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", value_map);
685 value_map.clearRetainingCapacity();
700686
701687 // double packed substitution
702 try values.putNoClobber("string", Value{ .string = "text" });
703 try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", values);
704 values.clearRetainingCapacity();
688 try value_map.putNoClobber("string", Value{ .string = "text" });
689 try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", value_map);
690 value_map.clearRetainingCapacity();
705691
706692 // triple packed substitution
707 try values.putNoClobber("int", Value{ .int = 42 });
708 try values.putNoClobber("string", Value{ .string = "text" });
709 try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", values);
710 values.clearRetainingCapacity();
693 try value_map.putNoClobber("int", Value{ .int = 42 });
694 try value_map.putNoClobber("string", Value{ .string = "text" });
695 try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", value_map);
696 value_map.clearRetainingCapacity();
711697
712698 // double separated substitution
713 try values.putNoClobber("int", Value{ .int = 42 });
714 try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", values);
715 values.clearRetainingCapacity();
699 try value_map.putNoClobber("int", Value{ .int = 42 });
700 try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", value_map);
701 value_map.clearRetainingCapacity();
716702
717703 // triple separated substitution
718 try values.putNoClobber("true", Value{ .boolean = true });
719 try values.putNoClobber("int", Value{ .int = 42 });
720 try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", values);
721 values.clearRetainingCapacity();
704 try value_map.putNoClobber("true", Value{ .boolean = true });
705 try value_map.putNoClobber("int", Value{ .int = 42 });
706 try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", value_map);
707 value_map.clearRetainingCapacity();
722708
723709 // misc prefix is preserved
724 try values.putNoClobber("false", Value{ .boolean = false });
725 try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", values);
726 values.clearRetainingCapacity();
710 try value_map.putNoClobber("false", Value{ .boolean = false });
711 try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", value_map);
712 value_map.clearRetainingCapacity();
727713
728714 // misc suffix is preserved
729 try values.putNoClobber("true", Value{ .boolean = true });
730 try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", values);
731 values.clearRetainingCapacity();
715 try value_map.putNoClobber("true", Value{ .boolean = true });
716 try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", value_map);
717 value_map.clearRetainingCapacity();
732718
733719 // surrounding content is preserved
734 try values.putNoClobber("int", Value{ .int = 42 });
735 try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values);
736 values.clearRetainingCapacity();
720 try value_map.putNoClobber("int", Value{ .int = 42 });
721 try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", value_map);
722 value_map.clearRetainingCapacity();
737723
738724 // incomplete key is preserved
739 try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", values);
725 try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", value_map);
740726
741727 // unknown key leads to an error
742 try std.testing.expectError(error.MissingValue, testReplaceVariablesAutoconfAt(allocator, "@bad@", "", values));
728 try std.testing.expectError(error.MissingValue, testReplaceVariablesAutoconfAt(allocator, "@bad@", "", value_map));
743729
744730 // unused key leads to an error
745 try values.putNoClobber("int", Value{ .int = 42 });
746 try values.putNoClobber("false", Value{ .boolean = false });
747 try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", values));
748 values.clearRetainingCapacity();
731 try value_map.putNoClobber("int", Value{ .int = 42 });
732 try value_map.putNoClobber("false", Value{ .boolean = false });
733 try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", value_map));
734 value_map.clearRetainingCapacity();
749735}
750736
751737test "expandVariablesAutoconfAt edge cases" {
752738 const allocator = std.testing.allocator;
753 var values: std.array_hash_map.String(Value) = .init(allocator);
754 defer values.deinit();
739 var value_map: std.array_hash_map.String(Value) = .init(allocator);
740 defer value_map.deinit();
755741
756742 // @-vars resolved only when they wrap valid characters, otherwise considered literals
757 try values.putNoClobber("string", Value{ .string = "text" });
758 try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", values);
759 values.clearRetainingCapacity();
743 try value_map.putNoClobber("string", Value{ .string = "text" });
744 try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", value_map);
745 value_map.clearRetainingCapacity();
760746
761747 // expanded variables are considered strings after expansion
762 try values.putNoClobber("string_at", Value{ .string = "@string@" });
763 try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", values);
764 values.clearRetainingCapacity();
748 try value_map.putNoClobber("string_at", Value{ .string = "@string@" });
749 try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", value_map);
750 value_map.clearRetainingCapacity();
765751}
766752
767753test "expandVariablesCmake simple cases" {
768754 const allocator = std.testing.allocator;
769 var values: std.array_hash_map.String(Value) = .init(allocator);
770 defer values.deinit();
755 var value_map: std.array_hash_map.String(Value) = .init(allocator);
756 defer value_map.deinit();
771757
772 try values.putNoClobber("undef", .undef);
773 try values.putNoClobber("defined", .defined);
774 try values.putNoClobber("true", Value{ .boolean = true });
775 try values.putNoClobber("false", Value{ .boolean = false });
776 try values.putNoClobber("int", Value{ .int = 42 });
777 try values.putNoClobber("ident", Value{ .string = "value" });
778 try values.putNoClobber("string", Value{ .string = "text" });
758 try value_map.putNoClobber("undef", .undef);
759 try value_map.putNoClobber("defined", .defined);
760 try value_map.putNoClobber("true", Value{ .boolean = true });
761 try value_map.putNoClobber("false", Value{ .boolean = false });
762 try value_map.putNoClobber("int", Value{ .int = 42 });
763 try value_map.putNoClobber("ident", Value{ .string = "value" });
764 try value_map.putNoClobber("string", Value{ .string = "text" });
779765
780766 // empty strings are preserved
781 try testReplaceVariablesCMake(allocator, "", "", values);
767 try testReplaceVariablesCMake(allocator, "", "", value_map);
782768
783769 // line with misc content is preserved
784 try testReplaceVariablesCMake(allocator, "no substitution", "no substitution", values);
770 try testReplaceVariablesCMake(allocator, "no substitution", "no substitution", value_map);
785771
786772 // empty ${} wrapper leads to an error
787 try std.testing.expectError(error.MissingKey, testReplaceVariablesCMake(allocator, "${}", "", values));
773 try std.testing.expectError(error.MissingKey, testReplaceVariablesCMake(allocator, "${}", "", value_map));
788774
789775 // empty @ sigils are preserved
790 try testReplaceVariablesCMake(allocator, "@", "@", values);
791 try testReplaceVariablesCMake(allocator, "@@", "@@", values);
792 try testReplaceVariablesCMake(allocator, "@@@", "@@@", values);
793 try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", values);
776 try testReplaceVariablesCMake(allocator, "@", "@", value_map);
777 try testReplaceVariablesCMake(allocator, "@@", "@@", value_map);
778 try testReplaceVariablesCMake(allocator, "@@@", "@@@", value_map);
779 try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", value_map);
794780
795781 // simple substitution
796 try testReplaceVariablesCMake(allocator, "@undef@", "", values);
797 try testReplaceVariablesCMake(allocator, "${undef}", "", values);
798 try testReplaceVariablesCMake(allocator, "@defined@", "", values);
799 try testReplaceVariablesCMake(allocator, "${defined}", "", values);
800 try testReplaceVariablesCMake(allocator, "@true@", "1", values);
801 try testReplaceVariablesCMake(allocator, "${true}", "1", values);
802 try testReplaceVariablesCMake(allocator, "@false@", "0", values);
803 try testReplaceVariablesCMake(allocator, "${false}", "0", values);
804 try testReplaceVariablesCMake(allocator, "@int@", "42", values);
805 try testReplaceVariablesCMake(allocator, "${int}", "42", values);
806 try testReplaceVariablesCMake(allocator, "@ident@", "value", values);
807 try testReplaceVariablesCMake(allocator, "${ident}", "value", values);
808 try testReplaceVariablesCMake(allocator, "@string@", "text", values);
809 try testReplaceVariablesCMake(allocator, "${string}", "text", values);
782 try testReplaceVariablesCMake(allocator, "@undef@", "", value_map);
783 try testReplaceVariablesCMake(allocator, "${undef}", "", value_map);
784 try testReplaceVariablesCMake(allocator, "@defined@", "", value_map);
785 try testReplaceVariablesCMake(allocator, "${defined}", "", value_map);
786 try testReplaceVariablesCMake(allocator, "@true@", "1", value_map);
787 try testReplaceVariablesCMake(allocator, "${true}", "1", value_map);
788 try testReplaceVariablesCMake(allocator, "@false@", "0", value_map);
789 try testReplaceVariablesCMake(allocator, "${false}", "0", value_map);
790 try testReplaceVariablesCMake(allocator, "@int@", "42", value_map);
791 try testReplaceVariablesCMake(allocator, "${int}", "42", value_map);
792 try testReplaceVariablesCMake(allocator, "@ident@", "value", value_map);
793 try testReplaceVariablesCMake(allocator, "${ident}", "value", value_map);
794 try testReplaceVariablesCMake(allocator, "@string@", "text", value_map);
795 try testReplaceVariablesCMake(allocator, "${string}", "text", value_map);
810796
811797 // double packed substitution
812 try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", values);
813 try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", values);
798 try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", value_map);
799 try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", value_map);
814800
815801 // triple packed substitution
816 try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", values);
817 try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", values);
818 try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", values);
819 try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", values);
802 try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", value_map);
803 try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", value_map);
804 try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", value_map);
805 try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", value_map);
820806
821807 // double separated substitution
822 try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", values);
823 try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", values);
808 try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", value_map);
809 try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", value_map);
824810
825811 // triple separated substitution
826 try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", values);
827 try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", values);
828 try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", values);
829 try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", values);
812 try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", value_map);
813 try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", value_map);
814 try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", value_map);
815 try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", value_map);
830816
831817 // misc prefix is preserved
832 try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", values);
833 try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", values);
818 try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", value_map);
819 try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", value_map);
834820
835821 // misc suffix is preserved
836 try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", values);
837 try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", values);
822 try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", value_map);
823 try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", value_map);
838824
839825 // surrounding content is preserved
840 try testReplaceVariablesCMake(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values);
841 try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", values);
826 try testReplaceVariablesCMake(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", value_map);
827 try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", value_map);
842828
843829 // incomplete key is preserved
844 try testReplaceVariablesCMake(allocator, "@undef", "@undef", values);
845 try testReplaceVariablesCMake(allocator, "${undef", "${undef", values);
846 try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", values);
847 try testReplaceVariablesCMake(allocator, "undef@", "undef@", values);
848 try testReplaceVariablesCMake(allocator, "undef}", "undef}", values);
830 try testReplaceVariablesCMake(allocator, "@undef", "@undef", value_map);
831 try testReplaceVariablesCMake(allocator, "${undef", "${undef", value_map);
832 try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", value_map);
833 try testReplaceVariablesCMake(allocator, "undef@", "undef@", value_map);
834 try testReplaceVariablesCMake(allocator, "undef}", "undef}", value_map);
849835
850836 // unknown key leads to an error
851 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", values));
852 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", values));
837 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", value_map));
838 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", value_map));
853839}
854840
855841test "expandVariablesCmake edge cases" {
856842 const allocator = std.testing.allocator;
857 var values: std.array_hash_map.String(Value) = .init(allocator);
858 defer values.deinit();
843 var value_map: std.array_hash_map.String(Value) = .init(allocator);
844 defer value_map.deinit();
859845
860846 // special symbols
861 try values.putNoClobber("at", Value{ .string = "@" });
862 try values.putNoClobber("dollar", Value{ .string = "$" });
863 try values.putNoClobber("underscore", Value{ .string = "_" });
847 try value_map.putNoClobber("at", Value{ .string = "@" });
848 try value_map.putNoClobber("dollar", Value{ .string = "$" });
849 try value_map.putNoClobber("underscore", Value{ .string = "_" });
864850
865851 // basic value
866 try values.putNoClobber("string", Value{ .string = "text" });
852 try value_map.putNoClobber("string", Value{ .string = "text" });
867853
868 // proxy case values
869 try values.putNoClobber("string_proxy", Value{ .string = "string" });
870 try values.putNoClobber("string_at", Value{ .string = "@string@" });
871 try values.putNoClobber("string_curly", Value{ .string = "{string}" });
872 try values.putNoClobber("string_var", Value{ .string = "${string}" });
854 // proxy case value_map
855 try value_map.putNoClobber("string_proxy", Value{ .string = "string" });
856 try value_map.putNoClobber("string_at", Value{ .string = "@string@" });
857 try value_map.putNoClobber("string_curly", Value{ .string = "{string}" });
858 try value_map.putNoClobber("string_var", Value{ .string = "${string}" });
873859
874 // stack case values
875 try values.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" });
876 try values.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" });
860 // stack case value_map
861 try value_map.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" });
862 try value_map.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" });
877863
878864 // @-vars resolved only when they wrap valid characters, otherwise considered literals
879 try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", values);
880 try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", values);
865 try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", value_map);
866 try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", value_map);
881867
882868 // @-vars are resolved inside ${}-vars
883 try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", values);
869 try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", value_map);
884870
885871 // expanded variables are considered strings after expansion
886 try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", values);
887 try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", values);
888 try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", values);
889 try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", values);
890 try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", values);
891 try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", values);
892 try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", values);
893 try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", values);
894 try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", values);
872 try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", value_map);
873 try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", value_map);
874 try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", value_map);
875 try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", value_map);
876 try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", value_map);
877 try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", value_map);
878 try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", value_map);
879 try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", value_map);
880 try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", value_map);
895881
896882 // when expanded variables contain invalid characters, they prevent further expansion
897 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", values));
898 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", values));
883 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", value_map));
884 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", value_map));
899885
900886 // nested expanded variables are expanded from the inside out
901 try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", values);
902 try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", values);
887 try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", value_map);
888 try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", value_map);
903889
904890 // nested vars are only expanded when ${} is closed
905 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", values));
906 try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", values);
907 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", values));
908 try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", values);
891 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", value_map));
892 try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", value_map);
893 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", value_map));
894 try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", value_map);
909895
910896 // invalid characters lead to an error
911 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", values));
912 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", values));
913 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", values));
897 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", value_map));
898 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", value_map));
899 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", value_map));
914900}
915901
916902test "expandVariablesCmake escaped characters" {
917903 const allocator = std.testing.allocator;
918 var values: std.array_hash_map.String(Value) = .init(allocator);
919 defer values.deinit();
904 var value_map: std.array_hash_map.String(Value) = .init(allocator);
905 defer value_map.deinit();
920906
921 try values.putNoClobber("string", Value{ .string = "text" });
907 try value_map.putNoClobber("string", Value{ .string = "text" });
922908
923909 // backslash is an invalid character for @ lookup
924 try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", values);
910 try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", value_map);
925911
926912 // backslash is preserved, but doesn't affect ${} variable expansion
927 try testReplaceVariablesCMake(allocator, "\\${string}", "\\text", values);
913 try testReplaceVariablesCMake(allocator, "\\${string}", "\\text", value_map);
928914
929915 // backslash breaks ${} opening bracket identification
930 try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", values);
916 try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", value_map);
931917
932918 // backslash is skipped when checking for invalid characters, yet it mangles the key
933 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${string\\}", "", values));
919 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${string\\}", "", value_map));
934920}
lib/std/Build/Configuration.zig+32
......@@ -1117,6 +1117,38 @@ pub const Step = extern struct {
11171117 undef = max_u32 - 1,
11181118 defined = max_u32,
11191119 _,
1120
1121 pub fn unpack(this: @This(), c: *const Configuration) Unpacked {
1122 return switch (this) {
1123 .int_0 => .{ .u64 = 0 },
1124 .int_1 => .{ .u64 = 1 },
1125 .bool_false => .{ .bool = false },
1126 .bool_true => .{ .bool = true },
1127 .undef => .undef,
1128 .defined => .defined,
1129 _ => {
1130 const value = extraData(c, Value, @intFromEnum(this));
1131 return switch (value.flags.tag) {
1132 .ident => .{ .ident = value.ident.value.?.slice(c) },
1133 .string => .{ .string = value.string.value.?.slice(c) },
1134 .small_unsigned => .{ .u64 = value.flags.small },
1135 .small_signed => .{ .i64 = @as(i29, @bitCast(value.flags.small)) },
1136 .i64 => .{ .i64 = value.i64.value.? },
1137 .u64 => .{ .u64 = value.u64.value.? },
1138 };
1139 },
1140 };
1141 }
1142 };
1143
1144 pub const Unpacked = union(enum) {
1145 bool: bool,
1146 undef,
1147 defined,
1148 i64: i64,
1149 u64: u64,
1150 ident: []const u8,
1151 string: []const u8,
11201152 };
11211153
11221154 pub fn initSigned(x: i64) @This() {
test/standalone/cmakedefine/build.zig-1
......@@ -48,7 +48,6 @@ pub fn build(b: *std.Build) void {
4848 .include_path = "stack.h",
4949 },
5050 .{
51 .AT = "@",
5251 .UNDERSCORE = "_",
5352 .NEST_UNDERSCORE_PROXY = "UNDERSCORE",
5453 .NEST_PROXY = "NEST_UNDERSCORE_PROXY",