authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-04 17:28:48-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:35-07:00
logd3ec255a1f7cd38164de816462c29de4a2981490
tree6a66fb9eaea32c84562b289522dcf2cbd1e2c5fc
parentbeef9fdf42726d8cfe3017ff3017767fb615ef08

more progress towards zig's build.zig compiling


15 files changed, 1117 insertions(+), 1028 deletions(-)

BRANCH_TODO+4-1
......@@ -27,9 +27,12 @@
2727 - but artifact install steps also add paths for dyn libs on windows
2828* no more "artifact arg" to run step. if you want to run the post-install binary, get the lazy path
2929 from the install step.
30* build system fmt step with check=false does not acquire a write lock on source files #35204
3130* fmt step: import zig fmt code directly rather than child proc
3231
32## Already Filed Followup Issues
33* build system fmt step with check=false does not acquire a write lock on source files #35204
34* enhance CheckFile step output when there is not a match #35208
35
3336## Release Notes
3437
3538### Run Step: Passthru Args
lib/compiler/Maker/Step.zig+22-1
......@@ -69,7 +69,7 @@ pub const Extended = union(enum) {
6969 check_file: Todo,
7070 compile: Compile,
7171 config_header: Todo,
72 fail: Todo,
72 fail: Fail,
7373 find_program: Todo,
7474 fmt: Todo,
7575 install_artifact: InstallArtifact,
......@@ -133,6 +133,27 @@ pub const Extended = union(enum) {
133133 _ = progress_node;
134134 }
135135 };
136
137 pub const Fail = struct {
138 pub fn make(
139 this: *@This(),
140 step_index: Configuration.Step.Index,
141 maker: *Maker,
142 progress_node: std.Progress.Node,
143 ) Step.ExtendedMakeError!void {
144 _ = this;
145 _ = progress_node;
146 const graph = maker.graph;
147 const arena = graph.arena; // TODO don't leak into the process arena
148 const conf = &maker.scanned_config.configuration;
149 const step = maker.stepByIndex(step_index);
150 const conf_step = step_index.ptr(conf);
151 const conf_fail = conf_step.extended.get(conf.extra).fail;
152
153 try step.result_error_msgs.append(arena, conf_fail.msg.slice(conf));
154 return error.MakeFailed;
155 }
156 };
136157};
137158
138159pub const State = enum {
lib/compiler/Maker/Step/CheckFile.zig created+60
......@@ -0,0 +1,60 @@
1const CheckFile = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Configuration = std.Build.Configuration;
6
7const Step = @import("../Step.zig");
8const Maker = @import("../../Maker.zig");
9
10pub fn make(
11 check_file: *CheckFile,
12 step_index: Configuration.Step.Index,
13 maker: *Maker,
14 progress_node: std.Progress.Node,
15) Step.ExtendedMakeError!void {
16 _ = progress_node;
17 const graph = maker.graph;
18 const arena = maker.graph.arena; // TODO don't leak into process arena
19 const io = graph.io;
20 const step = maker.stepByIndex(step_index);
21 const conf = &maker.scanned_config.configuration;
22 const conf_step = step_index.ptr(conf);
23 const conf_cf = conf_step.extended.get(conf.extra).install_file;
24 const lazy_path = conf_cf.file.get(conf);
25
26 try step.singleUnchangingWatchInput(maker, arena, lazy_path);
27
28 const src_path = try maker.resolveLazyPath(arena, lazy_path, step_index);
29 const limit: Io.Limit = if (conf_cf.max_bytes.value) |x| .limited(x) else .unlimited;
30
31 const contents = src_path.root_dir.handle.readFileAlloc(io, src_path.sub_path, arena, limit) catch |err|
32 return step.fail("failed to read {f}: {t}", .{ src_path, err });
33
34 for (check_file.expected_matches) |expected_match| {
35 if (std.mem.find(u8, contents, expected_match) == null) {
36 return step.fail(
37 \\
38 \\========= expected to find: ===================
39 \\{s}
40 \\========= but file does not contain it: =======
41 \\{s}
42 \\===============================================
43 , .{ expected_match, contents });
44 }
45 }
46
47 if (check_file.expected_exact) |expected_exact| {
48 if (!std.mem.eql(u8, expected_exact, contents)) {
49 return step.fail(
50 \\
51 \\========= expected: =====================
52 \\{s}
53 \\========= but found: ====================
54 \\{s}
55 \\========= from the following file: ======
56 \\{s}
57 , .{ expected_exact, contents, src_path });
58 }
59 }
60}
lib/compiler/Maker/Step/ConfigHeader.zig created+903
......@@ -0,0 +1,903 @@
1const ConfigHeader = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Configuration = std.Build.Configuration;
6const Writer = std.Io.Writer;
7
8const Step = @import("../Step.zig");
9const Maker = @import("../../Maker.zig");
10
11pub fn make(
12 config_header: *ConfigHeader,
13 step_index: Configuration.Step.Index,
14 maker: *Maker,
15 progress_node: std.Progress.Node,
16) Step.ExtendedMakeError!void {
17 const graph = maker.graph;
18 const arena = maker.graph.arena; // TODO don't leak into process arena
19 const step = maker.stepByIndex(step_index);
20 const io = graph.io;
21
22 if (config_header.style.getPath()) |lp|
23 try step.singleUnchangingWatchInput(maker, arena, lp);
24
25 var man = graph.cache.obtain();
26 defer man.deinit();
27
28 // Random bytes to make ConfigHeader unique. Refresh this with new
29 // random bytes when ConfigHeader implementation is modified in a
30 // non-backwards-compatible way.
31 man.hash.add(@as(u32, 0xdef08d23));
32 man.hash.addBytes(config_header.include_path);
33 man.hash.addOptionalBytes(config_header.include_guard_override);
34
35 var aw: Writer.Allocating = .init(arena);
36 defer aw.deinit();
37 const bw = &aw.writer;
38
39 const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
40 const c_generated_line = "/* " ++ header_text ++ " */\n";
41 const asm_generated_line = "; " ++ header_text ++ "\n";
42
43 switch (config_header.style) {
44 .autoconf_undef, .autoconf_at => |file_source| {
45 try bw.writeAll(c_generated_line);
46 const src_path = file_source.getPath2(b, step);
47 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
48 return step.fail("unable to read autoconf input file {s}: {t}", .{ src_path, err });
49 };
50 switch (config_header.style) {
51 .autoconf_undef => try render_autoconf_undef(step, contents, bw, &config_header.values, src_path),
52 .autoconf_at => try render_autoconf_at(step, contents, &aw, &config_header.values, src_path),
53 else => unreachable,
54 }
55 },
56 .cmake => |file_source| {
57 try bw.writeAll(c_generated_line);
58 const src_path = file_source.getPath2(b, step);
59 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
60 return step.fail("unable to read cmake input file {s}: {t}", .{ src_path, err });
61 };
62 try render_cmake(step, contents, bw, config_header.values, src_path);
63 },
64 .blank => {
65 try bw.writeAll(c_generated_line);
66 try render_blank(gpa, bw, config_header.values, config_header.include_path, config_header.include_guard_override);
67 },
68 .nasm => {
69 try bw.writeAll(asm_generated_line);
70 try render_nasm(bw, config_header.values);
71 },
72 }
73
74 const output = aw.written();
75 man.hash.addBytes(output);
76
77 if (try step.cacheHit(&man)) {
78 const digest = man.final();
79 config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest });
80 return;
81 }
82
83 const digest = man.final();
84
85 // If output_path has directory parts, deal with them. Example:
86 // output_dir is zig-cache/o/HASH
87 // output_path is libavutil/avconfig.h
88 // We want to open directory zig-cache/o/HASH/libavutil/
89 // but keep output_dir as zig-cache/o/HASH for -I include
90 const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path });
91 const sub_path_dirname = std.fs.path.dirname(sub_path).?;
92
93 b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| {
94 return step.fail("unable to make path '{f}{s}': {s}", .{
95 b.cache_root, sub_path_dirname, @errorName(err),
96 });
97 };
98
99 b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = output }) catch |err| {
100 return step.fail("unable to write file '{f}{s}': {s}", .{
101 b.cache_root, sub_path, @errorName(err),
102 });
103 };
104
105 config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest });
106 try man.writeManifest();
107}
108
109
110fn render_autoconf_undef(
111 step: *Step,
112 contents: []const u8,
113 bw: *Writer,
114 values: *const std.array_hash_map.String(Value),
115 src_path: []const u8,
116) !void {
117 const build = step.owner;
118 const allocator = build.allocator;
119
120 var is_used: std.bit_set.Dynamic = try .initEmpty(allocator, values.count());
121 defer is_used.deinit(allocator);
122
123 var any_errors = false;
124 var line_index: u32 = 0;
125 var line_it = std.mem.splitScalar(u8, contents, '\n');
126 while (line_it.next()) |line| : (line_index += 1) {
127 if (!std.mem.startsWith(u8, line, "#")) {
128 try bw.writeAll(line);
129 try bw.writeByte('\n');
130 continue;
131 }
132 var it = std.mem.tokenizeAny(u8, line[1..], " \t\r");
133 const undef = it.next().?;
134 if (!std.mem.eql(u8, undef, "undef")) {
135 try bw.writeAll(line);
136 try bw.writeByte('\n');
137 continue;
138 }
139 const name = it.next().?;
140 const index = values.getIndex(name) orelse {
141 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
142 src_path, line_index + 1, name,
143 });
144 any_errors = true;
145 continue;
146 };
147 is_used.set(index);
148 try renderValueC(bw, name, values.values()[index]);
149 }
150
151 var unused_value_it = is_used.iterator(.{ .kind = .unset });
152 while (unused_value_it.next()) |index| {
153 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, values.keys()[index] });
154 any_errors = true;
155 }
156
157 if (any_errors) {
158 return error.MakeFailed;
159 }
160}
161
162fn render_autoconf_at(
163 step: *Step,
164 contents: []const u8,
165 aw: *Writer.Allocating,
166 values: *const std.array_hash_map.String(Value),
167 src_path: []const u8,
168) !void {
169 const build = step.owner;
170 const allocator = build.allocator;
171 const bw = &aw.writer;
172
173 const used = allocator.alloc(bool, values.count()) catch @panic("OOM");
174 for (used) |*u| u.* = false;
175 defer allocator.free(used);
176
177 var any_errors = false;
178 var line_index: u32 = 0;
179 var line_it = std.mem.splitScalar(u8, contents, '\n');
180 while (line_it.next()) |line| : (line_index += 1) {
181 const last_line = line_it.index == line_it.buffer.len;
182
183 const old_len = aw.written().len;
184 expand_variables_autoconf_at(bw, line, values, used) catch |err| switch (err) {
185 error.MissingValue => {
186 const name = aw.written()[old_len..];
187 defer aw.shrinkRetainingCapacity(old_len);
188 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
189 src_path, line_index + 1, name,
190 });
191 any_errors = true;
192 continue;
193 },
194 else => {
195 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{
196 src_path, line_index + 1, @errorName(err),
197 });
198 any_errors = true;
199 continue;
200 },
201 };
202 if (!last_line) try bw.writeByte('\n');
203 }
204
205 for (values.entries.slice().items(.key), used) |name, u| {
206 if (!u) {
207 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
208 any_errors = true;
209 }
210 }
211
212 if (any_errors) return error.MakeFailed;
213}
214
215fn render_cmake(
216 step: *Step,
217 contents: []const u8,
218 bw: *Writer,
219 values: std.array_hash_map.String(Value),
220 src_path: []const u8,
221) !void {
222 const build = step.owner;
223 const allocator = build.allocator;
224
225 var values_copy = try values.clone(allocator);
226 defer values_copy.deinit(allocator);
227
228 var any_errors = false;
229 var line_index: u32 = 0;
230 var line_it = std.mem.splitScalar(u8, contents, '\n');
231 while (line_it.next()) |raw_line| : (line_index += 1) {
232 const last_line = line_it.index == line_it.buffer.len;
233
234 const line = expand_variables_cmake(allocator, raw_line, values) catch |err| switch (err) {
235 error.InvalidCharacter => {
236 try step.addError("{s}:{d}: error: invalid character in a variable name", .{
237 src_path, line_index + 1,
238 });
239 any_errors = true;
240 continue;
241 },
242 else => {
243 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{
244 src_path, line_index + 1, @errorName(err),
245 });
246 any_errors = true;
247 continue;
248 },
249 };
250 defer allocator.free(line);
251
252 const line_start = std.mem.findNone(u8, line, " \t\r") orelse {
253 try bw.writeAll(line);
254 if (!last_line) try bw.writeByte('\n');
255 continue;
256 };
257 const whitespace_prefix = line[0..line_start];
258 const trimmed_line = line[line_start..];
259
260 if (!std.mem.startsWith(u8, trimmed_line, "#")) {
261 try bw.writeAll(line);
262 if (!last_line) try bw.writeByte('\n');
263 continue;
264 }
265
266 var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r");
267 const cmakedefine = it.next().?;
268 if (!std.mem.eql(u8, cmakedefine, "cmakedefine") and
269 !std.mem.eql(u8, cmakedefine, "cmakedefine01"))
270 {
271 try bw.writeAll(line);
272 if (!last_line) try bw.writeByte('\n');
273 continue;
274 }
275
276 const booldefine = std.mem.eql(u8, cmakedefine, "cmakedefine01");
277
278 const name = it.next() orelse {
279 try step.addError("{s}:{d}: error: missing define name", .{
280 src_path, line_index + 1,
281 });
282 any_errors = true;
283 continue;
284 };
285 var value = values_copy.get(name) orelse blk: {
286 if (booldefine) {
287 break :blk Value{ .int = 0 };
288 }
289 break :blk Value.undef;
290 };
291
292 value = blk: {
293 switch (value) {
294 .boolean => |b| {
295 if (!b) {
296 break :blk Value.undef;
297 }
298 },
299 .int => |i| {
300 if (i == 0) {
301 break :blk Value.undef;
302 }
303 },
304 .string => |string| {
305 if (string.len == 0) {
306 break :blk Value.undef;
307 }
308 },
309
310 else => {},
311 }
312 break :blk value;
313 };
314
315 if (booldefine) {
316 value = blk: {
317 switch (value) {
318 .undef => {
319 break :blk Value{ .boolean = false };
320 },
321 .defined => {
322 break :blk Value{ .boolean = false };
323 },
324 .boolean => |b| {
325 break :blk Value{ .boolean = b };
326 },
327 .int => |i| {
328 break :blk Value{ .boolean = i != 0 };
329 },
330 .string => |string| {
331 break :blk Value{ .boolean = string.len != 0 };
332 },
333
334 else => {
335 break :blk Value{ .boolean = false };
336 },
337 }
338 };
339 } else if (value != Value.undef) {
340 value = Value{ .ident = it.rest() };
341 }
342
343 try bw.writeAll(whitespace_prefix);
344 try renderValueC(bw, name, value);
345 }
346
347 if (any_errors) {
348 return error.HeaderConfigFailed;
349 }
350}
351
352fn render_blank(
353 gpa: std.mem.Allocator,
354 bw: *Writer,
355 defines: std.array_hash_map.String(Value),
356 include_path: []const u8,
357 include_guard_override: ?[]const u8,
358) !void {
359 const include_guard_name = include_guard_override orelse blk: {
360 const name = try gpa.dupe(u8, include_path);
361 for (name) |*byte| {
362 switch (byte.*) {
363 'a'...'z' => byte.* = byte.* - 'a' + 'A',
364 'A'...'Z', '0'...'9' => continue,
365 else => byte.* = '_',
366 }
367 }
368 break :blk name;
369 };
370 defer if (include_guard_override == null) gpa.free(include_guard_name);
371
372 try bw.print(
373 \\#ifndef {[0]s}
374 \\#define {[0]s}
375 \\
376 , .{include_guard_name});
377
378 const values = defines.values();
379 for (defines.keys(), 0..) |name, i| try renderValueC(bw, name, values[i]);
380
381 try bw.print(
382 \\#endif /* {s} */
383 \\
384 , .{include_guard_name});
385}
386
387fn render_nasm(bw: *Writer, defines: std.array_hash_map.String(Value)) !void {
388 for (defines.keys(), defines.values()) |name, value| try renderValueNasm(bw, name, value);
389}
390
391fn renderValueC(bw: *Writer, name: []const u8, value: Value) !void {
392 switch (value) {
393 .undef => try bw.print("/* #undef {s} */\n", .{name}),
394 .defined => try bw.print("#define {s}\n", .{name}),
395 .boolean => |b| try bw.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
396 .int => |i| try bw.print("#define {s} {d}\n", .{ name, i }),
397 .ident => |ident| try bw.print("#define {s} {s}\n", .{ name, ident }),
398 // TODO: use C-specific escaping instead of zig string literals
399 .string => |string| try bw.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
400 }
401}
402
403fn renderValueNasm(bw: *Writer, name: []const u8, value: Value) !void {
404 switch (value) {
405 .undef => try bw.print("; %undef {s}\n", .{name}),
406 .defined => try bw.print("%define {s}\n", .{name}),
407 .boolean => |b| try bw.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
408 .int => |i| try bw.print("%define {s} {d}\n", .{ name, i }),
409 .ident => |ident| try bw.print("%define {s} {s}\n", .{ name, ident }),
410 // TODO: use nasm-specific escaping instead of zig string literals
411 .string => |string| try bw.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
412 }
413}
414
415fn expand_variables_autoconf_at(
416 bw: *Writer,
417 contents: []const u8,
418 values: *const std.array_hash_map.String(Value),
419 used: []bool,
420) !void {
421 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
422
423 var curr: usize = 0;
424 var source_offset: usize = 0;
425 while (curr < contents.len) : (curr += 1) {
426 if (contents[curr] != '@') continue;
427 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
428 if (close_pos == curr + 1) {
429 // closed immediately, preserve as a literal
430 continue;
431 }
432 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
433 if (valid_varname_end != close_pos) {
434 // contains invalid characters, preserve as a literal
435 continue;
436 }
437
438 const key = contents[curr + 1 .. close_pos];
439 const index = values.getIndex(key) orelse {
440 // Report the missing key to the caller.
441 try bw.writeAll(key);
442 return error.MissingValue;
443 };
444 const value = values.entries.slice().items(.value)[index];
445 used[index] = true;
446 try bw.writeAll(contents[source_offset..curr]);
447 switch (value) {
448 .undef, .defined => {},
449 .boolean => |b| try bw.writeByte(@as(u8, '0') + @intFromBool(b)),
450 .int => |i| try bw.print("{d}", .{i}),
451 .ident, .string => |s| try bw.writeAll(s),
452 }
453
454 curr = close_pos;
455 source_offset = close_pos + 1;
456 }
457 }
458
459 try bw.writeAll(contents[source_offset..]);
460}
461
462fn expand_variables_cmake(
463 allocator: Allocator,
464 contents: []const u8,
465 values: std.array_hash_map.String(Value),
466) ![]const u8 {
467 var result: std.array_list.Managed(u8) = .init(allocator);
468 errdefer result.deinit();
469
470 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";
471 const open_var = "${";
472
473 var curr: usize = 0;
474 var source_offset: usize = 0;
475 const Position = struct {
476 source: usize,
477 target: usize,
478 };
479 var var_stack: std.array_list.Managed(Position) = .init(allocator);
480 defer var_stack.deinit();
481 loop: while (curr < contents.len) : (curr += 1) {
482 switch (contents[curr]) {
483 '@' => blk: {
484 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
485 if (close_pos == curr + 1) {
486 // closed immediately, preserve as a literal
487 break :blk;
488 }
489 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
490 if (valid_varname_end != close_pos) {
491 // contains invalid characters, preserve as a literal
492 break :blk;
493 }
494
495 const key = contents[curr + 1 .. close_pos];
496 const value = values.get(key) orelse return error.MissingValue;
497 const missing = contents[source_offset..curr];
498 try result.appendSlice(missing);
499 switch (value) {
500 .undef, .defined => {},
501 .boolean => |b| {
502 try result.append(if (b) '1' else '0');
503 },
504 .int => |i| {
505 try result.print("{d}", .{i});
506 },
507 .ident, .string => |s| {
508 try result.appendSlice(s);
509 },
510 }
511
512 curr = close_pos;
513 source_offset = close_pos + 1;
514
515 continue :loop;
516 }
517 },
518 '$' => blk: {
519 const next = curr + 1;
520 if (next == contents.len or contents[next] != '{') {
521 // no open bracket detected, preserve as a literal
522 break :blk;
523 }
524 const missing = contents[source_offset..curr];
525 try result.appendSlice(missing);
526 try result.appendSlice(open_var);
527
528 source_offset = curr + open_var.len;
529 curr = next;
530 try var_stack.append(Position{
531 .source = curr,
532 .target = result.items.len - open_var.len,
533 });
534
535 continue :loop;
536 },
537 '}' => blk: {
538 if (var_stack.items.len == 0) {
539 // no open bracket, preserve as a literal
540 break :blk;
541 }
542 const open_pos = var_stack.pop().?;
543 if (source_offset == open_pos.source) {
544 source_offset += open_var.len;
545 }
546 const missing = contents[source_offset..curr];
547 try result.appendSlice(missing);
548
549 const key_start = open_pos.target + open_var.len;
550 const key = result.items[key_start..];
551 if (key.len == 0) {
552 return error.MissingKey;
553 }
554 const value = values.get(key) orelse return error.MissingValue;
555 result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len);
556 switch (value) {
557 .undef, .defined => {},
558 .boolean => |b| {
559 try result.append(if (b) '1' else '0');
560 },
561 .int => |i| {
562 try result.print("{d}", .{i});
563 },
564 .ident, .string => |s| {
565 try result.appendSlice(s);
566 },
567 }
568
569 source_offset = curr + 1;
570
571 continue :loop;
572 },
573 '\\' => {
574 // backslash is not considered a special character
575 continue :loop;
576 },
577 else => {},
578 }
579
580 if (var_stack.items.len > 0 and std.mem.findScalar(u8, valid_varname_chars, contents[curr]) == null) {
581 return error.InvalidCharacter;
582 }
583 }
584
585 if (source_offset != contents.len) {
586 const missing = contents[source_offset..];
587 try result.appendSlice(missing);
588 }
589
590 return result.toOwnedSlice();
591}
592
593fn testReplaceVariablesAutoconfAt(
594 allocator: Allocator,
595 contents: []const u8,
596 expected: []const u8,
597 values: std.array_hash_map.String(Value),
598) !void {
599 var aw: Writer.Allocating = .init(allocator);
600 defer aw.deinit();
601
602 const used = try allocator.alloc(bool, values.count());
603 for (used) |*u| u.* = false;
604 defer allocator.free(used);
605
606 try expand_variables_autoconf_at(&aw.writer, contents, values, used);
607
608 for (used) |u| if (!u) return error.UnusedValue;
609 try std.testing.expectEqualStrings(expected, aw.written());
610}
611
612fn testReplaceVariablesCMake(
613 allocator: Allocator,
614 contents: []const u8,
615 expected: []const u8,
616 values: std.array_hash_map.String(Value),
617) !void {
618 const actual = try expand_variables_cmake(allocator, contents, values);
619 defer allocator.free(actual);
620
621 try std.testing.expectEqualStrings(expected, actual);
622}
623
624test "expand_variables_autoconf_at simple cases" {
625 const allocator = std.testing.allocator;
626 var values: std.array_hash_map.String(Value) = .init(allocator);
627 defer values.deinit();
628
629 // empty strings are preserved
630 try testReplaceVariablesAutoconfAt(allocator, "", "", values);
631
632 // line with misc content is preserved
633 try testReplaceVariablesAutoconfAt(allocator, "no substitution", "no substitution", values);
634
635 // empty @ sigils are preserved
636 try testReplaceVariablesAutoconfAt(allocator, "@", "@", values);
637 try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", values);
638 try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", values);
639 try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", values);
640
641 // simple substitution
642 try values.putNoClobber("undef", .undef);
643 try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", values);
644 values.clearRetainingCapacity();
645
646 try values.putNoClobber("defined", .defined);
647 try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", values);
648 values.clearRetainingCapacity();
649
650 try values.putNoClobber("true", Value{ .boolean = true });
651 try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", values);
652 values.clearRetainingCapacity();
653
654 try values.putNoClobber("false", Value{ .boolean = false });
655 try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", values);
656 values.clearRetainingCapacity();
657
658 try values.putNoClobber("int", Value{ .int = 42 });
659 try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", values);
660 values.clearRetainingCapacity();
661
662 try values.putNoClobber("ident", Value{ .string = "value" });
663 try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", values);
664 values.clearRetainingCapacity();
665
666 try values.putNoClobber("string", Value{ .string = "text" });
667 try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", values);
668 values.clearRetainingCapacity();
669
670 // double packed substitution
671 try values.putNoClobber("string", Value{ .string = "text" });
672 try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", values);
673 values.clearRetainingCapacity();
674
675 // triple packed substitution
676 try values.putNoClobber("int", Value{ .int = 42 });
677 try values.putNoClobber("string", Value{ .string = "text" });
678 try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", values);
679 values.clearRetainingCapacity();
680
681 // double separated substitution
682 try values.putNoClobber("int", Value{ .int = 42 });
683 try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", values);
684 values.clearRetainingCapacity();
685
686 // triple separated substitution
687 try values.putNoClobber("true", Value{ .boolean = true });
688 try values.putNoClobber("int", Value{ .int = 42 });
689 try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", values);
690 values.clearRetainingCapacity();
691
692 // misc prefix is preserved
693 try values.putNoClobber("false", Value{ .boolean = false });
694 try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", values);
695 values.clearRetainingCapacity();
696
697 // misc suffix is preserved
698 try values.putNoClobber("true", Value{ .boolean = true });
699 try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", values);
700 values.clearRetainingCapacity();
701
702 // surrounding content is preserved
703 try values.putNoClobber("int", Value{ .int = 42 });
704 try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values);
705 values.clearRetainingCapacity();
706
707 // incomplete key is preserved
708 try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", values);
709
710 // unknown key leads to an error
711 try std.testing.expectError(error.MissingValue, testReplaceVariablesAutoconfAt(allocator, "@bad@", "", values));
712
713 // unused key leads to an error
714 try values.putNoClobber("int", Value{ .int = 42 });
715 try values.putNoClobber("false", Value{ .boolean = false });
716 try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", values));
717 values.clearRetainingCapacity();
718}
719
720test "expand_variables_autoconf_at edge cases" {
721 const allocator = std.testing.allocator;
722 var values: std.array_hash_map.String(Value) = .init(allocator);
723 defer values.deinit();
724
725 // @-vars resolved only when they wrap valid characters, otherwise considered literals
726 try values.putNoClobber("string", Value{ .string = "text" });
727 try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", values);
728 values.clearRetainingCapacity();
729
730 // expanded variables are considered strings after expansion
731 try values.putNoClobber("string_at", Value{ .string = "@string@" });
732 try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", values);
733 values.clearRetainingCapacity();
734}
735
736test "expand_variables_cmake simple cases" {
737 const allocator = std.testing.allocator;
738 var values: std.array_hash_map.String(Value) = .init(allocator);
739 defer values.deinit();
740
741 try values.putNoClobber("undef", .undef);
742 try values.putNoClobber("defined", .defined);
743 try values.putNoClobber("true", Value{ .boolean = true });
744 try values.putNoClobber("false", Value{ .boolean = false });
745 try values.putNoClobber("int", Value{ .int = 42 });
746 try values.putNoClobber("ident", Value{ .string = "value" });
747 try values.putNoClobber("string", Value{ .string = "text" });
748
749 // empty strings are preserved
750 try testReplaceVariablesCMake(allocator, "", "", values);
751
752 // line with misc content is preserved
753 try testReplaceVariablesCMake(allocator, "no substitution", "no substitution", values);
754
755 // empty ${} wrapper leads to an error
756 try std.testing.expectError(error.MissingKey, testReplaceVariablesCMake(allocator, "${}", "", values));
757
758 // empty @ sigils are preserved
759 try testReplaceVariablesCMake(allocator, "@", "@", values);
760 try testReplaceVariablesCMake(allocator, "@@", "@@", values);
761 try testReplaceVariablesCMake(allocator, "@@@", "@@@", values);
762 try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", values);
763
764 // simple substitution
765 try testReplaceVariablesCMake(allocator, "@undef@", "", values);
766 try testReplaceVariablesCMake(allocator, "${undef}", "", values);
767 try testReplaceVariablesCMake(allocator, "@defined@", "", values);
768 try testReplaceVariablesCMake(allocator, "${defined}", "", values);
769 try testReplaceVariablesCMake(allocator, "@true@", "1", values);
770 try testReplaceVariablesCMake(allocator, "${true}", "1", values);
771 try testReplaceVariablesCMake(allocator, "@false@", "0", values);
772 try testReplaceVariablesCMake(allocator, "${false}", "0", values);
773 try testReplaceVariablesCMake(allocator, "@int@", "42", values);
774 try testReplaceVariablesCMake(allocator, "${int}", "42", values);
775 try testReplaceVariablesCMake(allocator, "@ident@", "value", values);
776 try testReplaceVariablesCMake(allocator, "${ident}", "value", values);
777 try testReplaceVariablesCMake(allocator, "@string@", "text", values);
778 try testReplaceVariablesCMake(allocator, "${string}", "text", values);
779
780 // double packed substitution
781 try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", values);
782 try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", values);
783
784 // triple packed substitution
785 try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", values);
786 try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", values);
787 try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", values);
788 try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", values);
789
790 // double separated substitution
791 try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", values);
792 try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", values);
793
794 // triple separated substitution
795 try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", values);
796 try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", values);
797 try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", values);
798 try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", values);
799
800 // misc prefix is preserved
801 try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", values);
802 try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", values);
803
804 // misc suffix is preserved
805 try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", values);
806 try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", values);
807
808 // surrounding content is preserved
809 try testReplaceVariablesCMake(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values);
810 try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", values);
811
812 // incomplete key is preserved
813 try testReplaceVariablesCMake(allocator, "@undef", "@undef", values);
814 try testReplaceVariablesCMake(allocator, "${undef", "${undef", values);
815 try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", values);
816 try testReplaceVariablesCMake(allocator, "undef@", "undef@", values);
817 try testReplaceVariablesCMake(allocator, "undef}", "undef}", values);
818
819 // unknown key leads to an error
820 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", values));
821 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", values));
822}
823
824test "expand_variables_cmake edge cases" {
825 const allocator = std.testing.allocator;
826 var values: std.array_hash_map.String(Value) = .init(allocator);
827 defer values.deinit();
828
829 // special symbols
830 try values.putNoClobber("at", Value{ .string = "@" });
831 try values.putNoClobber("dollar", Value{ .string = "$" });
832 try values.putNoClobber("underscore", Value{ .string = "_" });
833
834 // basic value
835 try values.putNoClobber("string", Value{ .string = "text" });
836
837 // proxy case values
838 try values.putNoClobber("string_proxy", Value{ .string = "string" });
839 try values.putNoClobber("string_at", Value{ .string = "@string@" });
840 try values.putNoClobber("string_curly", Value{ .string = "{string}" });
841 try values.putNoClobber("string_var", Value{ .string = "${string}" });
842
843 // stack case values
844 try values.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" });
845 try values.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" });
846
847 // @-vars resolved only when they wrap valid characters, otherwise considered literals
848 try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", values);
849 try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", values);
850
851 // @-vars are resolved inside ${}-vars
852 try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", values);
853
854 // expanded variables are considered strings after expansion
855 try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", values);
856 try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", values);
857 try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", values);
858 try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", values);
859 try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", values);
860 try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", values);
861 try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", values);
862 try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", values);
863 try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", values);
864
865 // when expanded variables contain invalid characters, they prevent further expansion
866 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", values));
867 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", values));
868
869 // nested expanded variables are expanded from the inside out
870 try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", values);
871 try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", values);
872
873 // nested vars are only expanded when ${} is closed
874 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", values));
875 try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", values);
876 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", values));
877 try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", values);
878
879 // invalid characters lead to an error
880 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", values));
881 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", values));
882 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", values));
883}
884
885test "expand_variables_cmake escaped characters" {
886 const allocator = std.testing.allocator;
887 var values: std.array_hash_map.String(Value) = .init(allocator);
888 defer values.deinit();
889
890 try values.putNoClobber("string", Value{ .string = "text" });
891
892 // backslash is an invalid character for @ lookup
893 try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", values);
894
895 // backslash is preserved, but doesn't affect ${} variable expansion
896 try testReplaceVariablesCMake(allocator, "\\${string}", "\\text", values);
897
898 // backslash breaks ${} opening bracket identification
899 try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", values);
900
901 // backslash is skipped when checking for invalid characters, yet it mangles the key
902 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${string\\}", "", values));
903}
lib/compiler/configurer.zig+6-1
......@@ -861,7 +861,12 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
861861 },
862862 .install_dir => @panic("TODO"),
863863 .remove_dir => @panic("TODO"),
864 .fail => @panic("TODO"),
864 .fail => e: {
865 const sf: *Step.Fail = @fieldParentPtr("step", step);
866 break :e @enumFromInt(try wc.addExtra(@as(Configuration.Step.Fail, .{
867 .msg = sf.error_msg,
868 })));
869 },
865870 .find_program => @panic("TODO"),
866871 .fmt => @panic("TODO"),
867872 .translate_c => @panic("TODO"),
lib/std/Build.zig+46-10
......@@ -146,6 +146,22 @@ pub const Graph = struct {
146146 pub fn create(graph: *const Graph, comptime T: type) *T {
147147 return @ptrCast(graph.arena.allocBytesAligned(.of(T), @sizeOf(T), @returnAddress()) catch @panic("OOM"));
148148 }
149
150 pub fn addBytesList(graph: *Graph, bytes_list: []const []const u8) []const Configuration.Bytes {
151 const result = graph.alloc(Configuration.Bytes, bytes_list.len);
152 for (result, bytes_list) |*d, s| d.* = addBytes(graph, s);
153 return result;
154 }
155
156 pub fn addBytes(graph: *Graph, bytes: []const u8) Configuration.Bytes {
157 const wc = &graph.wip_configuration;
158 return wc.addBytes(bytes) catch @panic("OOM");
159 }
160
161 pub fn addString(graph: *Graph, bytes: []const u8) Configuration.String {
162 const wc = &graph.wip_configuration;
163 return wc.addString(bytes) catch @panic("OOM");
164 }
149165};
150166
151167const AvailableDeps = []const struct { []const u8, []const u8 };
......@@ -530,13 +546,17 @@ const OrderedUserValue = union(enum) {
530546 hasher.update(sp.sub_path);
531547 },
532548 .generated => |gen| {
533 hasher.update(std.mem.asBytes(&gen.index));
534 hasher.update(std.mem.asBytes(&gen.up));
549 hasher.update(@ptrCast(&gen.index));
550 hasher.update(@ptrCast(&gen.up));
535551 hasher.update(gen.sub_path);
536552 },
537553 .cwd_relative => |rel_path| {
538554 hasher.update(rel_path);
539555 },
556 .relative => |r| {
557 hasher.update(@ptrCast(&r.base));
558 hasher.update(@ptrCast(&r.sub_path));
559 },
540560 .dependency => |dep| {
541561 hasher.update(dep.dependency.builder.pkg_hash);
542562 hasher.update(dep.sub_path);
......@@ -1824,16 +1844,19 @@ inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, c
18241844 if (@hasDecl(pkg, "build_zig") and pkg.build_zig == asking_build_zig) break .{ pkg_hash, pkg.deps };
18251845 } else .{ "", deps.root_deps };
18261846 if (!std.mem.eql(u8, b_pkg_hash, b.pkg_hash)) {
1827 panic("'{}' is not the struct that corresponds to '{s}'", .{
1828 asking_build_zig, b.pathFromRoot("build.zig"),
1847 const build_zig_path = b.root.join("build.zig") catch @panic("OOM");
1848 panic("{} is not the struct that corresponds to {f}", .{
1849 asking_build_zig, build_zig_path,
18291850 });
18301851 }
18311852 comptime for (b_pkg_deps) |dep| {
18321853 if (std.mem.eql(u8, dep[0], dep_name)) return dep[1];
18331854 };
18341855
1835 const full_path = b.pathFromRoot("build.zig.zon");
1836 panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ dep_name, full_path });
1856 const full_path = b.root.join("build.zig.zon") catch @panic("OOM");
1857 panic("no dependency named {s} in {f}. All packages used in build.zig must be declared in this file", .{
1858 dep_name, full_path,
1859 });
18371860}
18381861
18391862fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void {
......@@ -1935,6 +1958,8 @@ pub fn dependencyFromBuildZig(
19351958) *Dependency {
19361959 const build_runner = @import("root");
19371960 const deps = build_runner.dependencies;
1961 const graph = b.graph;
1962 const arena = graph.arena;
19381963
19391964 find_dep: {
19401965 const pkg, const pkg_hash = inline for (@typeInfo(deps.packages).@"struct".decls) |decl| {
......@@ -1948,8 +1973,8 @@ pub fn dependencyFromBuildZig(
19481973 return dependencyInner(b, dep_name, pkg.build_root, pkg.build_zig, pkg_hash, pkg.deps, args);
19491974 }
19501975
1951 const full_path = b.pathFromRoot("build.zig.zon");
1952 panic("'{}' is not a build.zig struct of a dependency in '{s}'", .{ build_zig, full_path });
1976 const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM");
1977 panic("{} is not a build.zig struct of a dependency in {f}", .{ build_zig, full_path });
19531978}
19541979
19551980fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool {
......@@ -2024,6 +2049,7 @@ fn userLazyPathsAreTheSame(lhs_lp: LazyPath, rhs_lp: LazyPath) bool {
20242049
20252050 if (!std.mem.eql(u8, lhs_rel_path, rhs_rel_path)) return false;
20262051 },
2052 .relative => |lhs| return lhs.eql(rhs_lp.relative),
20272053 .dependency => |lhs_dep| {
20282054 const rhs_dep = rhs_lp.dependency;
20292055
......@@ -2150,6 +2176,10 @@ pub const LazyPath = union(enum) {
21502176 relative: struct {
21512177 base: Configuration.Path.Base,
21522178 sub_path: Configuration.String = .empty,
2179
2180 pub fn eql(a: @This(), b: @This()) bool {
2181 return a.base == b.base and a.sub_path == b.sub_path;
2182 }
21532183 },
21542184
21552185 /// Path to the Zig executable being used to execute "zig build".
......@@ -2177,11 +2207,11 @@ pub const LazyPath = union(enum) {
21772207 },
21782208 } },
21792209 .generated => |generated| .{ .generated = if (dirnameAllowEmpty(generated.sub_path)) |sub_dirname| .{
2180 .file = generated.file,
2210 .index = generated.index,
21812211 .up = generated.up,
21822212 .sub_path = sub_dirname,
21832213 } else .{
2184 .file = generated.file,
2214 .index = generated.index,
21852215 .up = generated.up + 1,
21862216 .sub_path = "",
21872217 } },
......@@ -2208,6 +2238,9 @@ pub const LazyPath = union(enum) {
22082238 }
22092239 },
22102240 },
2241 .relative => .{
2242 .relative = @panic("TODO"),
2243 },
22112244 .dependency => |dep| .{ .dependency = .{
22122245 .dependency = dep.dependency,
22132246 .sub_path = dirnameAllowEmpty(dep.sub_path) orelse {
......@@ -2239,6 +2272,9 @@ pub const LazyPath = union(enum) {
22392272 .cwd_relative => |cwd_relative| .{
22402273 .cwd_relative = try fs.path.resolve(arena, &.{ cwd_relative, sub_path }),
22412274 },
2275 .relative => .{
2276 .relative = @panic("TODO"),
2277 },
22422278 .dependency => |dep| .{ .dependency = .{
22432279 .dependency = dep.dependency,
22442280 .sub_path = try fs.path.resolve(arena, &.{ dep.sub_path, sub_path }),
lib/std/Build/Configuration.zig+10-2
......@@ -1011,10 +1011,17 @@ pub const Step = extern struct {
10111011
10121012 pub const CheckFile = struct {
10131013 flags: @This().Flags,
1014 file: LazyPath.Index,
1015 expected_exact: Storage.FlagOptional(.flags, .expected_exact, Bytes),
1016 expected_matches: Storage.FlagLengthPrefixedList(.flags, .expected_matches, Bytes),
1017 max_bytes: Storage.FlagOptional(.flags, .max_bytes, u32),
10141018
10151019 pub const Flags = packed struct(u32) {
10161020 tag: Tag = .check_file,
1017 _: u27 = 0,
1021 expected_exact: bool,
1022 expected_matches: bool,
1023 max_bytes: bool,
1024 _: u24 = 0,
10181025 };
10191026 };
10201027
......@@ -1028,7 +1035,8 @@ pub const Step = extern struct {
10281035 };
10291036
10301037 pub const Fail = struct {
1031 flags: @This().Flags,
1038 flags: @This().Flags = .{},
1039 msg: String,
10321040
10331041 pub const Flags = packed struct(u32) {
10341042 tag: Tag = .fail,
lib/std/Build/Module.zig+2-3
......@@ -145,14 +145,13 @@ pub const RcSourceFile = struct {
145145 /// as `/I <resolved path>`.
146146 include_paths: []const LazyPath = &.{},
147147
148 pub fn dupe(file: RcSourceFile, b: *std.Build) RcSourceFile {
149 const graph = b.owner.graph;
148 pub fn dupe(file: RcSourceFile, graph: *const std.Build.Graph) RcSourceFile {
150149 const arena = graph.arena;
151150 const include_paths = arena.alloc(LazyPath, file.include_paths.len) catch @panic("OOM");
152151 for (include_paths, file.include_paths) |*dest, lazy_path| dest.* = lazy_path.dupe(graph);
153152 return .{
154153 .file = file.file.dupe(graph),
155 .flags = b.dupeStrings(file.flags),
154 .flags = graph.dupeStrings(file.flags),
156155 .include_paths = include_paths,
157156 };
158157 }
lib/std/Build/Step/CheckFile.zig+15-61
......@@ -1,7 +1,4 @@
11//! Fail the build step if a file does not match certain checks.
2//! TODO: make this more flexible, supporting more kinds of checks.
3//! TODO: generalize the code in std.testing.expectEqualStrings and make this
4//! CheckFile step produce those helpful diagnostics when there is not a match.
52const CheckFile = @This();
63
74const std = @import("std");
......@@ -9,83 +6,40 @@ const Io = std.Io;
96const Step = std.Build.Step;
107const fs = std.fs;
118const mem = std.mem;
9const Configuration = std.Build.Configuration;
1210
1311step: Step,
14expected_matches: []const []const u8,
15expected_exact: ?[]const u8,
16source: std.Build.LazyPath,
17max_bytes: usize = 20 * 1024 * 1024,
12file: std.Build.LazyPath,
13expected_matches: []const Configuration.Bytes,
14expected_exact: ?Configuration.Bytes,
15max_bytes: ?u32,
1816
1917pub const base_tag: Step.Tag = .check_file;
2018
2119pub const Options = struct {
2220 expected_matches: []const []const u8 = &.{},
2321 expected_exact: ?[]const u8 = null,
22 max_bytes: ?u32 = null,
2423};
2524
26pub fn create(
27 owner: *std.Build,
28 source: std.Build.LazyPath,
29 options: Options,
30) *CheckFile {
31 const check_file = owner.allocator.create(CheckFile) catch @panic("OOM");
25pub fn create(owner: *std.Build, file: std.Build.LazyPath, options: Options) *CheckFile {
26 const graph = owner.graph;
27 const check_file = graph.create(CheckFile);
3228 check_file.* = .{
33 .step = Step.init(.{
29 .step = .init(.{
3430 .tag = base_tag,
3531 .name = "CheckFile",
3632 .owner = owner,
37 .makeFn = make,
3833 }),
39 .source = source.dupe(owner),
40 .expected_matches = owner.dupeStrings(options.expected_matches),
41 .expected_exact = options.expected_exact,
34 .file = file.dupe(graph),
35 .expected_matches = graph.addBytesList(options.expected_matches),
36 .expected_exact = if (options.expected_exact) |b| graph.addBytes(b) else null,
37 .max_bytes = options.max_bytes,
4238 };
43 check_file.source.addStepDependencies(&check_file.step);
39 file.addStepDependencies(&check_file.step);
4440 return check_file;
4541}
4642
4743pub fn setName(check_file: *CheckFile, name: []const u8) void {
4844 check_file.step.name = name;
4945}
50
51fn make(step: *Step, options: Step.MakeOptions) !void {
52 _ = options;
53 const b = step.owner;
54 const io = b.graph.io;
55 const check_file: *CheckFile = @fieldParentPtr("step", step);
56 try step.singleUnchangingWatchInput(check_file.source);
57
58 const src_path = check_file.source.getPath2(b, step);
59 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, b.allocator, .limited(check_file.max_bytes)) catch |err| {
60 return step.fail("unable to read '{s}': {s}", .{
61 src_path, @errorName(err),
62 });
63 };
64
65 for (check_file.expected_matches) |expected_match| {
66 if (mem.find(u8, contents, expected_match) == null) {
67 return step.fail(
68 \\
69 \\========= expected to find: ===================
70 \\{s}
71 \\========= but file does not contain it: =======
72 \\{s}
73 \\===============================================
74 , .{ expected_match, contents });
75 }
76 }
77
78 if (check_file.expected_exact) |expected_exact| {
79 if (!mem.eql(u8, expected_exact, contents)) {
80 return step.fail(
81 \\
82 \\========= expected: =====================
83 \\{s}
84 \\========= but found: ====================
85 \\{s}
86 \\========= from the following file: ======
87 \\{s}
88 , .{ expected_exact, contents, src_path });
89 }
90 }
91}
lib/std/Build/Step/Compile.zig+9-9
......@@ -296,7 +296,7 @@ pub const HeaderInstallation = union(enum) {
296296 source: LazyPath,
297297 dest_rel_path: []const u8,
298298
299 pub fn dupe(file: File, graph: *std.Build.Graph) File {
299 pub fn dupe(file: File, graph: *const std.Build.Graph) File {
300300 return .{
301301 .source = file.source.dupe(graph),
302302 .dest_rel_path = graph.dupePath(file.dest_rel_path),
......@@ -317,7 +317,7 @@ pub const HeaderInstallation = union(enum) {
317317 /// `exclude_extensions` takes precedence over `include_extensions`.
318318 include_extensions: ?[]const []const u8 = &.{".h"},
319319
320 pub fn dupe(opts: Directory.Options, graph: *std.Build.Graph) Directory.Options {
320 pub fn dupe(opts: Directory.Options, graph: *const std.Build.Graph) Directory.Options {
321321 return .{
322322 .exclude_extensions = graph.dupeStrings(opts.exclude_extensions),
323323 .include_extensions = if (opts.include_extensions) |incs| graph.dupeStrings(incs) else null,
......@@ -325,11 +325,11 @@ pub const HeaderInstallation = union(enum) {
325325 }
326326 };
327327
328 pub fn dupe(dir: Directory, b: *std.Build) Directory {
328 pub fn dupe(dir: Directory, graph: *const std.Build.Graph) Directory {
329329 return .{
330 .source = dir.source.dupe(b),
331 .dest_rel_path = b.dupePath(dir.dest_rel_path),
332 .options = dir.options.dupe(b),
330 .source = dir.source.dupe(graph),
331 .dest_rel_path = graph.dupePath(dir.dest_rel_path),
332 .options = dir.options.dupe(graph),
333333 };
334334 }
335335 };
......@@ -340,10 +340,10 @@ pub const HeaderInstallation = union(enum) {
340340 };
341341 }
342342
343 pub fn dupe(installation: HeaderInstallation, b: *std.Build) HeaderInstallation {
343 pub fn dupe(installation: HeaderInstallation, graph: *const std.Build.Graph) HeaderInstallation {
344344 return switch (installation) {
345 .file => |f| .{ .file = f.dupe(b) },
346 .directory => |d| .{ .directory = d.dupe(b) },
345 .file => |f| .{ .file = f.dupe(graph) },
346 .directory => |d| .{ .directory = d.dupe(graph) },
347347 };
348348 }
349349};
lib/std/Build/Step/ConfigHeader.zig+22-915
......@@ -4,9 +4,20 @@ const std = @import("std");
44const Io = std.Io;
55const Step = std.Build.Step;
66const Allocator = std.mem.Allocator;
7const Writer = std.Io.Writer;
87const Configuration = std.Build.Configuration;
98
9step: Step,
10values: std.array_hash_map.String(Value),
11/// This directory contains the generated file under the name `include_path`.
12generated_dir: Configuration.GeneratedFileIndex,
13
14style: Style,
15max_bytes: usize,
16include_path: []const u8,
17include_guard_override: ?[]const u8,
18
19pub const base_tag: Step.Tag = .config_header;
20
1021pub const Style = union(enum) {
1122 /// A configure format supported by autotools that uses `#undef foo` to
1223 /// mark lines that can be substituted with different values.
......@@ -38,18 +49,6 @@ pub const Value = union(enum) {
3849 string: []const u8,
3950};
4051
41step: Step,
42values: std.array_hash_map.String(Value),
43/// This directory contains the generated file under the name `include_path`.
44generated_dir: Configuration.GeneratedFileIndex,
45
46style: Style,
47max_bytes: usize,
48include_path: []const u8,
49include_guard_override: ?[]const u8,
50
51pub const base_tag: Step.Tag = .config_header;
52
5352pub const Options = struct {
5453 style: Style = .blank,
5554 max_bytes: usize = 2 * 1024 * 1024,
......@@ -66,10 +65,12 @@ pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
6665 var include_path: []const u8 = "config.h";
6766
6867 if (options.style.getPath()) |s| default_include_path: {
68 const wc = &graph.wip_configuration;
6969 const sub_path = switch (s) {
7070 .src_path => |sp| sp.sub_path,
7171 .generated => break :default_include_path,
7272 .cwd_relative => |sub_path| sub_path,
73 .relative => |r| wc.stringSlice(r.sub_path),
7374 .dependency => |dependency| dependency.sub_path,
7475 };
7576 const basename = std.fs.path.basename(sub_path);
......@@ -92,14 +93,13 @@ pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
9293 .tag = base_tag,
9394 .name = name,
9495 .owner = owner,
95 .makeFn = make,
9696 .first_ret_addr = options.first_ret_addr orelse @returnAddress(),
9797 }),
9898 .style = options.style,
9999 .values = .empty,
100100
101101 .max_bytes = options.max_bytes,
102 .include_path = include_path,
102 .include_path = graph.dupeString(include_path),
103103 .include_guard_override = options.include_guard_override,
104104 .generated_dir = graph.addGeneratedFile(&config_header.step),
105105 };
......@@ -119,19 +119,6 @@ pub fn addValue(config_header: *ConfigHeader, name: []const u8, comptime T: type
119119 return addValueInner(config_header, name, T, value) catch @panic("OOM");
120120}
121121
122pub fn addValues(config_header: *ConfigHeader, values: anytype) void {
123 inline for (@typeInfo(@TypeOf(values)).@"struct".fields) |field| {
124 addValue(config_header, field.name, field.type, @field(values, field.name));
125 }
126}
127
128pub fn getOutputDir(ch: *ConfigHeader) std.Build.LazyPath {
129 return .{ .generated = .{ .index = &ch.generated_dir } };
130}
131pub fn getOutputFile(ch: *ConfigHeader) std.Build.LazyPath {
132 return ch.getOutputDir().path(ch.step.owner, ch.include_path);
133}
134
135122fn addValueInner(config_header: *ConfigHeader, name: []const u8, comptime T: type, value: T) !void {
136123 const arena = config_header.step.owner.allocator;
137124 switch (@typeInfo(T)) {
......@@ -183,895 +170,15 @@ fn addValueInner(config_header: *ConfigHeader, name: []const u8, comptime T: typ
183170 }
184171}
185172
186fn make(step: *Step, options: Step.MakeOptions) !void {
187 _ = options;
188 const b = step.owner;
189 const config_header: *ConfigHeader = @fieldParentPtr("step", step);
190 if (config_header.style.getPath()) |lp| try step.singleUnchangingWatchInput(lp);
191
192 const gpa = b.allocator;
193 const arena = b.allocator;
194 const io = b.graph.io;
195
196 var man = b.graph.cache.obtain();
197 defer man.deinit();
198
199 // Random bytes to make ConfigHeader unique. Refresh this with new
200 // random bytes when ConfigHeader implementation is modified in a
201 // non-backwards-compatible way.
202 man.hash.add(@as(u32, 0xdef08d23));
203 man.hash.addBytes(config_header.include_path);
204 man.hash.addOptionalBytes(config_header.include_guard_override);
205
206 var aw: Writer.Allocating = .init(gpa);
207 defer aw.deinit();
208 const bw = &aw.writer;
209
210 const header_text = "This file was generated by ConfigHeader using the Zig Build System.";
211 const c_generated_line = "/* " ++ header_text ++ " */\n";
212 const asm_generated_line = "; " ++ header_text ++ "\n";
213
214 switch (config_header.style) {
215 .autoconf_undef, .autoconf_at => |file_source| {
216 try bw.writeAll(c_generated_line);
217 const src_path = file_source.getPath2(b, step);
218 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
219 return step.fail("unable to read autoconf input file '{s}': {s}", .{
220 src_path, @errorName(err),
221 });
222 };
223 switch (config_header.style) {
224 .autoconf_undef => try render_autoconf_undef(step, contents, bw, &config_header.values, src_path),
225 .autoconf_at => try render_autoconf_at(step, contents, &aw, &config_header.values, src_path),
226 else => unreachable,
227 }
228 },
229 .cmake => |file_source| {
230 try bw.writeAll(c_generated_line);
231 const src_path = file_source.getPath2(b, step);
232 const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| {
233 return step.fail("unable to read cmake input file '{s}': {s}", .{
234 src_path, @errorName(err),
235 });
236 };
237 try render_cmake(step, contents, bw, config_header.values, src_path);
238 },
239 .blank => {
240 try bw.writeAll(c_generated_line);
241 try render_blank(gpa, bw, config_header.values, config_header.include_path, config_header.include_guard_override);
242 },
243 .nasm => {
244 try bw.writeAll(asm_generated_line);
245 try render_nasm(bw, config_header.values);
246 },
247 }
248
249 const output = aw.written();
250 man.hash.addBytes(output);
251
252 if (try step.cacheHit(&man)) {
253 const digest = man.final();
254 config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest });
255 return;
256 }
257
258 const digest = man.final();
259
260 // If output_path has directory parts, deal with them. Example:
261 // output_dir is zig-cache/o/HASH
262 // output_path is libavutil/avconfig.h
263 // We want to open directory zig-cache/o/HASH/libavutil/
264 // but keep output_dir as zig-cache/o/HASH for -I include
265 const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path });
266 const sub_path_dirname = std.fs.path.dirname(sub_path).?;
267
268 b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| {
269 return step.fail("unable to make path '{f}{s}': {s}", .{
270 b.cache_root, sub_path_dirname, @errorName(err),
271 });
272 };
273
274 b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = output }) catch |err| {
275 return step.fail("unable to write file '{f}{s}': {s}", .{
276 b.cache_root, sub_path, @errorName(err),
277 });
278 };
279
280 config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest });
281 try man.writeManifest();
282}
283
284fn render_autoconf_undef(
285 step: *Step,
286 contents: []const u8,
287 bw: *Writer,
288 values: *const std.array_hash_map.String(Value),
289 src_path: []const u8,
290) !void {
291 const build = step.owner;
292 const allocator = build.allocator;
293
294 var is_used: std.bit_set.Dynamic = try .initEmpty(allocator, values.count());
295 defer is_used.deinit(allocator);
296
297 var any_errors = false;
298 var line_index: u32 = 0;
299 var line_it = std.mem.splitScalar(u8, contents, '\n');
300 while (line_it.next()) |line| : (line_index += 1) {
301 if (!std.mem.startsWith(u8, line, "#")) {
302 try bw.writeAll(line);
303 try bw.writeByte('\n');
304 continue;
305 }
306 var it = std.mem.tokenizeAny(u8, line[1..], " \t\r");
307 const undef = it.next().?;
308 if (!std.mem.eql(u8, undef, "undef")) {
309 try bw.writeAll(line);
310 try bw.writeByte('\n');
311 continue;
312 }
313 const name = it.next().?;
314 const index = values.getIndex(name) orelse {
315 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
316 src_path, line_index + 1, name,
317 });
318 any_errors = true;
319 continue;
320 };
321 is_used.set(index);
322 try renderValueC(bw, name, values.values()[index]);
323 }
324
325 var unused_value_it = is_used.iterator(.{ .kind = .unset });
326 while (unused_value_it.next()) |index| {
327 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, values.keys()[index] });
328 any_errors = true;
329 }
330
331 if (any_errors) {
332 return error.MakeFailed;
333 }
334}
335
336fn render_autoconf_at(
337 step: *Step,
338 contents: []const u8,
339 aw: *Writer.Allocating,
340 values: *const std.array_hash_map.String(Value),
341 src_path: []const u8,
342) !void {
343 const build = step.owner;
344 const allocator = build.allocator;
345 const bw = &aw.writer;
346
347 const used = allocator.alloc(bool, values.count()) catch @panic("OOM");
348 for (used) |*u| u.* = false;
349 defer allocator.free(used);
350
351 var any_errors = false;
352 var line_index: u32 = 0;
353 var line_it = std.mem.splitScalar(u8, contents, '\n');
354 while (line_it.next()) |line| : (line_index += 1) {
355 const last_line = line_it.index == line_it.buffer.len;
356
357 const old_len = aw.written().len;
358 expand_variables_autoconf_at(bw, line, values, used) catch |err| switch (err) {
359 error.MissingValue => {
360 const name = aw.written()[old_len..];
361 defer aw.shrinkRetainingCapacity(old_len);
362 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
363 src_path, line_index + 1, name,
364 });
365 any_errors = true;
366 continue;
367 },
368 else => {
369 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{
370 src_path, line_index + 1, @errorName(err),
371 });
372 any_errors = true;
373 continue;
374 },
375 };
376 if (!last_line) try bw.writeByte('\n');
377 }
378
379 for (values.entries.slice().items(.key), used) |name, u| {
380 if (!u) {
381 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
382 any_errors = true;
383 }
384 }
385
386 if (any_errors) return error.MakeFailed;
387}
388
389fn render_cmake(
390 step: *Step,
391 contents: []const u8,
392 bw: *Writer,
393 values: std.array_hash_map.String(Value),
394 src_path: []const u8,
395) !void {
396 const build = step.owner;
397 const allocator = build.allocator;
398
399 var values_copy = try values.clone(allocator);
400 defer values_copy.deinit(allocator);
401
402 var any_errors = false;
403 var line_index: u32 = 0;
404 var line_it = std.mem.splitScalar(u8, contents, '\n');
405 while (line_it.next()) |raw_line| : (line_index += 1) {
406 const last_line = line_it.index == line_it.buffer.len;
407
408 const line = expand_variables_cmake(allocator, raw_line, values) catch |err| switch (err) {
409 error.InvalidCharacter => {
410 try step.addError("{s}:{d}: error: invalid character in a variable name", .{
411 src_path, line_index + 1,
412 });
413 any_errors = true;
414 continue;
415 },
416 else => {
417 try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{
418 src_path, line_index + 1, @errorName(err),
419 });
420 any_errors = true;
421 continue;
422 },
423 };
424 defer allocator.free(line);
425
426 const line_start = std.mem.findNone(u8, line, " \t\r") orelse {
427 try bw.writeAll(line);
428 if (!last_line) try bw.writeByte('\n');
429 continue;
430 };
431 const whitespace_prefix = line[0..line_start];
432 const trimmed_line = line[line_start..];
433
434 if (!std.mem.startsWith(u8, trimmed_line, "#")) {
435 try bw.writeAll(line);
436 if (!last_line) try bw.writeByte('\n');
437 continue;
438 }
439
440 var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r");
441 const cmakedefine = it.next().?;
442 if (!std.mem.eql(u8, cmakedefine, "cmakedefine") and
443 !std.mem.eql(u8, cmakedefine, "cmakedefine01"))
444 {
445 try bw.writeAll(line);
446 if (!last_line) try bw.writeByte('\n');
447 continue;
448 }
449
450 const booldefine = std.mem.eql(u8, cmakedefine, "cmakedefine01");
451
452 const name = it.next() orelse {
453 try step.addError("{s}:{d}: error: missing define name", .{
454 src_path, line_index + 1,
455 });
456 any_errors = true;
457 continue;
458 };
459 var value = values_copy.get(name) orelse blk: {
460 if (booldefine) {
461 break :blk Value{ .int = 0 };
462 }
463 break :blk Value.undef;
464 };
465
466 value = blk: {
467 switch (value) {
468 .boolean => |b| {
469 if (!b) {
470 break :blk Value.undef;
471 }
472 },
473 .int => |i| {
474 if (i == 0) {
475 break :blk Value.undef;
476 }
477 },
478 .string => |string| {
479 if (string.len == 0) {
480 break :blk Value.undef;
481 }
482 },
483
484 else => {},
485 }
486 break :blk value;
487 };
488
489 if (booldefine) {
490 value = blk: {
491 switch (value) {
492 .undef => {
493 break :blk Value{ .boolean = false };
494 },
495 .defined => {
496 break :blk Value{ .boolean = false };
497 },
498 .boolean => |b| {
499 break :blk Value{ .boolean = b };
500 },
501 .int => |i| {
502 break :blk Value{ .boolean = i != 0 };
503 },
504 .string => |string| {
505 break :blk Value{ .boolean = string.len != 0 };
506 },
507
508 else => {
509 break :blk Value{ .boolean = false };
510 },
511 }
512 };
513 } else if (value != Value.undef) {
514 value = Value{ .ident = it.rest() };
515 }
516
517 try bw.writeAll(whitespace_prefix);
518 try renderValueC(bw, name, value);
519 }
520
521 if (any_errors) {
522 return error.HeaderConfigFailed;
523 }
524}
525
526fn render_blank(
527 gpa: std.mem.Allocator,
528 bw: *Writer,
529 defines: std.array_hash_map.String(Value),
530 include_path: []const u8,
531 include_guard_override: ?[]const u8,
532) !void {
533 const include_guard_name = include_guard_override orelse blk: {
534 const name = try gpa.dupe(u8, include_path);
535 for (name) |*byte| {
536 switch (byte.*) {
537 'a'...'z' => byte.* = byte.* - 'a' + 'A',
538 'A'...'Z', '0'...'9' => continue,
539 else => byte.* = '_',
540 }
541 }
542 break :blk name;
543 };
544 defer if (include_guard_override == null) gpa.free(include_guard_name);
545
546 try bw.print(
547 \\#ifndef {[0]s}
548 \\#define {[0]s}
549 \\
550 , .{include_guard_name});
551
552 const values = defines.values();
553 for (defines.keys(), 0..) |name, i| try renderValueC(bw, name, values[i]);
554
555 try bw.print(
556 \\#endif /* {s} */
557 \\
558 , .{include_guard_name});
559}
560
561fn render_nasm(bw: *Writer, defines: std.array_hash_map.String(Value)) !void {
562 for (defines.keys(), defines.values()) |name, value| try renderValueNasm(bw, name, value);
563}
564
565fn renderValueC(bw: *Writer, name: []const u8, value: Value) !void {
566 switch (value) {
567 .undef => try bw.print("/* #undef {s} */\n", .{name}),
568 .defined => try bw.print("#define {s}\n", .{name}),
569 .boolean => |b| try bw.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
570 .int => |i| try bw.print("#define {s} {d}\n", .{ name, i }),
571 .ident => |ident| try bw.print("#define {s} {s}\n", .{ name, ident }),
572 // TODO: use C-specific escaping instead of zig string literals
573 .string => |string| try bw.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
574 }
575}
576
577fn renderValueNasm(bw: *Writer, name: []const u8, value: Value) !void {
578 switch (value) {
579 .undef => try bw.print("; %undef {s}\n", .{name}),
580 .defined => try bw.print("%define {s}\n", .{name}),
581 .boolean => |b| try bw.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }),
582 .int => |i| try bw.print("%define {s} {d}\n", .{ name, i }),
583 .ident => |ident| try bw.print("%define {s} {s}\n", .{ name, ident }),
584 // TODO: use nasm-specific escaping instead of zig string literals
585 .string => |string| try bw.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }),
586 }
587}
588
589fn expand_variables_autoconf_at(
590 bw: *Writer,
591 contents: []const u8,
592 values: *const std.array_hash_map.String(Value),
593 used: []bool,
594) !void {
595 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
596
597 var curr: usize = 0;
598 var source_offset: usize = 0;
599 while (curr < contents.len) : (curr += 1) {
600 if (contents[curr] != '@') continue;
601 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
602 if (close_pos == curr + 1) {
603 // closed immediately, preserve as a literal
604 continue;
605 }
606 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
607 if (valid_varname_end != close_pos) {
608 // contains invalid characters, preserve as a literal
609 continue;
610 }
611
612 const key = contents[curr + 1 .. close_pos];
613 const index = values.getIndex(key) orelse {
614 // Report the missing key to the caller.
615 try bw.writeAll(key);
616 return error.MissingValue;
617 };
618 const value = values.entries.slice().items(.value)[index];
619 used[index] = true;
620 try bw.writeAll(contents[source_offset..curr]);
621 switch (value) {
622 .undef, .defined => {},
623 .boolean => |b| try bw.writeByte(@as(u8, '0') + @intFromBool(b)),
624 .int => |i| try bw.print("{d}", .{i}),
625 .ident, .string => |s| try bw.writeAll(s),
626 }
627
628 curr = close_pos;
629 source_offset = close_pos + 1;
630 }
631 }
632
633 try bw.writeAll(contents[source_offset..]);
634}
635
636fn expand_variables_cmake(
637 allocator: Allocator,
638 contents: []const u8,
639 values: std.array_hash_map.String(Value),
640) ![]const u8 {
641 var result: std.array_list.Managed(u8) = .init(allocator);
642 errdefer result.deinit();
643
644 const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-";
645 const open_var = "${";
646
647 var curr: usize = 0;
648 var source_offset: usize = 0;
649 const Position = struct {
650 source: usize,
651 target: usize,
652 };
653 var var_stack: std.array_list.Managed(Position) = .init(allocator);
654 defer var_stack.deinit();
655 loop: while (curr < contents.len) : (curr += 1) {
656 switch (contents[curr]) {
657 '@' => blk: {
658 if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| {
659 if (close_pos == curr + 1) {
660 // closed immediately, preserve as a literal
661 break :blk;
662 }
663 const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0;
664 if (valid_varname_end != close_pos) {
665 // contains invalid characters, preserve as a literal
666 break :blk;
667 }
668
669 const key = contents[curr + 1 .. close_pos];
670 const value = values.get(key) orelse return error.MissingValue;
671 const missing = contents[source_offset..curr];
672 try result.appendSlice(missing);
673 switch (value) {
674 .undef, .defined => {},
675 .boolean => |b| {
676 try result.append(if (b) '1' else '0');
677 },
678 .int => |i| {
679 try result.print("{d}", .{i});
680 },
681 .ident, .string => |s| {
682 try result.appendSlice(s);
683 },
684 }
685
686 curr = close_pos;
687 source_offset = close_pos + 1;
688
689 continue :loop;
690 }
691 },
692 '$' => blk: {
693 const next = curr + 1;
694 if (next == contents.len or contents[next] != '{') {
695 // no open bracket detected, preserve as a literal
696 break :blk;
697 }
698 const missing = contents[source_offset..curr];
699 try result.appendSlice(missing);
700 try result.appendSlice(open_var);
701
702 source_offset = curr + open_var.len;
703 curr = next;
704 try var_stack.append(Position{
705 .source = curr,
706 .target = result.items.len - open_var.len,
707 });
708
709 continue :loop;
710 },
711 '}' => blk: {
712 if (var_stack.items.len == 0) {
713 // no open bracket, preserve as a literal
714 break :blk;
715 }
716 const open_pos = var_stack.pop().?;
717 if (source_offset == open_pos.source) {
718 source_offset += open_var.len;
719 }
720 const missing = contents[source_offset..curr];
721 try result.appendSlice(missing);
722
723 const key_start = open_pos.target + open_var.len;
724 const key = result.items[key_start..];
725 if (key.len == 0) {
726 return error.MissingKey;
727 }
728 const value = values.get(key) orelse return error.MissingValue;
729 result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len);
730 switch (value) {
731 .undef, .defined => {},
732 .boolean => |b| {
733 try result.append(if (b) '1' else '0');
734 },
735 .int => |i| {
736 try result.print("{d}", .{i});
737 },
738 .ident, .string => |s| {
739 try result.appendSlice(s);
740 },
741 }
742
743 source_offset = curr + 1;
744
745 continue :loop;
746 },
747 '\\' => {
748 // backslash is not considered a special character
749 continue :loop;
750 },
751 else => {},
752 }
753
754 if (var_stack.items.len > 0 and std.mem.findScalar(u8, valid_varname_chars, contents[curr]) == null) {
755 return error.InvalidCharacter;
756 }
757 }
758
759 if (source_offset != contents.len) {
760 const missing = contents[source_offset..];
761 try result.appendSlice(missing);
173pub fn addValues(config_header: *ConfigHeader, values: anytype) void {
174 inline for (@typeInfo(@TypeOf(values)).@"struct".fields) |field| {
175 addValue(config_header, field.name, field.type, @field(values, field.name));
762176 }
763
764 return result.toOwnedSlice();
765}
766
767fn testReplaceVariablesAutoconfAt(
768 allocator: Allocator,
769 contents: []const u8,
770 expected: []const u8,
771 values: std.array_hash_map.String(Value),
772) !void {
773 var aw: Writer.Allocating = .init(allocator);
774 defer aw.deinit();
775
776 const used = try allocator.alloc(bool, values.count());
777 for (used) |*u| u.* = false;
778 defer allocator.free(used);
779
780 try expand_variables_autoconf_at(&aw.writer, contents, values, used);
781
782 for (used) |u| if (!u) return error.UnusedValue;
783 try std.testing.expectEqualStrings(expected, aw.written());
784177}
785178
786fn testReplaceVariablesCMake(
787 allocator: Allocator,
788 contents: []const u8,
789 expected: []const u8,
790 values: std.array_hash_map.String(Value),
791) !void {
792 const actual = try expand_variables_cmake(allocator, contents, values);
793 defer allocator.free(actual);
794
795 try std.testing.expectEqualStrings(expected, actual);
796}
797
798test "expand_variables_autoconf_at simple cases" {
799 const allocator = std.testing.allocator;
800 var values: std.array_hash_map.String(Value) = .init(allocator);
801 defer values.deinit();
802
803 // empty strings are preserved
804 try testReplaceVariablesAutoconfAt(allocator, "", "", values);
805
806 // line with misc content is preserved
807 try testReplaceVariablesAutoconfAt(allocator, "no substitution", "no substitution", values);
808
809 // empty @ sigils are preserved
810 try testReplaceVariablesAutoconfAt(allocator, "@", "@", values);
811 try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", values);
812 try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", values);
813 try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", values);
814
815 // simple substitution
816 try values.putNoClobber("undef", .undef);
817 try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", values);
818 values.clearRetainingCapacity();
819
820 try values.putNoClobber("defined", .defined);
821 try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", values);
822 values.clearRetainingCapacity();
823
824 try values.putNoClobber("true", Value{ .boolean = true });
825 try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", values);
826 values.clearRetainingCapacity();
827
828 try values.putNoClobber("false", Value{ .boolean = false });
829 try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", values);
830 values.clearRetainingCapacity();
831
832 try values.putNoClobber("int", Value{ .int = 42 });
833 try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", values);
834 values.clearRetainingCapacity();
835
836 try values.putNoClobber("ident", Value{ .string = "value" });
837 try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", values);
838 values.clearRetainingCapacity();
839
840 try values.putNoClobber("string", Value{ .string = "text" });
841 try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", values);
842 values.clearRetainingCapacity();
843
844 // double packed substitution
845 try values.putNoClobber("string", Value{ .string = "text" });
846 try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", values);
847 values.clearRetainingCapacity();
848
849 // triple packed substitution
850 try values.putNoClobber("int", Value{ .int = 42 });
851 try values.putNoClobber("string", Value{ .string = "text" });
852 try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", values);
853 values.clearRetainingCapacity();
854
855 // double separated substitution
856 try values.putNoClobber("int", Value{ .int = 42 });
857 try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", values);
858 values.clearRetainingCapacity();
859
860 // triple separated substitution
861 try values.putNoClobber("true", Value{ .boolean = true });
862 try values.putNoClobber("int", Value{ .int = 42 });
863 try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", values);
864 values.clearRetainingCapacity();
865
866 // misc prefix is preserved
867 try values.putNoClobber("false", Value{ .boolean = false });
868 try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", values);
869 values.clearRetainingCapacity();
870
871 // misc suffix is preserved
872 try values.putNoClobber("true", Value{ .boolean = true });
873 try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", values);
874 values.clearRetainingCapacity();
875
876 // surrounding content is preserved
877 try values.putNoClobber("int", Value{ .int = 42 });
878 try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values);
879 values.clearRetainingCapacity();
880
881 // incomplete key is preserved
882 try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", values);
883
884 // unknown key leads to an error
885 try std.testing.expectError(error.MissingValue, testReplaceVariablesAutoconfAt(allocator, "@bad@", "", values));
886
887 // unused key leads to an error
888 try values.putNoClobber("int", Value{ .int = 42 });
889 try values.putNoClobber("false", Value{ .boolean = false });
890 try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", values));
891 values.clearRetainingCapacity();
892}
893
894test "expand_variables_autoconf_at edge cases" {
895 const allocator = std.testing.allocator;
896 var values: std.array_hash_map.String(Value) = .init(allocator);
897 defer values.deinit();
898
899 // @-vars resolved only when they wrap valid characters, otherwise considered literals
900 try values.putNoClobber("string", Value{ .string = "text" });
901 try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", values);
902 values.clearRetainingCapacity();
903
904 // expanded variables are considered strings after expansion
905 try values.putNoClobber("string_at", Value{ .string = "@string@" });
906 try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", values);
907 values.clearRetainingCapacity();
908}
909
910test "expand_variables_cmake simple cases" {
911 const allocator = std.testing.allocator;
912 var values: std.array_hash_map.String(Value) = .init(allocator);
913 defer values.deinit();
914
915 try values.putNoClobber("undef", .undef);
916 try values.putNoClobber("defined", .defined);
917 try values.putNoClobber("true", Value{ .boolean = true });
918 try values.putNoClobber("false", Value{ .boolean = false });
919 try values.putNoClobber("int", Value{ .int = 42 });
920 try values.putNoClobber("ident", Value{ .string = "value" });
921 try values.putNoClobber("string", Value{ .string = "text" });
922
923 // empty strings are preserved
924 try testReplaceVariablesCMake(allocator, "", "", values);
925
926 // line with misc content is preserved
927 try testReplaceVariablesCMake(allocator, "no substitution", "no substitution", values);
928
929 // empty ${} wrapper leads to an error
930 try std.testing.expectError(error.MissingKey, testReplaceVariablesCMake(allocator, "${}", "", values));
931
932 // empty @ sigils are preserved
933 try testReplaceVariablesCMake(allocator, "@", "@", values);
934 try testReplaceVariablesCMake(allocator, "@@", "@@", values);
935 try testReplaceVariablesCMake(allocator, "@@@", "@@@", values);
936 try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", values);
937
938 // simple substitution
939 try testReplaceVariablesCMake(allocator, "@undef@", "", values);
940 try testReplaceVariablesCMake(allocator, "${undef}", "", values);
941 try testReplaceVariablesCMake(allocator, "@defined@", "", values);
942 try testReplaceVariablesCMake(allocator, "${defined}", "", values);
943 try testReplaceVariablesCMake(allocator, "@true@", "1", values);
944 try testReplaceVariablesCMake(allocator, "${true}", "1", values);
945 try testReplaceVariablesCMake(allocator, "@false@", "0", values);
946 try testReplaceVariablesCMake(allocator, "${false}", "0", values);
947 try testReplaceVariablesCMake(allocator, "@int@", "42", values);
948 try testReplaceVariablesCMake(allocator, "${int}", "42", values);
949 try testReplaceVariablesCMake(allocator, "@ident@", "value", values);
950 try testReplaceVariablesCMake(allocator, "${ident}", "value", values);
951 try testReplaceVariablesCMake(allocator, "@string@", "text", values);
952 try testReplaceVariablesCMake(allocator, "${string}", "text", values);
953
954 // double packed substitution
955 try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", values);
956 try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", values);
957
958 // triple packed substitution
959 try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", values);
960 try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", values);
961 try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", values);
962 try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", values);
963
964 // double separated substitution
965 try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", values);
966 try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", values);
967
968 // triple separated substitution
969 try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", values);
970 try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", values);
971 try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", values);
972 try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", values);
973
974 // misc prefix is preserved
975 try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", values);
976 try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", values);
977
978 // misc suffix is preserved
979 try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", values);
980 try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", values);
981
982 // surrounding content is preserved
983 try testReplaceVariablesCMake(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values);
984 try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", values);
985
986 // incomplete key is preserved
987 try testReplaceVariablesCMake(allocator, "@undef", "@undef", values);
988 try testReplaceVariablesCMake(allocator, "${undef", "${undef", values);
989 try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", values);
990 try testReplaceVariablesCMake(allocator, "undef@", "undef@", values);
991 try testReplaceVariablesCMake(allocator, "undef}", "undef}", values);
992
993 // unknown key leads to an error
994 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", values));
995 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", values));
996}
997
998test "expand_variables_cmake edge cases" {
999 const allocator = std.testing.allocator;
1000 var values: std.array_hash_map.String(Value) = .init(allocator);
1001 defer values.deinit();
1002
1003 // special symbols
1004 try values.putNoClobber("at", Value{ .string = "@" });
1005 try values.putNoClobber("dollar", Value{ .string = "$" });
1006 try values.putNoClobber("underscore", Value{ .string = "_" });
1007
1008 // basic value
1009 try values.putNoClobber("string", Value{ .string = "text" });
1010
1011 // proxy case values
1012 try values.putNoClobber("string_proxy", Value{ .string = "string" });
1013 try values.putNoClobber("string_at", Value{ .string = "@string@" });
1014 try values.putNoClobber("string_curly", Value{ .string = "{string}" });
1015 try values.putNoClobber("string_var", Value{ .string = "${string}" });
1016
1017 // stack case values
1018 try values.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" });
1019 try values.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" });
1020
1021 // @-vars resolved only when they wrap valid characters, otherwise considered literals
1022 try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", values);
1023 try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", values);
1024
1025 // @-vars are resolved inside ${}-vars
1026 try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", values);
1027
1028 // expanded variables are considered strings after expansion
1029 try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", values);
1030 try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", values);
1031 try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", values);
1032 try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", values);
1033 try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", values);
1034 try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", values);
1035 try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", values);
1036 try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", values);
1037 try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", values);
1038
1039 // when expanded variables contain invalid characters, they prevent further expansion
1040 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", values));
1041 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", values));
1042
1043 // nested expanded variables are expanded from the inside out
1044 try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", values);
1045 try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", values);
1046
1047 // nested vars are only expanded when ${} is closed
1048 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", values));
1049 try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", values);
1050 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", values));
1051 try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", values);
1052
1053 // invalid characters lead to an error
1054 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", values));
1055 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", values));
1056 try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", values));
179pub fn getOutputDir(ch: *ConfigHeader) std.Build.LazyPath {
180 return .{ .generated = .{ .index = ch.generated_dir } };
1057181}
1058
1059test "expand_variables_cmake escaped characters" {
1060 const allocator = std.testing.allocator;
1061 var values: std.array_hash_map.String(Value) = .init(allocator);
1062 defer values.deinit();
1063
1064 try values.putNoClobber("string", Value{ .string = "text" });
1065
1066 // backslash is an invalid character for @ lookup
1067 try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", values);
1068
1069 // backslash is preserved, but doesn't affect ${} variable expansion
1070 try testReplaceVariablesCMake(allocator, "\\${string}", "\\text", values);
1071
1072 // backslash breaks ${} opening bracket identification
1073 try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", values);
1074
1075 // backslash is skipped when checking for invalid characters, yet it mangles the key
1076 try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${string\\}", "", values));
182pub fn getOutputFile(ch: *ConfigHeader) std.Build.LazyPath {
183 return ch.getOutputDir().path(ch.step.owner, ch.include_path);
1077184}
lib/std/Build/Step/Fail.zig+8-18
......@@ -1,35 +1,25 @@
11//! Fail the build with a given message.
2const Fail = @This();
3
24const std = @import("std");
35const Step = std.Build.Step;
4const Fail = @This();
6const Configuration = std.Build.Configuration;
57
68step: Step,
7error_msg: []const u8,
9error_msg: Configuration.String,
810
911pub const base_tag: Step.Tag = .fail;
1012
1113pub fn create(owner: *std.Build, error_msg: []const u8) *Fail {
12 const fail = owner.allocator.create(Fail) catch @panic("OOM");
13
14 const graph = owner.graph;
15 const fail = graph.create(Fail);
1416 fail.* = .{
15 .step = Step.init(.{
17 .step = .init(.{
1618 .tag = base_tag,
1719 .name = "fail",
1820 .owner = owner,
19 .makeFn = make,
2021 }),
21 .error_msg = owner.dupe(error_msg),
22 .error_msg = graph.addString(error_msg),
2223 };
23
2424 return fail;
2525}
26
27fn make(step: *Step, options: Step.MakeOptions) !void {
28 _ = options; // No progress to report.
29
30 const fail: *Fail = @fieldParentPtr("step", step);
31
32 try step.result_error_msgs.append(step.owner.allocator, fail.error_msg);
33
34 return error.MakeFailed;
35}
test/standalone/libfuzzer/build.zig+1-1
......@@ -24,6 +24,6 @@ pub fn build(b: *std.Build) void {
2424 b.default_step = run_step;
2525
2626 const run_artifact = b.addRunArtifact(exe);
27 run_artifact.addArg(b.cache_root.path orelse "");
27 run_artifact.addFileArg(.cache_root);
2828 run_step.dependOn(&run_artifact.step);
2929}
test/standalone/windows_resources/build.zig+1-1
......@@ -38,7 +38,7 @@ fn add(
3838 .file = b.path("res/zig.rc"),
3939 .flags = &.{"/c65001"}, // UTF-8 code page
4040 .include_paths = &.{
41 .{ .generated = .{ .file = &generated_h_step.generated_directory } },
41 .{ .generated = .{ .index = generated_h_step.generated_directory } },
4242 },
4343 });
4444 exe.rc_includes = switch (rc_includes) {
test/tests.zig+8-5
......@@ -2433,8 +2433,10 @@ pub fn addCliTests(b: *std.Build) *Step {
24332433 });
24342434 run_test.addArg("--build-file");
24352435 run_test.addFileArg(b.path("test/cli/options/build.zig"));
2436
24362437 run_test.addArg("--cache-dir");
2437 run_test.addFileArg(.{ .cwd_relative = b.cache_root.join(b.allocator, &.{}) catch @panic("OOM") });
2438 run_test.addFileArg(.cache_root);
2439
24382440 run_test.setName("test build options");
24392441
24402442 step.dependOn(&run_test.step);
......@@ -2966,10 +2968,11 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons
29662968
29672969 run.addArg(b.graph.zig_exe);
29682970 run.addFileArg(b.path("test/incremental/").path(b, entry.path));
2969 run.addArgs(&.{
2970 "--zig-lib-dir", b.graph.zig_lib_directory.path orelse ".",
2971 "--target", target_str,
2972 });
2971
2972 run.addArg("--zig-lib-dir");
2973 run.addFileArg(.zig_lib);
2974
2975 run.addArgs(&.{ "--target", target_str });
29732976
29742977 run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc
29752978