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;...@@ -10,9 +10,13 @@ const Allocator = std.mem.Allocator;
10const Step = @import("../Step.zig");10const Step = @import("../Step.zig");
11const Maker = @import("../../Maker.zig");11const Maker = @import("../../Maker.zig");
1212
13 const header_text = "This file was generated by ConfigHeader using the Zig Build System.";13const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
14 const c_generated_line = "/* " ++ header_text ++ " */\n";14const c_generated_line = "/* " ++ header_text ++ " */\n";
15 const asm_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
17pub fn make(21pub fn make(
18 config_header: *ConfigHeader,22 config_header: *ConfigHeader,
...@@ -23,7 +27,6 @@ pub fn make(...@@ -23,7 +27,6 @@ pub fn make(
23 _ = config_header;27 _ = config_header;
24 _ = progress_node;28 _ = progress_node;
25 const graph = maker.graph;29 const graph = maker.graph;
26 const gpa = maker.gpa;
27 const step = maker.stepByIndex(step_index);30 const step = maker.stepByIndex(step_index);
28 const io = graph.io;31 const io = graph.io;
29 const arena = graph.arena; // TODO don't leak into the process arena32 const arena = graph.arena; // TODO don't leak into the process arena
...@@ -32,8 +35,20 @@ pub fn make(...@@ -32,8 +35,20 @@ pub fn make(
32 const conf_ch = conf_step.extended.get(conf.extra).config_header;35 const conf_ch = conf_step.extended.get(conf.extra).config_header;
33 const cache_root = graph.local_cache_root;36 const cache_root = graph.local_cache_root;
3437
35 if (conf_ch.style.getPath()) |lp|38 const input_size_limit: Io.Limit = if (conf_ch.input_size_limit.value) |x| .limited64(x) else .unlimited;
36 try step.singleUnchangingWatchInput(maker, arena, lp);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
38 var man = graph.cache.obtain();53 var man = graph.cache.obtain();
39 defer man.deinit();54 defer man.deinit();
...@@ -42,48 +57,49 @@ pub fn make(...@@ -42,48 +57,49 @@ pub fn make(
42 // random bytes when ConfigHeader implementation is modified in a57 // random bytes when ConfigHeader implementation is modified in a
43 // non-backwards-compatible way.58 // non-backwards-compatible way.
44 man.hash.add(@as(u32, 0xdef08d23));59 man.hash.add(@as(u32, 0xdef08d23));
45 man.hash.addBytes(conf_ch.include_path);60 man.hash.add(@as(u32, @bitCast(conf_ch.flags)));
46 man.hash.addOptionalBytes(conf_ch.include_guard_override);61 man.hash.addBytes(include_path);
62 man.hash.addOptionalBytes(include_guard_override);
4763
48 var aw: Writer.Allocating = .init(arena);64 var aw: Writer.Allocating = .init(arena);
49 defer aw.deinit();65 defer aw.deinit();
5066
51 switch (conf_ch.flags.style) {67 switch (conf_ch.flags.style) {
52 .autoconf_undef => {68 .autoconf_undef => {
53 const src_path = try maker.resolveLazyPathIndex(arena, conf_ch.template_file.value.?, step_index);69 const tf = template_file.?;
54 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(conf_ch.max_bytes)) catch |err|70 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
55 return step.fail("unable to read autoconf input file {s}: {t}", .{ src_path, err });71 return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
56 renderAutoConfUndef(step, contents, &aw.writer, &conf_ch.values, src_path) catch |err| switch (err) {72 renderAutoConfUndef(maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
57 error.WriteFailed => return error.OutOfMemory,73 error.WriteFailed => return error.OutOfMemory,
58 else => |e| return e,74 else => |e| return e,
59 };75 };
60 },76 },
61 .autoconf_at => {77 .autoconf_at => {
62 const src_path = try maker.resolveLazyPathIndex(arena, conf_ch.template_file.value.?, step_index);78 const tf = template_file.?;
63 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(conf_ch.max_bytes)) catch |err|79 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
64 return step.fail("unable to read autoconf input file {s}: {t}", .{ src_path, err });80 return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err });
65 renderAutoconfAt(step, contents, &aw, &conf_ch.values, src_path) catch |err| switch (err) {81 renderAutoconfAt(maker, step, contents, &aw, value_pairs, &value_map, tf) catch |err| switch (err) {
66 error.WriteFailed => return error.OutOfMemory,82 error.WriteFailed => return error.OutOfMemory,
67 else => |e| return e,83 else => |e| return e,
68 };84 };
69 },85 },
70 .cmake => {86 .cmake => {
71 const src_path = try maker.resolveLazyPathIndex(arena, conf_ch.template_file.value.?, step_index);87 const tf = template_file.?;
72 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(conf_ch.max_bytes)) catch |err|88 const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err|
73 return step.fail("unable to read cmake input file {s}: {t}", .{ src_path, err });89 return step.fail(maker, "unable to read cmake input file {f}: {t}", .{ tf, err });
74 renderCmake(step, contents, &aw.writer, conf_ch.values, src_path) catch |err| switch (err) {90 renderCmake(arena, maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) {
75 error.WriteFailed => return error.OutOfMemory,91 error.WriteFailed => return error.OutOfMemory,
76 else => |e| return e,92 else => |e| return e,
77 };93 };
78 },94 },
79 .blank => {95 .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) {
81 error.WriteFailed => return error.OutOfMemory,97 error.WriteFailed => return error.OutOfMemory,
82 else => |e| return e,98 else => |e| return e,
83 };99 };
84 },100 },
85 .nasm => {101 .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) {
87 error.WriteFailed => return error.OutOfMemory,103 error.WriteFailed => return error.OutOfMemory,
88 else => |e| return e,104 else => |e| return e,
89 };105 };
...@@ -93,9 +109,9 @@ pub fn make(...@@ -93,9 +109,9 @@ pub fn make(
93 const output = aw.written();109 const output = aw.written();
94 man.hash.addBytes(output);110 man.hash.addBytes(output);
95111
96 if (try step.cacheHit(&man)) {112 if (try step.cacheHit(maker, &man)) {
97 const digest = man.final();113 const digest = man.final();
98 maker.generatedPath().* = .{114 maker.generatedPath(conf_ch.generated_dir).* = .{
99 .root_dir = cache_root,115 .root_dir = cache_root,
100 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),116 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
101 };117 };
...@@ -116,12 +132,12 @@ pub fn make(...@@ -116,12 +132,12 @@ pub fn make(
116 const out_path_dirname = out_path.dirname().?;132 const out_path_dirname = out_path.dirname().?;
117133
118 out_path_dirname.root_dir.handle.createDirPath(io, out_path_dirname.sub_path) catch |err|134 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
121 out_path.root_dir.handle.writeFile(io, .{ .sub_path = out_path.sub_path, .data = output }) catch |err|137 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).* = .{
125 .root_dir = cache_root,141 .root_dir = cache_root,
126 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),142 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
127 };143 };
...@@ -129,21 +145,34 @@ pub fn make(...@@ -129,21 +145,34 @@ pub fn make(
129 try step.writeManifest(maker, &man);145 try step.writeManifest(maker, &man);
130}146}
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
132fn renderAutoConfUndef(163fn renderAutoConfUndef(
164 maker: *Maker,
133 step: *Step,165 step: *Step,
134 contents: []const u8,166 contents: []const u8,
135 w: *Writer,167 w: *Writer,
136 values: *const std.array_hash_map.String(Value),168 value_pairs: []const Value.Pair,
137 src_path: []const u8,169 value_map: *ValueMap,
170 src_path: Path,
138) !void {171) !void {
139 const build = step.owner;172 const conf = &maker.scanned_config.configuration;
140 const allocator = build.allocator;
141173
142 try w.writeAll(c_generated_line);174 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
147 var any_errors = false;176 var any_errors = false;
148 var line_index: u32 = 0;177 var line_index: u32 = 0;
149 var line_it = std.mem.splitScalar(u8, contents, '\n');178 var line_it = std.mem.splitScalar(u8, contents, '\n');
...@@ -161,45 +190,35 @@ fn renderAutoConfUndef(...@@ -161,45 +190,35 @@ fn renderAutoConfUndef(
161 continue;190 continue;
162 }191 }
163 const name = it.next().?;192 const name = it.next().?;
164 const index = values.getIndex(name) orelse {193 const index = value_map.getIndex(name) orelse {
165 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{194 try step.addError(maker, "{f}:{d}: unspecified config header value: {s}", .{
166 src_path, line_index + 1, name,195 src_path, line_index + 1, name,
167 });196 });
168 any_errors = true;197 any_errors = true;
169 continue;198 continue;
170 };199 };
171 is_used.set(index);200 value_map.values()[index] = true; // Set to used.
172 try renderValueC(w, name, values.values()[index]);201 try renderValueC(conf, w, name, value_pairs[index].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;
179 }202 }
180203
181 if (any_errors) {204 try ensureAllValuesUsed(maker, step, value_map, src_path);
182 return error.MakeFailed;205 if (any_errors) return error.MakeFailed;
183 }
184}206}
185207
186fn renderAutoconfAt(208fn renderAutoconfAt(
209 maker: *Maker,
187 step: *Step,210 step: *Step,
188 contents: []const u8,211 contents: []const u8,
189 aw: *Writer.Allocating,212 aw: *Writer.Allocating,
190 values: *const std.array_hash_map.String(Value),213 value_pairs: []const Value.Pair,
191 src_path: []const u8,214 value_map: *const ValueMap,
215 src_path: Path,
192) !void {216) !void {
193 const build = step.owner;
194 const allocator = build.allocator;
195 const w = &aw.writer;217 const w = &aw.writer;
218 const conf = &maker.scanned_config.configuration;
196219
197 try w.writeAll(c_generated_line);220 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
203 var any_errors = false;222 var any_errors = false;
204 var line_index: u32 = 0;223 var line_index: u32 = 0;
205 var line_it = std.mem.splitScalar(u8, contents, '\n');224 var line_it = std.mem.splitScalar(u8, contents, '\n');
...@@ -207,19 +226,19 @@ fn renderAutoconfAt(...@@ -207,19 +226,19 @@ fn renderAutoconfAt(
207 const last_line = line_it.index == line_it.buffer.len;226 const last_line = line_it.index == line_it.buffer.len;
208227
209 const old_len = aw.written().len;228 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) {
211 error.MissingValue => {230 error.MissingValue => {
212 const name = aw.written()[old_len..];231 const name = aw.written()[old_len..];
213 defer aw.shrinkRetainingCapacity(old_len);232 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}", .{
215 src_path, line_index + 1, name,234 src_path, line_index + 1, name,
216 });235 });
217 any_errors = true;236 any_errors = true;
218 continue;237 continue;
219 },238 },
220 else => {239 else => {
221 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{240 try step.addError(maker, "{f}:{d}: unable to substitute variable: error: {t}", .{
222 src_path, line_index + 1, @errorName(err),241 src_path, line_index + 1, err,
223 });242 });
224 any_errors = true;243 any_errors = true;
225 continue;244 continue;
...@@ -228,30 +247,23 @@ fn renderAutoconfAt(...@@ -228,30 +247,23 @@ fn renderAutoconfAt(
228 if (!last_line) try w.writeByte('\n');247 if (!last_line) try w.writeByte('\n');
229 }248 }
230249
231 for (values.entries.slice().items(.key), used) |name, u| {250 try ensureAllValuesUsed(maker, step, value_map, src_path);
232 if (!u) {
233 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
234 any_errors = true;
235 }
236 }
237
238 if (any_errors) return error.MakeFailed;251 if (any_errors) return error.MakeFailed;
239}252}
240253
241fn renderCmake(254fn renderCmake(
255 arena: Allocator,
256 maker: *Maker,
242 step: *Step,257 step: *Step,
243 contents: []const u8,258 contents: []const u8,
244 w: *Writer,259 w: *Writer,
245 values: std.array_hash_map.String(Value),260 value_pairs: []const Value.Pair,
246 src_path: []const u8,261 value_map: *ValueMap,
262 src_path: Path,
247) !void {263) !void {
248 const build = step.owner;264 const conf = &maker.scanned_config.configuration;
249 const allocator = build.allocator;
250
251 try w.writeAll(c_generated_line);
252265
253 var values_copy = try values.clone(allocator);266 try w.writeAll(c_generated_line);
254 defer values_copy.deinit(allocator);
255267
256 var any_errors = false;268 var any_errors = false;
257 var line_index: u32 = 0;269 var line_index: u32 = 0;
...@@ -259,23 +271,22 @@ fn renderCmake(...@@ -259,23 +271,22 @@ fn renderCmake(
259 while (line_it.next()) |raw_line| : (line_index += 1) {271 while (line_it.next()) |raw_line| : (line_index += 1) {
260 const last_line = line_it.index == line_it.buffer.len;272 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) {
263 error.InvalidCharacter => {275 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", .{
265 src_path, line_index + 1,277 src_path, line_index + 1,
266 });278 });
267 any_errors = true;279 any_errors = true;
268 continue;280 continue;
269 },281 },
270 else => {282 else => {
271 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{283 try step.addError(maker, "{f}:{d}: failed substituting variable: {t}", .{
272 src_path, line_index + 1, @errorName(err),284 src_path, line_index + 1, err,
273 });285 });
274 any_errors = true;286 any_errors = true;
275 continue;287 continue;
276 },288 },
277 };289 };
278 defer allocator.free(line);
279290
280 const line_start = std.mem.findNone(u8, line, " \t\r") orelse {291 const line_start = std.mem.findNone(u8, line, " \t\r") orelse {
281 try w.writeAll(line);292 try w.writeAll(line);
...@@ -293,152 +304,134 @@ fn renderCmake(...@@ -293,152 +304,134 @@ fn renderCmake(
293304
294 var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r");305 var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r");
295 const cmakedefine = it.next().?;306 const cmakedefine = it.next().?;
296 if (!std.mem.eql(u8, cmakedefine, "cmakedefine") and307
297 !std.mem.eql(u8, cmakedefine, "cmakedefine01"))308 const booldefine = if (std.mem.eql(u8, cmakedefine, "cmakedefine01"))
298 {309 true
310 else if (std.mem.eql(u8, cmakedefine, "cmakedefine"))
311 false
312 else {
299 try w.writeAll(line);313 try w.writeAll(line);
300 if (!last_line) try w.writeByte('\n');314 if (!last_line) try w.writeByte('\n');
301 continue;315 continue;
302 }316 };
303
304 const booldefine = std.mem.eql(u8, cmakedefine, "cmakedefine01");
305317
306 const name = it.next() orelse {318 const name = it.next() orelse {
307 try step.addError("{s}:{d}: error: missing define name", .{319 try step.addError(maker, "{f}:{d}: error: missing define name", .{ src_path, line_index + 1 });
308 src_path, line_index + 1,
309 });
310 any_errors = true;320 any_errors = true;
311 continue;321 continue;
312 };322 };
313 var value = values_copy.get(name) orelse blk: {323 const orig_value: Value.Index = v: {
314 if (booldefine) {324 const index = value_map.getIndex(name) orelse break :v if (booldefine) .int_0 else .undef;
315 break :blk Value{ .int = 0 };325 value_map.values()[index] = true; // Mark as used.
316 }326 break :v value_pairs[index].index;
317 break :blk Value.undef;
318 };327 };
319328 const value = switch (orig_value.unpack(conf)) {
320 value = blk: {329 .bool => |b| if (!b) .undef else orig_value,
321 switch (value) {330 inline .i64, .u64 => |i| if (i == 0) .undef else orig_value,
322 .boolean => |b| {331 .string => |s| if (s.len == 0) .undef else orig_value,
323 if (!b) {332 else => orig_value,
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;
341 };333 };
342334
335 try w.writeAll(whitespace_prefix);
336
343 if (booldefine) {337 if (booldefine) {
344 value = blk: {338 try renderValueCBool(w, name, switch (value.unpack(conf)) {
345 switch (value) {339 .undef, .defined => false,
346 .undef => {340 .bool => |b| b,
347 break :blk Value{ .boolean = false };341 inline .u64, .i64 => |i| i != 0,
348 },342 .string => |s| s.len != 0,
349 .defined => {343 .ident => false,
350 break :blk Value{ .boolean = false };344 });
351 },345 } else if (value != .undef) {
352 .boolean => |b| {346 try renderValueCIdent(w, name, it.rest());
353 break :blk Value{ .boolean = b };347 } else {
354 },348 try renderValueC(conf, w, name, value);
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() };
369 }349 }
370
371 try w.writeAll(whitespace_prefix);
372 try renderValueC(w, name, value);
373 }350 }
374351
375 if (any_errors) {352 try ensureAllValuesUsed(maker, step, value_map, src_path);
376 return error.HeaderConfigFailed;353 if (any_errors) return error.MakeFailed;
377 }
378}354}
379355
380fn renderBlank(356fn renderBlank(
381 gpa: std.mem.Allocator,357 conf: *const Configuration,
382 w: *Writer,358 w: *Writer,
383 defines: std.array_hash_map.String(Value),359 value_pairs: []const Value.Pair,
360 value_map: *const ValueMap,
384 include_path: []const u8,361 include_path: []const u8,
385 include_guard_override: ?[]const u8,362 include_guard_override: ?[]const u8,
386) !void {363) !void {
387 try w.writeAll(c_generated_line);364 try w.writeAll(c_generated_line);
388365
389 const include_guard_name = include_guard_override orelse blk: {366 const include_guard_fmt: IncludeGuardFmt = .{
390 const name = try gpa.dupe(u8, include_path);367 .include_path = include_path,
391 for (name) |*byte| {368 .override = include_guard_override,
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;
399 };369 };
400 defer if (include_guard_override == null) gpa.free(include_guard_name);
401370
402 try w.print(371 try w.print(
403 \\#ifndef {[0]s}372 \\#ifndef {[0]f}
404 \\#define {[0]s}373 \\#define {[0]f}
405 \\374 \\
406 , .{include_guard_name});375 , .{include_guard_fmt});
407376
408 const values = defines.values();377 for (value_map.keys(), value_pairs) |name, pair| try renderValueC(conf, w, name, pair.index);
409 for (defines.keys(), 0..) |name, i| try renderValueC(w, name, values[i]);
410378
411 try w.print(379 try w.print(
412 \\#endif /* {s} */380 \\#endif /* {f} */
413 \\381 \\
414 , .{include_guard_name});382 , .{include_guard_fmt});
415}383}
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 {
418 try w.writeAll(asm_generated_line);405 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);
420}407}
421408
422fn renderValueC(w: *Writer, name: []const u8, value: Value) !void {409fn renderValueC(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void {
423 switch (value) {410 switch (value.unpack(conf)) {
424 .undef => try w.print("/* #undef {s} */\n", .{name}),411 .undef => try w.print("/* #undef {s} */\n", .{name}),
425 .defined => try w.print("#define {s}\n", .{name}),412 .defined => try w.print("#define {s}\n", .{name}),
426 .boolean => |b| try w.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),413 .bool => |b| return renderValueCBool(w, name, b),
427 .int => |i| try w.print("#define {s} {d}\n", .{ name, i }),414 inline .u64, .i64 => |i| try w.print("#define {s} {d}\n", .{ name, i }),
428 .ident => |ident| try w.print("#define {s} {s}\n", .{ name, ident }),415 .ident => |ident| return renderValueCIdent(w, name, ident),
429 // TODO: use C-specific escaping instead of zig string literals
430 .string => |string| try w.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),416 .string => |string| try w.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
431 }417 }
432}418}
433419
434fn renderValueNasm(w: *Writer, name: []const u8, value: Value) !void {420fn renderValueCIdent(w: *Writer, name: []const u8, ident: []const u8) Writer.Error!void {
435 switch (value) {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)) {
436 .undef => try w.print("; %undef {s}\n", .{name}),430 .undef => try w.print("; %undef {s}\n", .{name}),
437 .defined => try w.print("%define {s}\n", .{name}),431 .defined => try w.print("%define {s}\n", .{name}),
438 .boolean => |b| try w.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),432 .bool => |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 }),433 inline .u64, .i64 => |i| try w.print("%define {s} {d}\n", .{ name, i }),
440 .ident => |ident| try w.print("%define {s} {s}\n", .{ name, ident }),434 .ident => |ident| try w.print("%define {s} {s}\n", .{ name, ident }),
441 // TODO: use nasm-specific escaping instead of zig string literals
442 .string => |string| try w.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),435 .string => |string| try w.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
443 }436 }
444}437}
...@@ -446,8 +439,9 @@ fn renderValueNasm(w: *Writer, name: []const u8, value: Value) !void {...@@ -446,8 +439,9 @@ fn renderValueNasm(w: *Writer, name: []const u8, value: Value) !void {
446fn expandVariablesAutoconfAt(439fn expandVariablesAutoconfAt(
447 w: *Writer,440 w: *Writer,
448 contents: []const u8,441 contents: []const u8,
449 values: *const std.array_hash_map.String(Value),442 conf: *const Configuration,
450 used: []bool,443 value_pairs: []const Value.Pair,
444 value_map: *const ValueMap,
451) !void {445) !void {
452 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";446 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
453447
...@@ -467,18 +461,18 @@ fn expandVariablesAutoconfAt(...@@ -467,18 +461,18 @@ fn expandVariablesAutoconfAt(
467 }461 }
468462
469 const key = contents[curr + 1 .. close_pos];463 const key = contents[curr + 1 .. close_pos];
470 const index = values.getIndex(key) orelse {464 const index = value_map.getIndex(key) orelse {
471 // Report the missing key to the caller.465 // Report the missing key to the caller.
472 try w.writeAll(key);466 try w.writeAll(key);
473 return error.MissingValue;467 return error.MissingValue;
474 };468 };
475 const value = values.entries.slice().items(.value)[index];469 const value = value_pairs[index].index;
476 used[index] = true;470 value_map.values()[index] = true; // Mark as used.
477 try w.writeAll(contents[source_offset..curr]);471 try w.writeAll(contents[source_offset..curr]);
478 switch (value) {472 switch (value.unpack(conf)) {
479 .undef, .defined => {},473 .undef, .defined => {},
480 .boolean => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)),474 .bool => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)),
481 .int => |i| try w.print("{d}", .{i}),475 inline .u64, .i64 => |i| try w.print("{d}", .{i}),
482 .ident, .string => |s| try w.writeAll(s),476 .ident, .string => |s| try w.writeAll(s),
483 }477 }
484478
...@@ -491,12 +485,13 @@ fn expandVariablesAutoconfAt(...@@ -491,12 +485,13 @@ fn expandVariablesAutoconfAt(
491}485}
492486
493fn expandVariablesCmake(487fn expandVariablesCmake(
494 allocator: Allocator,488 arena: Allocator,
495 contents: []const u8,489 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,
497) ![]const u8 {493) ![]const u8 {
498 var result: std.array_list.Managed(u8) = .init(allocator);494 var result: std.ArrayList(u8) = .empty;
499 errdefer result.deinit();
500495
501 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";496 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";
502 const open_var = "${";497 const open_var = "${";
...@@ -507,8 +502,7 @@ fn expandVariablesCmake(...@@ -507,8 +502,7 @@ fn expandVariablesCmake(
507 source: usize,502 source: usize,
508 target: usize,503 target: usize,
509 };504 };
510 var var_stack: std.array_list.Managed(Position) = .init(allocator);505 var var_stack: std.ArrayList(Position) = .empty;
511 defer var_stack.deinit();
512 loop: while (curr < contents.len) : (curr += 1) {506 loop: while (curr < contents.len) : (curr += 1) {
513 switch (contents[curr]) {507 switch (contents[curr]) {
514 '@' => blk: {508 '@' => blk: {
...@@ -524,20 +518,16 @@ fn expandVariablesCmake(...@@ -524,20 +518,16 @@ fn expandVariablesCmake(
524 }518 }
525519
526 const key = contents[curr + 1 .. close_pos];520 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;
528 const missing = contents[source_offset..curr];524 const missing = contents[source_offset..curr];
529 try result.appendSlice(missing);525 try result.appendSlice(arena, missing);
530 switch (value) {526 switch (value.unpack(conf)) {
531 .undef, .defined => {},527 .undef, .defined => {},
532 .boolean => |b| {528 .bool => |b| try result.append(arena, if (b) '1' else '0'),
533 try result.append(if (b) '1' else '0');529 inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
534 },530 .ident, .string => |s| try result.appendSlice(arena, s),
535 .int => |i| {
536 try result.print("{d}", .{i});
537 },
538 .ident, .string => |s| {
539 try result.appendSlice(s);
540 },
541 }531 }
542532
543 curr = close_pos;533 curr = close_pos;
...@@ -553,12 +543,12 @@ fn expandVariablesCmake(...@@ -553,12 +543,12 @@ fn expandVariablesCmake(
553 break :blk;543 break :blk;
554 }544 }
555 const missing = contents[source_offset..curr];545 const missing = contents[source_offset..curr];
556 try result.appendSlice(missing);546 try result.appendSlice(arena, missing);
557 try result.appendSlice(open_var);547 try result.appendSlice(arena, open_var);
558548
559 source_offset = curr + open_var.len;549 source_offset = curr + open_var.len;
560 curr = next;550 curr = next;
561 try var_stack.append(Position{551 try var_stack.append(arena, .{
562 .source = curr,552 .source = curr,
563 .target = result.items.len - open_var.len,553 .target = result.items.len - open_var.len,
564 });554 });
...@@ -575,26 +565,22 @@ fn expandVariablesCmake(...@@ -575,26 +565,22 @@ fn expandVariablesCmake(
575 source_offset += open_var.len;565 source_offset += open_var.len;
576 }566 }
577 const missing = contents[source_offset..curr];567 const missing = contents[source_offset..curr];
578 try result.appendSlice(missing);568 try result.appendSlice(arena, missing);
579569
580 const key_start = open_pos.target + open_var.len;570 const key_start = open_pos.target + open_var.len;
581 const key = result.items[key_start..];571 const key = result.items[key_start..];
582 if (key.len == 0) {572 if (key.len == 0) {
583 return error.MissingKey;573 return error.MissingKey;
584 }574 }
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;
586 result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len);578 result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len);
587 switch (value) {579 switch (value.unpack(conf)) {
588 .undef, .defined => {},580 .undef, .defined => {},
589 .boolean => |b| {581 .bool => |b| try result.append(arena, if (b) '1' else '0'),
590 try result.append(if (b) '1' else '0');582 inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}),
591 },583 .ident, .string => |s| try result.appendSlice(arena, s),
592 .int => |i| {
593 try result.print("{d}", .{i});
594 },
595 .ident, .string => |s| {
596 try result.appendSlice(s);
597 },
598 }584 }
599585
600 source_offset = curr + 1;586 source_offset = curr + 1;
...@@ -615,320 +601,320 @@ fn expandVariablesCmake(...@@ -615,320 +601,320 @@ fn expandVariablesCmake(
615601
616 if (source_offset != contents.len) {602 if (source_offset != contents.len) {
617 const missing = contents[source_offset..];603 const missing = contents[source_offset..];
618 try result.appendSlice(missing);604 try result.appendSlice(arena, missing);
619 }605 }
620606
621 return result.toOwnedSlice();607 try result.shrinkToLen(arena);
608
609 return result.toOwnedSliceAssert();
622}610}
623611
624fn testReplaceVariablesAutoconfAt(612fn testReplaceVariablesAutoconfAt(
625 allocator: Allocator,613 arena: Allocator,
626 contents: []const u8,614 contents: []const u8,
627 expected: []const u8,615 expected: []const u8,
628 values: std.array_hash_map.String(Value),616 value_map: *const ValueMap,
629) !void {617) !void {
630 var aw: Writer.Allocating = .init(allocator);618 var aw: Writer.Allocating = .init(arena);
631 defer aw.deinit();619 defer aw.deinit();
632620
633 const used = try allocator.alloc(bool, values.count());621 const used = try arena.alloc(bool, value_map.count());
634 for (used) |*u| u.* = false;622 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
639 for (used) |u| if (!u) return error.UnusedValue;626 for (used) |u| if (!u) return error.UnusedValue;
640 try std.testing.expectEqualStrings(expected, aw.written());627 try std.testing.expectEqualStrings(expected, aw.written());
641}628}
642629
643fn testReplaceVariablesCMake(630fn testReplaceVariablesCMake(
644 allocator: Allocator,631 arena: Allocator,
645 contents: []const u8,632 contents: []const u8,
646 expected: []const u8,633 expected: []const u8,
647 values: std.array_hash_map.String(Value),634 value_map: *const ValueMap,
648) !void {635) !void {
649 const actual = try expandVariablesCmake(allocator, contents, values);636 const actual = try expandVariablesCmake(arena, contents, value_map);
650 defer allocator.free(actual);
651637
652 try std.testing.expectEqualStrings(expected, actual);638 try std.testing.expectEqualStrings(expected, actual);
653}639}
654640
655test "expandVariablesAutoconfAt simple cases" {641test "expandVariablesAutoconfAt simple cases" {
656 const allocator = std.testing.allocator;642 const allocator = std.testing.allocator;
657 var values: std.array_hash_map.String(Value) = .init(allocator);643 var value_map: ValueMap = .empty;
658 defer values.deinit();644 defer value_map.deinit();
659645
660 // empty strings are preserved646 // empty strings are preserved
661 try testReplaceVariablesAutoconfAt(allocator, "", "", values);647 try testReplaceVariablesAutoconfAt(allocator, "", "", value_map);
662648
663 // line with misc content is preserved649 // 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
666 // empty @ sigils are preserved652 // empty @ sigils are preserved
667 try testReplaceVariablesAutoconfAt(allocator, "@", "@", values);653 try testReplaceVariablesAutoconfAt(allocator, "@", "@", value_map);
668 try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", values);654 try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", value_map);
669 try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", values);655 try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", value_map);
670 try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", values);656 try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", value_map);
671657
672 // simple substitution658 // simple substitution
673 try values.putNoClobber("undef", .undef);659 try value_map.putNoClobber("undef", .undef);
674 try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", values);660 try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", value_map);
675 values.clearRetainingCapacity();661 value_map.clearRetainingCapacity();
676662
677 try values.putNoClobber("defined", .defined);663 try value_map.putNoClobber("defined", .defined);
678 try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", values);664 try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", value_map);
679 values.clearRetainingCapacity();665 value_map.clearRetainingCapacity();
680666
681 try values.putNoClobber("true", Value{ .boolean = true });667 try value_map.putNoClobber("true", Value{ .boolean = true });
682 try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", values);668 try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", value_map);
683 values.clearRetainingCapacity();669 value_map.clearRetainingCapacity();
684670
685 try values.putNoClobber("false", Value{ .boolean = false });671 try value_map.putNoClobber("false", Value{ .boolean = false });
686 try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", values);672 try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", value_map);
687 values.clearRetainingCapacity();673 value_map.clearRetainingCapacity();
688674
689 try values.putNoClobber("int", Value{ .int = 42 });675 try value_map.putNoClobber("int", Value{ .int = 42 });
690 try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", values);676 try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", value_map);
691 values.clearRetainingCapacity();677 value_map.clearRetainingCapacity();
692678
693 try values.putNoClobber("ident", Value{ .string = "value" });679 try value_map.putNoClobber("ident", Value{ .string = "value" });
694 try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", values);680 try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", value_map);
695 values.clearRetainingCapacity();681 value_map.clearRetainingCapacity();
696682
697 try values.putNoClobber("string", Value{ .string = "text" });683 try value_map.putNoClobber("string", Value{ .string = "text" });
698 try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", values);684 try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", value_map);
699 values.clearRetainingCapacity();685 value_map.clearRetainingCapacity();
700686
701 // double packed substitution687 // double packed substitution
702 try values.putNoClobber("string", Value{ .string = "text" });688 try value_map.putNoClobber("string", Value{ .string = "text" });
703 try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", values);689 try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", value_map);
704 values.clearRetainingCapacity();690 value_map.clearRetainingCapacity();
705691
706 // triple packed substitution692 // triple packed substitution
707 try values.putNoClobber("int", Value{ .int = 42 });693 try value_map.putNoClobber("int", Value{ .int = 42 });
708 try values.putNoClobber("string", Value{ .string = "text" });694 try value_map.putNoClobber("string", Value{ .string = "text" });
709 try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", values);695 try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", value_map);
710 values.clearRetainingCapacity();696 value_map.clearRetainingCapacity();
711697
712 // double separated substitution698 // double separated substitution
713 try values.putNoClobber("int", Value{ .int = 42 });699 try value_map.putNoClobber("int", Value{ .int = 42 });
714 try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", values);700 try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", value_map);
715 values.clearRetainingCapacity();701 value_map.clearRetainingCapacity();
716702
717 // triple separated substitution703 // triple separated substitution
718 try values.putNoClobber("true", Value{ .boolean = true });704 try value_map.putNoClobber("true", Value{ .boolean = true });
719 try values.putNoClobber("int", Value{ .int = 42 });705 try value_map.putNoClobber("int", Value{ .int = 42 });
720 try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", values);706 try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", value_map);
721 values.clearRetainingCapacity();707 value_map.clearRetainingCapacity();
722708
723 // misc prefix is preserved709 // misc prefix is preserved
724 try values.putNoClobber("false", Value{ .boolean = false });710 try value_map.putNoClobber("false", Value{ .boolean = false });
725 try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", values);711 try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", value_map);
726 values.clearRetainingCapacity();712 value_map.clearRetainingCapacity();
727713
728 // misc suffix is preserved714 // misc suffix is preserved
729 try values.putNoClobber("true", Value{ .boolean = true });715 try value_map.putNoClobber("true", Value{ .boolean = true });
730 try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", values);716 try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", value_map);
731 values.clearRetainingCapacity();717 value_map.clearRetainingCapacity();
732718
733 // surrounding content is preserved719 // surrounding content is preserved
734 try values.putNoClobber("int", Value{ .int = 42 });720 try value_map.putNoClobber("int", Value{ .int = 42 });
735 try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values);721 try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", value_map);
736 values.clearRetainingCapacity();722 value_map.clearRetainingCapacity();
737723
738 // incomplete key is preserved724 // incomplete key is preserved
739 try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", values);725 try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", value_map);
740726
741 // unknown key leads to an error727 // 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
744 // unused key leads to an error730 // unused key leads to an error
745 try values.putNoClobber("int", Value{ .int = 42 });731 try value_map.putNoClobber("int", Value{ .int = 42 });
746 try values.putNoClobber("false", Value{ .boolean = false });732 try value_map.putNoClobber("false", Value{ .boolean = false });
747 try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", values));733 try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", value_map));
748 values.clearRetainingCapacity();734 value_map.clearRetainingCapacity();
749}735}
750736
751test "expandVariablesAutoconfAt edge cases" {737test "expandVariablesAutoconfAt edge cases" {
752 const allocator = std.testing.allocator;738 const allocator = std.testing.allocator;
753 var values: std.array_hash_map.String(Value) = .init(allocator);739 var value_map: std.array_hash_map.String(Value) = .init(allocator);
754 defer values.deinit();740 defer value_map.deinit();
755741
756 // @-vars resolved only when they wrap valid characters, otherwise considered literals742 // @-vars resolved only when they wrap valid characters, otherwise considered literals
757 try values.putNoClobber("string", Value{ .string = "text" });743 try value_map.putNoClobber("string", Value{ .string = "text" });
758 try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", values);744 try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", value_map);
759 values.clearRetainingCapacity();745 value_map.clearRetainingCapacity();
760746
761 // expanded variables are considered strings after expansion747 // expanded variables are considered strings after expansion
762 try values.putNoClobber("string_at", Value{ .string = "@string@" });748 try value_map.putNoClobber("string_at", Value{ .string = "@string@" });
763 try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", values);749 try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", value_map);
764 values.clearRetainingCapacity();750 value_map.clearRetainingCapacity();
765}751}
766752
767test "expandVariablesCmake simple cases" {753test "expandVariablesCmake simple cases" {
768 const allocator = std.testing.allocator;754 const allocator = std.testing.allocator;
769 var values: std.array_hash_map.String(Value) = .init(allocator);755 var value_map: std.array_hash_map.String(Value) = .init(allocator);
770 defer values.deinit();756 defer value_map.deinit();
771757
772 try values.putNoClobber("undef", .undef);758 try value_map.putNoClobber("undef", .undef);
773 try values.putNoClobber("defined", .defined);759 try value_map.putNoClobber("defined", .defined);
774 try values.putNoClobber("true", Value{ .boolean = true });760 try value_map.putNoClobber("true", Value{ .boolean = true });
775 try values.putNoClobber("false", Value{ .boolean = false });761 try value_map.putNoClobber("false", Value{ .boolean = false });
776 try values.putNoClobber("int", Value{ .int = 42 });762 try value_map.putNoClobber("int", Value{ .int = 42 });
777 try values.putNoClobber("ident", Value{ .string = "value" });763 try value_map.putNoClobber("ident", Value{ .string = "value" });
778 try values.putNoClobber("string", Value{ .string = "text" });764 try value_map.putNoClobber("string", Value{ .string = "text" });
779765
780 // empty strings are preserved766 // empty strings are preserved
781 try testReplaceVariablesCMake(allocator, "", "", values);767 try testReplaceVariablesCMake(allocator, "", "", value_map);
782768
783 // line with misc content is preserved769 // 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
786 // empty ${} wrapper leads to an error772 // 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
789 // empty @ sigils are preserved775 // empty @ sigils are preserved
790 try testReplaceVariablesCMake(allocator, "@", "@", values);776 try testReplaceVariablesCMake(allocator, "@", "@", value_map);
791 try testReplaceVariablesCMake(allocator, "@@", "@@", values);777 try testReplaceVariablesCMake(allocator, "@@", "@@", value_map);
792 try testReplaceVariablesCMake(allocator, "@@@", "@@@", values);778 try testReplaceVariablesCMake(allocator, "@@@", "@@@", value_map);
793 try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", values);779 try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", value_map);
794780
795 // simple substitution781 // simple substitution
796 try testReplaceVariablesCMake(allocator, "@undef@", "", values);782 try testReplaceVariablesCMake(allocator, "@undef@", "", value_map);
797 try testReplaceVariablesCMake(allocator, "${undef}", "", values);783 try testReplaceVariablesCMake(allocator, "${undef}", "", value_map);
798 try testReplaceVariablesCMake(allocator, "@defined@", "", values);784 try testReplaceVariablesCMake(allocator, "@defined@", "", value_map);
799 try testReplaceVariablesCMake(allocator, "${defined}", "", values);785 try testReplaceVariablesCMake(allocator, "${defined}", "", value_map);
800 try testReplaceVariablesCMake(allocator, "@true@", "1", values);786 try testReplaceVariablesCMake(allocator, "@true@", "1", value_map);
801 try testReplaceVariablesCMake(allocator, "${true}", "1", values);787 try testReplaceVariablesCMake(allocator, "${true}", "1", value_map);
802 try testReplaceVariablesCMake(allocator, "@false@", "0", values);788 try testReplaceVariablesCMake(allocator, "@false@", "0", value_map);
803 try testReplaceVariablesCMake(allocator, "${false}", "0", values);789 try testReplaceVariablesCMake(allocator, "${false}", "0", value_map);
804 try testReplaceVariablesCMake(allocator, "@int@", "42", values);790 try testReplaceVariablesCMake(allocator, "@int@", "42", value_map);
805 try testReplaceVariablesCMake(allocator, "${int}", "42", values);791 try testReplaceVariablesCMake(allocator, "${int}", "42", value_map);
806 try testReplaceVariablesCMake(allocator, "@ident@", "value", values);792 try testReplaceVariablesCMake(allocator, "@ident@", "value", value_map);
807 try testReplaceVariablesCMake(allocator, "${ident}", "value", values);793 try testReplaceVariablesCMake(allocator, "${ident}", "value", value_map);
808 try testReplaceVariablesCMake(allocator, "@string@", "text", values);794 try testReplaceVariablesCMake(allocator, "@string@", "text", value_map);
809 try testReplaceVariablesCMake(allocator, "${string}", "text", values);795 try testReplaceVariablesCMake(allocator, "${string}", "text", value_map);
810796
811 // double packed substitution797 // double packed substitution
812 try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", values);798 try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", value_map);
813 try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", values);799 try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", value_map);
814800
815 // triple packed substitution801 // triple packed substitution
816 try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", values);802 try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", value_map);
817 try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", values);803 try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", value_map);
818 try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", values);804 try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", value_map);
819 try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", values);805 try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", value_map);
820806
821 // double separated substitution807 // double separated substitution
822 try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", values);808 try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", value_map);
823 try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", values);809 try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", value_map);
824810
825 // triple separated substitution811 // triple separated substitution
826 try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", values);812 try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", value_map);
827 try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", values);813 try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", value_map);
828 try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", values);814 try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", value_map);
829 try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", values);815 try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", value_map);
830816
831 // misc prefix is preserved817 // misc prefix is preserved
832 try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", values);818 try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", value_map);
833 try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", values);819 try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", value_map);
834820
835 // misc suffix is preserved821 // misc suffix is preserved
836 try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", values);822 try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", value_map);
837 try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", values);823 try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", value_map);
838824
839 // surrounding content is preserved825 // surrounding content is preserved
840 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);
841 try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", values);827 try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", value_map);
842828
843 // incomplete key is preserved829 // incomplete key is preserved
844 try testReplaceVariablesCMake(allocator, "@undef", "@undef", values);830 try testReplaceVariablesCMake(allocator, "@undef", "@undef", value_map);
845 try testReplaceVariablesCMake(allocator, "${undef", "${undef", values);831 try testReplaceVariablesCMake(allocator, "${undef", "${undef", value_map);
846 try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", values);832 try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", value_map);
847 try testReplaceVariablesCMake(allocator, "undef@", "undef@", values);833 try testReplaceVariablesCMake(allocator, "undef@", "undef@", value_map);
848 try testReplaceVariablesCMake(allocator, "undef}", "undef}", values);834 try testReplaceVariablesCMake(allocator, "undef}", "undef}", value_map);
849835
850 // unknown key leads to an error836 // unknown key leads to an error
851 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", values));837 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", value_map));
852 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", values));838 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", value_map));
853}839}
854840
855test "expandVariablesCmake edge cases" {841test "expandVariablesCmake edge cases" {
856 const allocator = std.testing.allocator;842 const allocator = std.testing.allocator;
857 var values: std.array_hash_map.String(Value) = .init(allocator);843 var value_map: std.array_hash_map.String(Value) = .init(allocator);
858 defer values.deinit();844 defer value_map.deinit();
859845
860 // special symbols846 // special symbols
861 try values.putNoClobber("at", Value{ .string = "@" });847 try value_map.putNoClobber("at", Value{ .string = "@" });
862 try values.putNoClobber("dollar", Value{ .string = "$" });848 try value_map.putNoClobber("dollar", Value{ .string = "$" });
863 try values.putNoClobber("underscore", Value{ .string = "_" });849 try value_map.putNoClobber("underscore", Value{ .string = "_" });
864850
865 // basic value851 // basic value
866 try values.putNoClobber("string", Value{ .string = "text" });852 try value_map.putNoClobber("string", Value{ .string = "text" });
867853
868 // proxy case values854 // proxy case value_map
869 try values.putNoClobber("string_proxy", Value{ .string = "string" });855 try value_map.putNoClobber("string_proxy", Value{ .string = "string" });
870 try values.putNoClobber("string_at", Value{ .string = "@string@" });856 try value_map.putNoClobber("string_at", Value{ .string = "@string@" });
871 try values.putNoClobber("string_curly", Value{ .string = "{string}" });857 try value_map.putNoClobber("string_curly", Value{ .string = "{string}" });
872 try values.putNoClobber("string_var", Value{ .string = "${string}" });858 try value_map.putNoClobber("string_var", Value{ .string = "${string}" });
873859
874 // stack case values860 // stack case value_map
875 try values.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" });861 try value_map.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" });
876 try values.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" });862 try value_map.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" });
877863
878 // @-vars resolved only when they wrap valid characters, otherwise considered literals864 // @-vars resolved only when they wrap valid characters, otherwise considered literals
879 try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", values);865 try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", value_map);
880 try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", values);866 try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", value_map);
881867
882 // @-vars are resolved inside ${}-vars868 // @-vars are resolved inside ${}-vars
883 try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", values);869 try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", value_map);
884870
885 // expanded variables are considered strings after expansion871 // expanded variables are considered strings after expansion
886 try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", values);872 try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", value_map);
887 try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", values);873 try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", value_map);
888 try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", values);874 try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", value_map);
889 try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", values);875 try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", value_map);
890 try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", values);876 try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", value_map);
891 try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", values);877 try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", value_map);
892 try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", values);878 try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", value_map);
893 try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", values);879 try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", value_map);
894 try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", values);880 try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", value_map);
895881
896 // when expanded variables contain invalid characters, they prevent further expansion882 // when expanded variables contain invalid characters, they prevent further expansion
897 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", values));883 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", value_map));
898 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", values));884 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", value_map));
899885
900 // nested expanded variables are expanded from the inside out886 // nested expanded variables are expanded from the inside out
901 try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", values);887 try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", value_map);
902 try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", values);888 try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", value_map);
903889
904 // nested vars are only expanded when ${} is closed890 // nested vars are only expanded when ${} is closed
905 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", values));891 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", value_map));
906 try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", values);892 try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", value_map);
907 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", values));893 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", value_map));
908 try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", values);894 try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", value_map);
909895
910 // invalid characters lead to an error896 // invalid characters lead to an error
911 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", values));897 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", value_map));
912 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", values));898 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", value_map));
913 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", values));899 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", value_map));
914}900}
915901
916test "expandVariablesCmake escaped characters" {902test "expandVariablesCmake escaped characters" {
917 const allocator = std.testing.allocator;903 const allocator = std.testing.allocator;
918 var values: std.array_hash_map.String(Value) = .init(allocator);904 var value_map: std.array_hash_map.String(Value) = .init(allocator);
919 defer values.deinit();905 defer value_map.deinit();
920906
921 try values.putNoClobber("string", Value{ .string = "text" });907 try value_map.putNoClobber("string", Value{ .string = "text" });
922908
923 // backslash is an invalid character for @ lookup909 // backslash is an invalid character for @ lookup
924 try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", values);910 try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", value_map);
925911
926 // backslash is preserved, but doesn't affect ${} variable expansion912 // 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
929 // backslash breaks ${} opening bracket identification915 // backslash breaks ${} opening bracket identification
930 try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", values);916 try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", value_map);
931917
932 // backslash is skipped when checking for invalid characters, yet it mangles the key918 // 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));
934}920}
lib/std/Build/Configuration.zig+32
...@@ -1117,6 +1117,38 @@ pub const Step = extern struct {...@@ -1117,6 +1117,38 @@ pub const Step = extern struct {
1117 undef = max_u32 - 1,1117 undef = max_u32 - 1,
1118 defined = max_u32,1118 defined = max_u32,
1119 _,1119 _,
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,
1120 };1152 };
11211153
1122 pub fn initSigned(x: i64) @This() {1154 pub fn initSigned(x: i64) @This() {
test/standalone/cmakedefine/build.zig-1
...@@ -48,7 +48,6 @@ pub fn build(b: *std.Build) void {...@@ -48,7 +48,6 @@ pub fn build(b: *std.Build) void {
48 .include_path = "stack.h",48 .include_path = "stack.h",
49 },49 },
50 .{50 .{
51 .AT = "@",
52 .UNDERSCORE = "_",51 .UNDERSCORE = "_",
53 .NEST_UNDERSCORE_PROXY = "UNDERSCORE",52 .NEST_UNDERSCORE_PROXY = "UNDERSCORE",
54 .NEST_PROXY = "NEST_UNDERSCORE_PROXY",53 .NEST_PROXY = "NEST_UNDERSCORE_PROXY",