1const ConfigHeader = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Configuration = std.Build.Configuration;
6const Writer = std.Io.Writer;
7const Path = std.Build.Cache.Path;
8const Allocator = std.mem.Allocator;
9
10const Step = @import("../Step.zig");
11const Maker = @import("../../Maker.zig");
12
13const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
14const c_generated_line = "/* " ++ header_text ++ " */";
15const asm_generated_line = "; " ++ header_text;
16
17const os_newline = if (@import("builtin").os.tag == .windows) "\r\n" else "\n";
18
19/// Table value is whether the value is used.
20const ValueMap = std.array_hash_map.String(bool);
21const Value = Configuration.Step.ConfigHeader.Value;
22
23pub fn make(
24 config_header: *ConfigHeader,
25 step_index: Configuration.Step.Index,
26 maker: *Maker,
27 progress_node: std.Progress.Node,
28) Step.ExtendedMakeError!void {
29 _ = config_header;
30 const graph = maker.graph;
31 const step = maker.stepByIndex(step_index);
32 const io = graph.io;
33 const arena = graph.arena; // TODO don't leak into the process arena
34 const conf = &maker.scanned_config.configuration;
35 const conf_step = step_index.ptr(conf);
36 const conf_ch = conf_step.extended.get(conf.extra).config_header;
37 const cache_root = graph.local_cache_root;
38
39 const input_size_limit: Io.Limit = if (conf_ch.input_size_limit.value) |x| .limited64(x) else .unlimited;
40 const include_guard_override: ?[]const u8 = if (conf_ch.include_guard.value) |s| s.slice(conf) else null;
41 const include_path: []const u8 = conf_ch.include_path.slice(conf);
42 const template_file = if (conf_ch.template_file.value) |lp|
43 try maker.resolveLazyPathIndex(arena, lp, step_index)
44 else
45 null;
46 const value_pairs = conf_ch.values.slice;
47
48 if (conf_ch.template_file.value) |lp| try step.singleUnchangingWatchInput(maker, arena, lp.get(conf));
49
50 var value_map: ValueMap = .empty;
51 try value_map.ensureTotalCapacity(arena, value_pairs.len);
52 for (value_pairs) |pair| value_map.putAssumeCapacityNoClobber(pair.key.slice(conf), false);
53
54 var man = graph.cache.obtain();
55 defer man.deinit();
56
57 // Random bytes to make ConfigHeader unique. Refresh this with new
58 // random bytes when ConfigHeader implementation is modified in a
59 // non-backwards-compatible way.
60 man.hash.add(@as(u32, 0xdef08d23));
61 man.hash.add(@as(u32, @bitCast(conf_ch.flags)));
62 man.hash.addBytes(include_path);
63 man.hash.addOptionalBytes(include_guard_override);
64
65 var aw: Writer.Allocating = .init(arena);
66 defer aw.deinit();
67
68 switch (conf_ch.flags.style) {
69 .autoconf_undef => {
70 const tf = template_file.?;
71 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
72 return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
73 renderAutoConfUndef(maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
74 error.WriteFailed => return error.OutOfMemory,
75 else => |e| return e,
76 };
77 },
78 .autoconf_at => {
79 const tf = template_file.?;
80 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
81 return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
82 renderAutoconfAt(maker, step, contents, &aw, value_pairs, &value_map, tf) catch |err| switch (err) {
83 error.WriteFailed => return error.OutOfMemory,
84 else => |e| return e,
85 };
86 },
87 .cmake => {
88 const tf = template_file.?;
89 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
90 return step.fail(maker, "unable to read cmake input file {f}: {t}", .{ tf, err });
91 renderCmake(arena, maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
92 error.WriteFailed => return error.OutOfMemory,
93 else => |e| return e,
94 };
95 },
96 .meson => {
97 const tf = template_file.?;
98 const contents = tf.root_dir.handle.readFileAlloc(
99 io,
100 tf.sub_path,
101 arena,
102 input_size_limit,
103 ) catch |err| return step.fail(
104 maker,
105 "unable to read meson input file {f}: {t}",
106 .{ tf, err },
107 );
108
109 renderMeson(
110 maker,
111 step,
112 contents,
113 &aw,
114 value_pairs,
115 &value_map,
116 tf,
117 ) catch |err| switch (err) {
118 error.WriteFailed => return error.OutOfMemory,
119 else => |e| return e,
120 };
121 },
122 .blank => {
123 renderBlank(conf, &aw.writer, value_pairs, &value_map, include_path, include_guard_override) catch |err| switch (err) {
124 error.WriteFailed => return error.OutOfMemory,
125 else => |e| return e,
126 };
127 },
128 .nasm => {
129 renderNasm(conf, &aw.writer, value_pairs, &value_map) catch |err| switch (err) {
130 error.WriteFailed => return error.OutOfMemory,
131 else => |e| return e,
132 };
133 },
134 }
135
136 const output = aw.written();
137 man.hash.addBytes(output);
138
139 if (try step.cacheHit(maker, &man, progress_node)) {
140 const digest = man.final();
141 maker.generatedPath(conf_ch.generated_dir).* = .{
142 .root_dir = cache_root,
143 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
144 };
145 return;
146 }
147
148 const digest = man.final();
149
150 // If output_path has directory parts, deal with them. Example:
151 // output_dir is zig-cache/o/HASH
152 // output_path is libavutil/avconfig.h
153 // We want to open directory zig-cache/o/HASH/libavutil/
154 // but keep output_dir as zig-cache/o/HASH for -I include
155 const out_path: Path = .{
156 .root_dir = cache_root,
157 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, conf_ch.include_path.slice(conf) }),
158 };
159 const out_path_dirname = out_path.dirname().?;
160
161 out_path_dirname.root_dir.handle.createDirPath(io, out_path_dirname.sub_path) catch |err|
162 return step.fail(maker, "unable to make path {f}: {t}", .{ out_path_dirname, err });
163
164 out_path.root_dir.handle.writeFile(io, .{ .sub_path = out_path.sub_path, .data = output }) catch |err|
165 return step.fail(maker, "unable to write file {f}: {t}", .{ out_path, err });
166
167 maker.generatedPath(conf_ch.generated_dir).* = .{
168 .root_dir = cache_root,
169 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
170 };
171
172 try step.writeManifest(maker, &man);
173}
174
175fn ensureAllValuesUsed(
176 maker: *Maker,
177 step: *Step,
178 value_map: *const ValueMap,
179 src_path: Path,
180) Step.ExtendedMakeError!void {
181 var any_errors = false;
182 for (value_map.keys(), value_map.values()) |name, used| {
183 if (used) continue;
184 try step.addError(maker, "{f}: config header value unused: {s}", .{ src_path, name });
185 any_errors = true;
186 }
187 if (any_errors) return error.MakeFailed;
188}
189
190fn detectNewline(contents: []const u8) []const u8 {
191 const lf_index = std.mem.findScalar(u8, contents, '\n') orelse return os_newline;
192 return if (lf_index > 0 and contents[lf_index - 1] == '\r') "\r\n" else "\n";
193}
194
195fn renderAutoConfUndef(
196 maker: *Maker,
197 step: *Step,
198 contents: []const u8,
199 w: *Writer,
200 value_pairs: []const Value.Pair,
201 value_map: *ValueMap,
202 src_path: Path,
203) !void {
204 const conf = &maker.scanned_config.configuration;
205 const newline = detectNewline(contents);
206
207 try w.writeAll(c_generated_line);
208 try w.writeAll(newline);
209
210 var any_errors = false;
211 var line_index: u32 = 0;
212 var line_it = std.mem.splitScalar(u8, contents, '\n');
213 while (line_it.next()) |raw_line| : (line_index += 1) {
214 const last_line = line_it.index == line_it.buffer.len;
215 const line = std.mem.trimEnd(u8, raw_line, "\r");
216
217 if (!std.mem.startsWith(u8, line, "#")) {
218 try w.writeAll(line);
219 if (!last_line) try w.writeAll(newline);
220 continue;
221 }
222 var it = std.mem.tokenizeAny(u8, line[1..], " \t\r");
223 const undef = it.next().?;
224 if (!std.mem.eql(u8, undef, "undef")) {
225 try w.writeAll(line);
226 if (!last_line) try w.writeAll(newline);
227 continue;
228 }
229 const name = it.next().?;
230 const index = value_map.getIndex(name) orelse {
231 try step.addError(maker, "{f}:{d}: unspecified config header value: {s}", .{
232 src_path, line_index + 1, name,
233 });
234 any_errors = true;
235 continue;
236 };
237 value_map.values()[index] = true; // Set to used.
238 try renderValueC(conf, w, newline, name, value_pairs[index].index);
239 }
240
241 try ensureAllValuesUsed(maker, step, value_map, src_path);
242 if (any_errors) return error.MakeFailed;
243}
244
245fn renderAutoconfAt(
246 maker: *Maker,
247 step: *Step,
248 contents: []const u8,
249 aw: *Writer.Allocating,
250 value_pairs: []const Value.Pair,
251 value_map: *const ValueMap,
252 src_path: Path,
253) !void {
254 const w = &aw.writer;
255 const conf = &maker.scanned_config.configuration;
256 const newline = detectNewline(contents);
257
258 try w.writeAll(c_generated_line);
259 try w.writeAll(newline);
260
261 var any_errors = false;
262 var line_index: u32 = 0;
263 var line_it = std.mem.splitScalar(u8, contents, '\n');
264 while (line_it.next()) |raw_line| : (line_index += 1) {
265 const last_line = line_it.index == line_it.buffer.len;
266 const line = std.mem.trimEnd(u8, raw_line, "\r");
267
268 const old_len = aw.written().len;
269 expandVariablesAutoconfAt(w, line, conf, value_pairs, value_map) catch |err| switch (err) {
270 error.MissingValue => {
271 const name = aw.written()[old_len..];
272 defer aw.shrinkRetainingCapacity(old_len);
273 try step.addError(maker, "{f}:{d}: error: unspecified config header value: {s}", .{
274 src_path, line_index + 1, name,
275 });
276 any_errors = true;
277 continue;
278 },
279 else => {
280 try step.addError(maker, "{f}:{d}: unable to substitute variable: error: {t}", .{
281 src_path, line_index + 1, err,
282 });
283 any_errors = true;
284 continue;
285 },
286 };
287 if (!last_line) try w.writeAll(newline);
288 }
289
290 try ensureAllValuesUsed(maker, step, value_map, src_path);
291 if (any_errors) return error.MakeFailed;
292}
293
294fn renderCmake(
295 arena: Allocator,
296 maker: *Maker,
297 step: *Step,
298 contents: []const u8,
299 w: *Writer,
300 value_pairs: []const Value.Pair,
301 value_map: *ValueMap,
302 src_path: Path,
303) !void {
304 const conf = &maker.scanned_config.configuration;
305 const newline = detectNewline(contents);
306
307 try w.writeAll(c_generated_line);
308 try w.writeAll(newline);
309
310 var any_errors = false;
311 var line_index: u32 = 0;
312 var line_it = std.mem.splitScalar(u8, contents, '\n');
313 while (line_it.next()) |raw_line| : (line_index += 1) {
314 const last_line = line_it.index == line_it.buffer.len;
315 const stripped_line = std.mem.trimEnd(u8, raw_line, "\r");
316
317 const line = expandVariablesCmake(arena, stripped_line, conf, value_pairs, value_map) catch |err| switch (err) {
318 error.InvalidCharacter => {
319 try step.addError(maker, "{f}:{d}: invalid character in a variable name", .{
320 src_path, line_index + 1,
321 });
322 any_errors = true;
323 continue;
324 },
325 else => {
326 try step.addError(maker, "{f}:{d}: failed substituting variable: {t}", .{
327 src_path, line_index + 1, err,
328 });
329 any_errors = true;
330 continue;
331 },
332 };
333
334 const line_start = std.mem.findNone(u8, line, " \t\r") orelse {
335 try w.writeAll(line);
336 if (!last_line) try w.writeAll(newline);
337 continue;
338 };
339 const whitespace_prefix = line[0..line_start];
340 const trimmed_line = line[line_start..];
341
342 if (!std.mem.startsWith(u8, trimmed_line, "#")) {
343 try w.writeAll(line);
344 if (!last_line) try w.writeAll(newline);
345 continue;
346 }
347
348 var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r");
349 const cmakedefine = it.next().?;
350
351 const booldefine = if (std.mem.eql(u8, cmakedefine, "cmakedefine01"))
352 true
353 else if (std.mem.eql(u8, cmakedefine, "cmakedefine"))
354 false
355 else {
356 try w.writeAll(line);
357 if (!last_line) try w.writeAll(newline);
358 continue;
359 };
360
361 const name = it.next() orelse {
362 try step.addError(maker, "{f}:{d}: error: missing define name", .{ src_path, line_index + 1 });
363 any_errors = true;
364 continue;
365 };
366 const orig_value: Value.Index = v: {
367 const index = value_map.getIndex(name) orelse break :v if (booldefine) .int_0 else .undef;
368 value_map.values()[index] = true; // Mark as used.
369 break :v value_pairs[index].index;
370 };
371 const value = switch (orig_value.unpack(conf)) {
372 .bool => |b| if (!b) .undef else orig_value,
373 inline .i64, .u64 => |i| if (i == 0) .undef else orig_value,
374 .string => |s| if (s.len == 0) .undef else orig_value,
375 else => orig_value,
376 };
377
378 try w.writeAll(whitespace_prefix);
379
380 if (booldefine) {
381 try renderValueCBool(w, newline, name, switch (value.unpack(conf)) {
382 .undef, .defined => false,
383 .bool => |b| b,
384 inline .u64, .i64 => |i| i != 0,
385 .string => |s| s.len != 0,
386 .ident => false,
387 });
388 } else if (value != .undef) {
389 try renderValueCIdent(w, newline, name, it.rest());
390 } else {
391 try renderValueC(conf, w, newline, name, value);
392 }
393 }
394
395 try ensureAllValuesUsed(maker, step, value_map, src_path);
396 if (any_errors) return error.MakeFailed;
397}
398
399fn renderMeson(
400 maker: *Maker,
401 step: *Step,
402 contents: []const u8,
403 aw: *Writer.Allocating,
404 value_pairs: []const Value.Pair,
405 value_map: *const ValueMap,
406 src_path: Path,
407) !void {
408 const w = &aw.writer;
409 const conf = &maker.scanned_config.configuration;
410 const newline = detectNewline(contents);
411
412 try w.writeAll(c_generated_line);
413 try w.writeAll(newline);
414
415 var any_errors = false;
416 var line_index: u32 = 0;
417 var line_it = std.mem.splitScalar(u8, contents, '\n');
418 // https://mesonbuild.com/Configuration.html
419 while (line_it.next()) |raw_line| : (line_index += 1) {
420 const last_line = line_it.index == line_it.buffer.len;
421 const line = std.mem.trimEnd(u8, raw_line, "\r");
422
423 const old_len = aw.written().len;
424 expandVariablesMeson(w, conf, line, value_pairs, value_map) catch |err| switch (err) {
425 error.MissingToken => {
426 try step.addError(maker, "{f}:{d}: error: missing define name", .{ src_path, line_index + 1 });
427 any_errors = true;
428 continue;
429 },
430 error.MissingValue => {
431 const name = aw.written()[old_len..];
432 defer aw.shrinkRetainingCapacity(old_len);
433
434 try step.addError(maker, "{f}:{d}: error: unspecified config header value: {q}", .{
435 src_path, line_index + 1, name,
436 });
437 any_errors = true;
438 continue;
439 },
440 else => {
441 try step.addError(maker, "{f}:{d}: unable to substitute variable: error: {t}", .{
442 src_path, line_index + 1, err,
443 });
444 any_errors = true;
445 continue;
446 },
447 };
448 if (!last_line) try w.writeAll(newline);
449 }
450
451 try ensureAllValuesUsed(maker, step, value_map, src_path);
452 if (any_errors) return error.MakeFailed;
453}
454
455fn renderBlank(
456 conf: *const Configuration,
457 w: *Writer,
458 value_pairs: []const Value.Pair,
459 value_map: *const ValueMap,
460 include_path: []const u8,
461 include_guard_override: ?[]const u8,
462) !void {
463 try w.writeAll(c_generated_line);
464 try w.writeAll(os_newline);
465
466 const include_guard_fmt: IncludeGuardFmt = .{
467 .include_path = include_path,
468 .override = include_guard_override,
469 };
470
471 try w.print(
472 "#ifndef {[0]f}{[1]s}#define {[0]f}{[1]s}",
473 .{ include_guard_fmt, os_newline },
474 );
475
476 for (value_map.keys(), value_pairs) |name, pair| try renderValueC(conf, w, os_newline, name, pair.index);
477
478 try w.print("#endif /* {f} */{s}", .{ include_guard_fmt, os_newline });
479}
480
481const IncludeGuardFmt = struct {
482 include_path: []const u8,
483 override: ?[]const u8,
484
485 pub fn format(this: @This(), w: *Writer) Writer.Error!void {
486 if (this.override) |s| return w.writeAll(s);
487 for (this.include_path) |byte| switch (byte) {
488 'a'...'z' => try w.writeByte(byte - 'a' + 'A'),
489 'A'...'Z', '0'...'9' => continue,
490 else => try w.writeByte('_'),
491 };
492 }
493};
494
495fn renderNasm(
496 conf: *const Configuration,
497 w: *Writer,
498 value_pairs: []const Value.Pair,
499 value_map: *const ValueMap,
500) !void {
501 try w.writeAll(asm_generated_line);
502 try w.writeAll(os_newline);
503 for (value_map.keys(), value_pairs) |name, pair| try renderValueNasm(conf, w, os_newline, name, pair.index);
504}
505
506fn renderValueC(conf: *const Configuration, w: *Writer, newline: []const u8, name: []const u8, value: Value.Index) !void {
507 switch (value.unpack(conf)) {
508 .undef => try w.print("/* #undef {s} */{s}", .{ name, newline }),
509 .defined => try w.print("#define {s}{s}", .{ name, newline }),
510 .bool => |b| return renderValueCBool(w, newline, name, b),
511 inline .u64, .i64 => |i| try w.print("#define {s} {d}{s}", .{ name, i, newline }),
512 .ident => |ident| return renderValueCIdent(w, newline, name, ident),
513 .string => |string| try w.print("#define {s} \"{f}\"{s}", .{ name, std.zig.fmtString(string), newline }),
514 }
515}
516
517fn renderValueMeson(
518 conf: *const Configuration,
519 w: *Writer,
520 name: []const u8,
521 value: Value.Index,
522) !void {
523 switch (value.unpack(conf)) {
524 .undef => try w.print("/* #undef {s} */", .{name}),
525 .defined => try w.print("#define {s}", .{name}),
526 .bool => |b| {
527 if (b) {
528 try w.print("#define {s}", .{name});
529 } else {
530 try w.print("#undef {s}", .{name});
531 }
532 },
533 inline .u64, .i64 => |int| try w.print("#define {s} {d}", .{ name, int }),
534 .ident => |ident| try w.print("#define {s} {s}", .{ name, ident }),
535 .string => |string| try w.print("#define {s} \"{f}\"", .{ name, std.zig.fmtString(string) }),
536 }
537}
538
539fn renderValueCIdent(w: *Writer, newline: []const u8, name: []const u8, ident: []const u8) Writer.Error!void {
540 try w.print("#define {s}", .{name});
541 if (ident.len > 0) {
542 try w.writeByte(' ');
543 try w.writeAll(ident);
544 }
545 return w.writeAll(newline);
546}
547
548fn renderValueCBool(w: *Writer, newline: []const u8, name: []const u8, b: bool) Writer.Error!void {
549 return w.print("#define {s} {c}{s}", .{ name, @as(u8, '0') + @intFromBool(b), newline });
550}
551
552fn renderValueNasm(conf: *const Configuration, w: *Writer, newline: []const u8, name: []const u8, value: Value.Index) !void {
553 switch (value.unpack(conf)) {
554 .undef => try w.print("; %undef {s}{s}", .{ name, newline }),
555 .defined => try w.print("%define {s}{s}", .{ name, newline }),
556 .bool => |b| try w.print("%define {s} {c}{s}", .{ name, @as(u8, '0') + @intFromBool(b), newline }),
557 inline .u64, .i64 => |i| try w.print("%define {s} {d}{s}", .{ name, i, newline }),
558 .ident => |ident| try w.print("%define {s} {s}{s}", .{ name, ident, newline }),
559 .string => |string| try w.print("%define {s} \"{f}\"{s}", .{ name, std.zig.fmtString(string), newline }),
560 }
561}
562
563fn expandVariablesAutoconfAt(
564 w: *Writer,
565 contents: []const u8,
566 conf: *const Configuration,
567 value_pairs: []const Value.Pair,
568 value_map: *const ValueMap,
569) !void {
570 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
571
572 var curr: usize = 0;
573 var source_offset: usize = 0;
574 while (curr < contents.len) : (curr += 1) {
575 if (contents[curr] != '@') continue;
576 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
577 if (close_pos == curr + 1) {
578 // closed immediately, preserve as a literal
579 continue;
580 }
581 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
582 if (valid_varname_end != close_pos) {
583 // contains invalid characters, preserve as a literal
584 continue;
585 }
586
587 const key = contents[curr + 1 .. close_pos];
588 const index = value_map.getIndex(key) orelse {
589 // Report the missing key to the caller.
590 try w.writeAll(key);
591 return error.MissingValue;
592 };
593 const value = value_pairs[index].index;
594 value_map.values()[index] = true; // Mark as used.
595 try w.writeAll(contents[source_offset..curr]);
596 switch (value.unpack(conf)) {
597 .undef, .defined => {},
598 .bool => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)),
599 inline .u64, .i64 => |i| try w.print("{d}", .{i}),
600 .ident, .string => |s| try w.writeAll(s),
601 }
602
603 curr = close_pos;
604 source_offset = close_pos + 1;
605 }
606 }
607
608 try w.writeAll(contents[source_offset..]);
609}
610
611fn expandVariablesCmake(
612 arena: Allocator,
613 contents: []const u8,
614 conf: *const Configuration,
615 value_pairs: []const Value.Pair,
616 value_map: *const ValueMap,
617) ![]const u8 {
618 var result: std.ArrayList(u8) = .empty;
619
620 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";
621 const open_var = "${";
622
623 var curr: usize = 0;
624 var source_offset: usize = 0;
625 const Position = struct {
626 source: usize,
627 target: usize,
628 };
629 var var_stack: std.ArrayList(Position) = .empty;
630 loop: while (curr < contents.len) : (curr += 1) {
631 switch (contents[curr]) {
632 '@' => blk: {
633 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
634 if (close_pos == curr + 1) {
635 // closed immediately, preserve as a literal
636 break :blk;
637 }
638 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
639 if (valid_varname_end != close_pos) {
640 // contains invalid characters, preserve as a literal
641 break :blk;
642 }
643
644 const key = contents[curr + 1 .. close_pos];
645 const index = value_map.getIndex(key) orelse return error.MissingValue;
646 value_map.values()[index] = true; // Mark as used.
647 const value = value_pairs[index].index;
648 const missing = contents[source_offset..curr];
649 try result.appendSlice(arena, missing);
650 switch (value.unpack(conf)) {
651 .undef, .defined => {},
652 .bool => |b| try result.append(arena, if (b) '1' else '0'),
653 inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
654 .ident, .string => |s| try result.appendSlice(arena, s),
655 }
656
657 curr = close_pos;
658 source_offset = close_pos + 1;
659
660 continue :loop;
661 }
662 },
663 '$' => blk: {
664 const next = curr + 1;
665 if (next == contents.len or contents[next] != '{') {
666 // no open bracket detected, preserve as a literal
667 break :blk;
668 }
669 const missing = contents[source_offset..curr];
670 try result.appendSlice(arena, missing);
671 try result.appendSlice(arena, open_var);
672
673 source_offset = curr + open_var.len;
674 curr = next;
675 try var_stack.append(arena, .{
676 .source = curr,
677 .target = result.items.len - open_var.len,
678 });
679
680 continue :loop;
681 },
682 '}' => blk: {
683 if (var_stack.items.len == 0) {
684 // no open bracket, preserve as a literal
685 break :blk;
686 }
687 const open_pos = var_stack.pop().?;
688 if (source_offset == open_pos.source) {
689 source_offset += open_var.len;
690 }
691 const missing = contents[source_offset..curr];
692 try result.appendSlice(arena, missing);
693
694 const key_start = open_pos.target + open_var.len;
695 const key = result.items[key_start..];
696 if (key.len == 0) {
697 return error.MissingKey;
698 }
699 const index = value_map.getIndex(key) orelse return error.MissingValue;
700 value_map.values()[index] = true; // Mark as used.
701 const value = value_pairs[index].index;
702 result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len);
703 switch (value.unpack(conf)) {
704 .undef, .defined => {},
705 .bool => |b| try result.append(arena, if (b) '1' else '0'),
706 inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
707 .ident, .string => |s| try result.appendSlice(arena, s),
708 }
709
710 source_offset = curr + 1;
711
712 continue :loop;
713 },
714 '\\' => {
715 // backslash is not considered a special character
716 continue :loop;
717 },
718 else => {},
719 }
720
721 if (var_stack.items.len > 0 and std.mem.findScalar(u8, valid_varname_chars, contents[curr]) == null) {
722 return error.InvalidCharacter;
723 }
724 }
725
726 if (source_offset != contents.len) {
727 const missing = contents[source_offset..];
728 try result.appendSlice(arena, missing);
729 }
730
731 try result.shrinkToLen(arena);
732
733 return result.toOwnedSliceAssert();
734}
735
736fn expandVariablesMeson(
737 w: *Writer,
738 conf: *const Configuration,
739 line: []const u8,
740 value_pairs: []const Value.Pair,
741 value_map: *const ValueMap,
742) !void {
743 const mesondefine = "#mesondefine";
744 if (std.mem.startsWith(u8, line, mesondefine)) {
745 const line_offset = mesondefine.len + 1;
746 if (line_offset > line.len) return error.MissingToken;
747
748 var it = std.mem.tokenizeAny(u8, line[line_offset..], " \t\r");
749 const name = it.next() orelse return error.MissingToken;
750
751 const index = value_map.getIndex(name) orelse {
752 // Report the missing key to the caller.
753 try w.writeAll(name);
754 return error.MissingValue;
755 };
756
757 const value = value_pairs[index].index;
758 value_map.values()[index] = true; // Mark as used.
759
760 try renderValueMeson(conf, w, name, value);
761
762 // comments/any other text passthrough unaffected
763 return try w.writeAll(line[line_offset + name.len ..]);
764 }
765
766 try expandVariablesAutoconfAt(w, line, conf, value_pairs, value_map);
767}