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

Correct cmakedefine implementation
This commit is contained in:
Andrew Kelley 2023-06-23 23:56:18 -07:00 committed by GitHub
commit b129f1b046
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 386 additions and 14 deletions

View File

@ -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;
}

View File

@ -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");

View File

@ -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");
}
}

View File

@ -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}

View File

@ -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