authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-23 23:56:18-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-06-23 23:56:18-07:00
logb129f1b0462e6c80b4a97851ea3aa8b3f0a4aa37
tree33b0a1d8ed1ca572bfb1ff159f3663397fe00aea
parentff0a88b133b9c4f27528f39d05ff65a977756bee
parentf74285b3be58f0282fcdfeab4345660b6f5d35e5
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #16037 from Jan200101/PR/cmakedefine-fix

Correct cmakedefine implementation

5 files changed, 386 insertions(+), 14 deletions(-)

lib/std/Build/Step/ConfigHeader.zig+140-14
......@@ -1,6 +1,7 @@
11const std = @import("std");
22const ConfigHeader = @This();
33const Step = std.Build.Step;
4const Allocator = std.mem.Allocator;
45
56pub const Style = union(enum) {
67 /// The configure format supported by autotools. It uses `#undef foo` to
......@@ -292,13 +293,27 @@ fn render_cmake(
292293 values: std.StringArrayHashMap(Value),
293294 src_path: []const u8,
294295) !void {
296 var build = step.owner;
297 var allocator = build.allocator;
298
295299 var values_copy = try values.clone();
296300 defer values_copy.deinit();
297301
298302 var any_errors = false;
299303 var line_index: u32 = 0;
300304 var line_it = std.mem.splitScalar(u8, contents, '\n');
301 while (line_it.next()) |line| : (line_index += 1) {
305 while (line_it.next()) |raw_line| : (line_index += 1) {
306 // if we reached the end of the buffer there is nothing worth doing anymore
307 if (line_it.index == line_it.buffer.len) {
308 continue;
309 }
310
311 const first_pass = replace_variables(allocator, raw_line, values, "@", "@") catch @panic("Failed to substitute");
312 const line = replace_variables(allocator, first_pass, values, "${", "}") catch @panic("Failed to substitute");
313
314 allocator.free(first_pass);
315 defer allocator.free(line);
316
302317 if (!std.mem.startsWith(u8, line, "#")) {
303318 try output.appendSlice(line);
304319 try output.appendSlice("\n");
......@@ -313,6 +328,9 @@ fn render_cmake(
313328 try output.appendSlice("\n");
314329 continue;
315330 }
331
332 const booldefine = std.mem.eql(u8, cmakedefine, "cmakedefine01");
333
316334 const name = it.next() orelse {
317335 try step.addError("{s}:{d}: error: missing define name", .{
318336 src_path, line_index + 1,
......@@ -320,19 +338,66 @@ fn render_cmake(
320338 any_errors = true;
321339 continue;
322340 };
323 const kv = values_copy.fetchSwapRemove(name) orelse {
324 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
325 src_path, line_index + 1, name,
326 });
327 any_errors = true;
328 continue;
341 var value = values_copy.get(name) orelse blk: {
342 if (booldefine) {
343 break :blk Value{ .int = 0 };
344 }
345 break :blk Value.undef;
329346 };
330 try renderValueC(output, name, kv.value);
331 }
332347
333 for (values_copy.keys()) |name| {
334 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
335 any_errors = true;
348 value = blk: {
349 switch (value) {
350 .boolean => |b| {
351 if (!b) {
352 break :blk Value.undef;
353 }
354 },
355 .int => |i| {
356 if (i == 0) {
357 break :blk Value.undef;
358 }
359 },
360 .string => |string| {
361 if (string.len == 0) {
362 break :blk Value.undef;
363 }
364 },
365
366 else => {
367 break :blk value;
368 },
369 }
370 };
371
372 if (booldefine) {
373 value = blk: {
374 switch (value) {
375 .undef => {
376 break :blk Value{ .boolean = false };
377 },
378 .defined => {
379 break :blk Value{ .boolean = false };
380 },
381 .boolean => |b| {
382 break :blk Value{ .boolean = b };
383 },
384 .int => |i| {
385 break :blk Value{ .boolean = i != 0 };
386 },
387 .string => |string| {
388 break :blk Value{ .boolean = string.len != 0 };
389 },
390
391 else => {
392 break :blk Value{ .boolean = false };
393 },
394 }
395 };
396 } else if (value != Value.undef) {
397 value = Value{ .ident = it.rest() };
398 }
399
400 try renderValueC(output, name, value);
336401 }
337402
338403 if (any_errors) {
......@@ -392,8 +457,7 @@ fn renderValueC(output: *std.ArrayList(u8), name: []const u8, value: Value) !voi
392457 .boolean => |b| {
393458 try output.appendSlice("#define ");
394459 try output.appendSlice(name);
395 try output.appendSlice(" ");
396 try output.appendSlice(if (b) "true\n" else "false\n");
460 try output.appendSlice(if (b) " 1\n" else " 0\n");
397461 },
398462 .int => |i| {
399463 try output.writer().print("#define {s} {d}\n", .{ name, i });
......@@ -437,3 +501,65 @@ fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !
437501 },
438502 }
439503}
504
505fn replace_variables(
506 allocator: Allocator,
507 contents: []const u8,
508 values: std.StringArrayHashMap(Value),
509 prefix: []const u8,
510 suffix: []const u8,
511) ![]const u8 {
512 var content_buf = allocator.dupe(u8, contents) catch @panic("OOM");
513
514 var last_index: usize = 0;
515 while (std.mem.indexOfPos(u8, content_buf, last_index, prefix)) |prefix_index| {
516 const start_index = prefix_index + prefix.len;
517 if (std.mem.indexOfPos(u8, content_buf, start_index, suffix)) |suffix_index| {
518 const end_index = suffix_index + suffix.len;
519
520 const beginline = content_buf[0..prefix_index];
521 const endline = content_buf[end_index..];
522 const key = content_buf[start_index..suffix_index];
523 const value = values.get(key) orelse .undef;
524
525 switch (value) {
526 .boolean => |b| {
527 const buf = try std.fmt.allocPrint(allocator, "{s}{}{s}", .{ beginline, @intFromBool(b), endline });
528 last_index = start_index + 1;
529
530 allocator.free(content_buf);
531 content_buf = buf;
532 },
533 .int => |i| {
534 const buf = try std.fmt.allocPrint(allocator, "{s}{}{s}", .{ beginline, i, endline });
535 const isNegative = i < 0;
536 const digits = (if (0 < i) std.math.log10(std.math.absCast(i)) else 0) + 1;
537 last_index = start_index + @intFromBool(isNegative) + digits + 1;
538
539 allocator.free(content_buf);
540 content_buf = buf;
541 },
542 .string => |string| {
543 const buf = try std.fmt.allocPrint(allocator, "{s}{s}{s}", .{ beginline, string, endline });
544 last_index = start_index + string.len + 1;
545
546 allocator.free(content_buf);
547 content_buf = buf;
548 },
549
550 else => {
551 const buf = try std.fmt.allocPrint(allocator, "{s}{s}", .{ beginline, endline });
552 last_index = start_index + 1;
553
554 allocator.free(content_buf);
555 content_buf = buf;
556 },
557 }
558 continue;
559 }
560
561 last_index = start_index + 1;
562 }
563
564 return content_buf;
565}
test/standalone.zig+4
......@@ -226,6 +226,10 @@ pub const build_cases = [_]BuildCase{
226226 .build_root = "test/standalone/strip_empty_loop",
227227 .import = @import("standalone/strip_empty_loop/build.zig"),
228228 },
229 .{
230 .build_root = "test/standalone/cmakedefine",
231 .import = @import("standalone/cmakedefine/build.zig"),
232 },
229233};
230234
231235const std = @import("std");
test/standalone/cmakedefine/build.zig created+56
......@@ -0,0 +1,56 @@
1const std = @import("std");
2const ConfigHeader = std.Build.Step.ConfigHeader;
3
4pub fn build(b: *std.Build) void {
5 const config_header = b.addConfigHeader(
6 .{
7 .style = .{ .cmake = .{ .path = "config.h.cmake" } },
8 },
9 .{
10 .noval = null,
11 .trueval = true,
12 .falseval = false,
13 .zeroval = 0,
14 .oneval = 1,
15 .tenval = 10,
16 .stringval = "test",
17
18 .boolnoval = void{},
19 .booltrueval = true,
20 .boolfalseval = false,
21 .boolzeroval = 0,
22 .booloneval = 1,
23 .booltenval = 10,
24 .boolstringval = "test",
25 },
26 );
27
28 const test_step = b.step("test", "Test it");
29 test_step.makeFn = compare_headers;
30 test_step.dependOn(&config_header.step);
31}
32
33fn compare_headers(step: *std.Build.Step, prog_node: *std.Progress.Node) !void {
34 _ = prog_node;
35 const allocator = step.owner.allocator;
36 const cmake_header_path = "expected.h";
37
38 const config_header_step = step.dependencies.getLast();
39 const config_header = @fieldParentPtr(ConfigHeader, "step", config_header_step);
40
41 const zig_header_path = config_header.output_file.path orelse @panic("Could not locate header file");
42
43 const cwd = std.fs.cwd();
44
45 const cmake_header = try cwd.readFileAlloc(allocator, cmake_header_path, config_header.max_bytes);
46 defer allocator.free(cmake_header);
47
48 const zig_header = try cwd.readFileAlloc(allocator, zig_header_path, config_header.max_bytes);
49 defer allocator.free(zig_header);
50
51 const header_text_index = std.mem.indexOf(u8, zig_header, "\n") orelse @panic("Could not find comment in header filer");
52
53 if (!std.mem.eql(u8, zig_header[header_text_index + 1 ..], cmake_header)) {
54 @panic("processed cmakedefine header does not match expected output");
55 }
56}
test/standalone/cmakedefine/config.h.cmake created+93
......@@ -0,0 +1,93 @@
1// cmakedefine
2// undefined
3#cmakedefine noval unreachable
4
5// 1
6#cmakedefine trueval 1
7
8// undefined
9#cmakedefine falseval unreachable
10
11// undefined
12#cmakedefine zeroval unreachable
13
14// 1
15#cmakedefine oneval 1
16
17// 1
18#cmakedefine tenval 1
19
20// 1
21#cmakedefine stringval 1
22
23
24// cmakedefine01
25// 0
26#cmakedefine01 boolnoval
27
28// 1
29#cmakedefine01 booltrueval
30
31// 0
32#cmakedefine01 boolfalseval
33
34// 0
35#cmakedefine01 boolzeroval
36
37// 1
38#cmakedefine01 booloneval
39
40// 1
41#cmakedefine01 booltenval
42
43// 1
44#cmakedefine01 boolstringval
45
46
47// @ substition
48
49// no substition
50// @noval@
51
52// 1
53// @trueval@
54
55// 0
56// @falseval@
57
58// 0
59// @zeroval@
60
61// 1
62// @oneval@
63
64// 10
65// @tenval@
66
67// test
68// @stringval@
69
70
71// ${} substition
72
73// removal
74// ${noval}
75
76// 1
77// ${trueval}
78
79// 0
80// ${falseval}
81
82// 0
83// ${zeroval}
84
85// 1
86// ${oneval}
87
88// 10
89// ${tenval}
90
91// test
92// ${stringval}
93
test/standalone/cmakedefine/expected.h created+93
......@@ -0,0 +1,93 @@
1// cmakedefine
2// undefined
3/* #undef noval */
4
5// 1
6#define trueval 1
7
8// undefined
9/* #undef falseval */
10
11// undefined
12/* #undef zeroval */
13
14// 1
15#define oneval 1
16
17// 1
18#define tenval 1
19
20// 1
21#define stringval 1
22
23
24// cmakedefine01
25// 0
26#define boolnoval 0
27
28// 1
29#define booltrueval 1
30
31// 0
32#define boolfalseval 0
33
34// 0
35#define boolzeroval 0
36
37// 1
38#define booloneval 1
39
40// 1
41#define booltenval 1
42
43// 1
44#define boolstringval 1
45
46
47// @ substition
48
49// no substition
50//
51
52// 1
53// 1
54
55// 0
56// 0
57
58// 0
59// 0
60
61// 1
62// 1
63
64// 10
65// 10
66
67// test
68// test
69
70
71// substition
72
73// removal
74//
75
76// 1
77// 1
78
79// 0
80// 0
81
82// 0
83// 0
84
85// 1
86// 1
87
88// 10
89// 10
90
91// test
92// test
93