From 5177068c885cb2377bc6058c81418318b9cfdb9c Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Wed, 21 Jun 2023 21:51:06 +0200 Subject: [PATCH 1/3] std.Build: correctly implement cmakedefine and cmakedefine01 --- lib/std/Build/Step/ConfigHeader.zig | 80 ++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/lib/std/Build/Step/ConfigHeader.zig b/lib/std/Build/Step/ConfigHeader.zig index 4b76e24b26..6fa5cc4a38 100644 --- a/lib/std/Build/Step/ConfigHeader.zig +++ b/lib/std/Build/Step/ConfigHeader.zig @@ -299,6 +299,11 @@ fn render_cmake( var line_index: u32 = 0; var line_it = std.mem.splitScalar(u8, contents, '\n'); while (line_it.next()) |line| : (line_index += 1) { + // if we reached the end of the buffer there is nothing worth doing anymore + if (line_it.index == line_it.buffer.len) { + continue; + } + if (!std.mem.startsWith(u8, line, "#")) { try output.appendSlice(line); try output.appendSlice("\n"); @@ -313,6 +318,9 @@ fn render_cmake( try output.appendSlice("\n"); continue; } + + const booldefine = std.mem.eql(u8, cmakedefine, "cmakedefine01"); + const name = it.next() orelse { try step.addError("{s}:{d}: error: missing define name", .{ src_path, line_index + 1, @@ -320,19 +328,66 @@ fn render_cmake( any_errors = true; continue; }; - const kv = values_copy.fetchSwapRemove(name) orelse { - try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ - src_path, line_index + 1, name, - }); - any_errors = true; - continue; + var value = values_copy.get(name) orelse blk: { + if (booldefine) { + break :blk Value{ .int = 0 }; + } + break :blk Value.undef; }; - try renderValueC(output, name, kv.value); - } - for (values_copy.keys()) |name| { - try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); - any_errors = true; + value = blk: { + switch (value) { + .boolean => |b| { + if (!b) { + break :blk Value.undef; + } + }, + .int => |i| { + if (i == 0) { + break :blk Value.undef; + } + }, + .string => |string| { + if (string.len == 0) { + break :blk Value.undef; + } + }, + + else => { + break :blk value; + }, + } + }; + + if (booldefine) { + value = blk: { + switch (value) { + .undef => { + break :blk Value{ .boolean = false }; + }, + .defined => { + break :blk Value{ .boolean = false }; + }, + .boolean => |b| { + break :blk Value{ .boolean = b }; + }, + .int => |i| { + break :blk Value{ .boolean = i != 0 }; + }, + .string => |string| { + break :blk Value{ .boolean = string.len != 0 }; + }, + + else => { + break :blk Value{ .boolean = false }; + }, + } + }; + } else if (value != Value.undef) { + value = Value{ .ident = it.rest() }; + } + + try renderValueC(output, name, value); } if (any_errors) { @@ -392,8 +447,7 @@ fn renderValueC(output: *std.ArrayList(u8), name: []const u8, value: Value) !voi .boolean => |b| { try output.appendSlice("#define "); try output.appendSlice(name); - try output.appendSlice(" "); - try output.appendSlice(if (b) "true\n" else "false\n"); + try output.appendSlice(if (b) " 1\n" else " 0\n"); }, .int => |i| { try output.writer().print("#define {s} {d}\n", .{ name, i }); From 1864ba2cccea8452c319bf2cb9787ecb41d44a34 Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Wed, 21 Jun 2023 21:50:55 +0200 Subject: [PATCH 2/3] std.Build: implement variable substitution --- lib/std/Build/Step/ConfigHeader.zig | 74 ++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/Step/ConfigHeader.zig b/lib/std/Build/Step/ConfigHeader.zig index 6fa5cc4a38..3d4ed628cc 100644 --- a/lib/std/Build/Step/ConfigHeader.zig +++ b/lib/std/Build/Step/ConfigHeader.zig @@ -1,6 +1,7 @@ const std = @import("std"); const ConfigHeader = @This(); const Step = std.Build.Step; +const Allocator = std.mem.Allocator; pub const Style = union(enum) { /// The configure format supported by autotools. It uses `#undef foo` to @@ -292,18 +293,27 @@ fn render_cmake( values: std.StringArrayHashMap(Value), src_path: []const u8, ) !void { + var build = step.owner; + var allocator = build.allocator; + var values_copy = try values.clone(); defer values_copy.deinit(); var any_errors = false; var line_index: u32 = 0; var line_it = std.mem.splitScalar(u8, contents, '\n'); - while (line_it.next()) |line| : (line_index += 1) { + while (line_it.next()) |raw_line| : (line_index += 1) { // if we reached the end of the buffer there is nothing worth doing anymore if (line_it.index == line_it.buffer.len) { continue; } + const first_pass = replace_variables(allocator, raw_line, values, "@", "@") catch @panic("Failed to substitute"); + const line = replace_variables(allocator, first_pass, values, "${", "}") catch @panic("Failed to substitute"); + + allocator.free(first_pass); + defer allocator.free(line); + if (!std.mem.startsWith(u8, line, "#")) { try output.appendSlice(line); try output.appendSlice("\n"); @@ -491,3 +501,65 @@ fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) ! }, } } + +fn replace_variables( + allocator: Allocator, + contents: []const u8, + values: std.StringArrayHashMap(Value), + prefix: []const u8, + suffix: []const u8, +) ![]const u8 { + var content_buf = allocator.dupe(u8, contents) catch @panic("OOM"); + + var last_index: usize = 0; + while (std.mem.indexOfPos(u8, content_buf, last_index, prefix)) |prefix_index| { + const start_index = prefix_index + prefix.len; + if (std.mem.indexOfPos(u8, content_buf, start_index, suffix)) |suffix_index| { + const end_index = suffix_index + suffix.len; + + const beginline = content_buf[0..prefix_index]; + const endline = content_buf[end_index..]; + const key = content_buf[start_index..suffix_index]; + const value = values.get(key) orelse .undef; + + switch (value) { + .boolean => |b| { + const buf = try std.fmt.allocPrint(allocator, "{s}{}{s}", .{ beginline, @intFromBool(b), endline }); + last_index = start_index + 1; + + allocator.free(content_buf); + content_buf = buf; + }, + .int => |i| { + const buf = try std.fmt.allocPrint(allocator, "{s}{}{s}", .{ beginline, i, endline }); + const isNegative = i < 0; + const digits = (if (0 < i) std.math.log10(std.math.absCast(i)) else 0) + 1; + last_index = start_index + @intFromBool(isNegative) + digits + 1; + + allocator.free(content_buf); + content_buf = buf; + }, + .string => |string| { + const buf = try std.fmt.allocPrint(allocator, "{s}{s}{s}", .{ beginline, string, endline }); + last_index = start_index + string.len + 1; + + allocator.free(content_buf); + content_buf = buf; + }, + + else => { + const buf = try std.fmt.allocPrint(allocator, "{s}{s}", .{ beginline, endline }); + last_index = start_index + 1; + + allocator.free(content_buf); + content_buf = buf; + }, + } + continue; + } + + last_index = start_index + 1; + } + + return content_buf; +} From f74285b3be58f0282fcdfeab4345660b6f5d35e5 Mon Sep 17 00:00:00 2001 From: Jan200101 Date: Wed, 21 Jun 2023 21:50:35 +0200 Subject: [PATCH 3/3] test: add standalone test for cmakedefine --- test/standalone.zig | 4 + test/standalone/cmakedefine/build.zig | 56 +++++++++++++ test/standalone/cmakedefine/config.h.cmake | 93 ++++++++++++++++++++++ test/standalone/cmakedefine/expected.h | 93 ++++++++++++++++++++++ 4 files changed, 246 insertions(+) create mode 100644 test/standalone/cmakedefine/build.zig create mode 100644 test/standalone/cmakedefine/config.h.cmake create mode 100644 test/standalone/cmakedefine/expected.h diff --git a/test/standalone.zig b/test/standalone.zig index a055da9761..b15e5f7033 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -226,6 +226,10 @@ pub const build_cases = [_]BuildCase{ .build_root = "test/standalone/strip_empty_loop", .import = @import("standalone/strip_empty_loop/build.zig"), }, + .{ + .build_root = "test/standalone/cmakedefine", + .import = @import("standalone/cmakedefine/build.zig"), + }, }; const std = @import("std"); diff --git a/test/standalone/cmakedefine/build.zig b/test/standalone/cmakedefine/build.zig new file mode 100644 index 0000000000..35dd431946 --- /dev/null +++ b/test/standalone/cmakedefine/build.zig @@ -0,0 +1,56 @@ +const std = @import("std"); +const ConfigHeader = std.Build.Step.ConfigHeader; + +pub fn build(b: *std.Build) void { + const config_header = b.addConfigHeader( + .{ + .style = .{ .cmake = .{ .path = "config.h.cmake" } }, + }, + .{ + .noval = null, + .trueval = true, + .falseval = false, + .zeroval = 0, + .oneval = 1, + .tenval = 10, + .stringval = "test", + + .boolnoval = void{}, + .booltrueval = true, + .boolfalseval = false, + .boolzeroval = 0, + .booloneval = 1, + .booltenval = 10, + .boolstringval = "test", + }, + ); + + const test_step = b.step("test", "Test it"); + test_step.makeFn = compare_headers; + test_step.dependOn(&config_header.step); +} + +fn compare_headers(step: *std.Build.Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const allocator = step.owner.allocator; + const cmake_header_path = "expected.h"; + + const config_header_step = step.dependencies.getLast(); + const config_header = @fieldParentPtr(ConfigHeader, "step", config_header_step); + + const zig_header_path = config_header.output_file.path orelse @panic("Could not locate header file"); + + const cwd = std.fs.cwd(); + + const cmake_header = try cwd.readFileAlloc(allocator, cmake_header_path, config_header.max_bytes); + defer allocator.free(cmake_header); + + const zig_header = try cwd.readFileAlloc(allocator, zig_header_path, config_header.max_bytes); + defer allocator.free(zig_header); + + const header_text_index = std.mem.indexOf(u8, zig_header, "\n") orelse @panic("Could not find comment in header filer"); + + if (!std.mem.eql(u8, zig_header[header_text_index + 1 ..], cmake_header)) { + @panic("processed cmakedefine header does not match expected output"); + } +} diff --git a/test/standalone/cmakedefine/config.h.cmake b/test/standalone/cmakedefine/config.h.cmake new file mode 100644 index 0000000000..d6961653df --- /dev/null +++ b/test/standalone/cmakedefine/config.h.cmake @@ -0,0 +1,93 @@ +// cmakedefine +// undefined +#cmakedefine noval unreachable + +// 1 +#cmakedefine trueval 1 + +// undefined +#cmakedefine falseval unreachable + +// undefined +#cmakedefine zeroval unreachable + +// 1 +#cmakedefine oneval 1 + +// 1 +#cmakedefine tenval 1 + +// 1 +#cmakedefine stringval 1 + + +// cmakedefine01 +// 0 +#cmakedefine01 boolnoval + +// 1 +#cmakedefine01 booltrueval + +// 0 +#cmakedefine01 boolfalseval + +// 0 +#cmakedefine01 boolzeroval + +// 1 +#cmakedefine01 booloneval + +// 1 +#cmakedefine01 booltenval + +// 1 +#cmakedefine01 boolstringval + + +// @ substition + +// no substition +// @noval@ + +// 1 +// @trueval@ + +// 0 +// @falseval@ + +// 0 +// @zeroval@ + +// 1 +// @oneval@ + +// 10 +// @tenval@ + +// test +// @stringval@ + + +// ${} substition + +// removal +// ${noval} + +// 1 +// ${trueval} + +// 0 +// ${falseval} + +// 0 +// ${zeroval} + +// 1 +// ${oneval} + +// 10 +// ${tenval} + +// test +// ${stringval} + diff --git a/test/standalone/cmakedefine/expected.h b/test/standalone/cmakedefine/expected.h new file mode 100644 index 0000000000..d71f070678 --- /dev/null +++ b/test/standalone/cmakedefine/expected.h @@ -0,0 +1,93 @@ +// cmakedefine +// undefined +/* #undef noval */ + +// 1 +#define trueval 1 + +// undefined +/* #undef falseval */ + +// undefined +/* #undef zeroval */ + +// 1 +#define oneval 1 + +// 1 +#define tenval 1 + +// 1 +#define stringval 1 + + +// cmakedefine01 +// 0 +#define boolnoval 0 + +// 1 +#define booltrueval 1 + +// 0 +#define boolfalseval 0 + +// 0 +#define boolzeroval 0 + +// 1 +#define booloneval 1 + +// 1 +#define booltenval 1 + +// 1 +#define boolstringval 1 + + +// @ substition + +// no substition +// + +// 1 +// 1 + +// 0 +// 0 + +// 0 +// 0 + +// 1 +// 1 + +// 10 +// 10 + +// test +// test + + +// substition + +// removal +// + +// 1 +// 1 + +// 0 +// 0 + +// 0 +// 0 + +// 1 +// 1 + +// 10 +// 10 + +// test +// test +