commit b129f1b0462e6c80b4a97851ea3aa8b3f0a4aa37 (tree)
parent ff0a88b133b9c4f27528f39d05ff65a977756bee
Author: Andrew Kelley <andrew@ziglang.org>
Date: Fri, 23 Jun 2023 23:56:18 -0700
Merge pull request #16037 from Jan200101/PR/cmakedefine-fix
Correct cmakedefine implementation
Diffstat:
5 files changed, 386 insertions(+), 14 deletions(-)
diff --git 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,13 +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");
@@ -313,6 +328,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 +338,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 +457,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 });
@@ -437,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;
+}
diff --git 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
@@ -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
@@ -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
@@ -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
+