authorgravatar for david.vanderson@gmail.comDavid Vanderson <david.vanderson@gmail.com> 2023-01-19 21:30:43-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-05 06:26:30-07:00
logb04e48566c58ed22fdb0dbe7ac866877ad53133c
treea277985b27136dce3930578cea2a6d374b2a4ff8
parent11cc1c16fa36a7eb13cba1c43fb153ee6aca7b58

std.build: support for generated c headers

Add ability to generate a c header file from scratch, and then both compile with it and install it if needed. Example: ```zig const avconfig_h = b.addConfigHeader(.{ .path = "libavutil/avconfig.h" }, .generated, .{ .AV_HAVE_BIGENDIAN = 0, // TODO: detect based on target .AV_HAVE_FAST_UNALIGNED = 1, // TODO: detect based on target }); lib.addConfigHeader(avconfig_h); lib.installConfigHeader(avconfig_h); ```

2 files changed, 99 insertions(+), 33 deletions(-)

lib/std/Build/CompileStep.zig+6
......@@ -442,6 +442,12 @@ pub fn installHeader(a: *CompileStep, src_path: []const u8, dest_rel_path: []con
442442 a.installed_headers.append(&install_file.step) catch @panic("OOM");
443443}
444444
445pub fn installConfigHeader(a: *CompileStep, config_header: *ConfigHeaderStep) void {
446 const install_file = a.builder.addInstallFileWithDir(config_header.getOutputSource(), .header, config_header.output_path);
447 a.builder.getInstallStep().dependOn(&install_file.step);
448 a.installed_headers.append(&install_file.step) catch unreachable;
449}
450
445451pub fn installHeadersDirectory(
446452 a: *CompileStep,
447453 src_dir_path: []const u8,
lib/std/Build/ConfigHeaderStep.zig+93-33
......@@ -11,6 +11,8 @@ pub const Style = enum {
1111 /// The configure format supported by CMake. It uses `@@FOO@@` and
1212 /// `#cmakedefine` for template substitution.
1313 cmake,
14 /// Generate a c header from scratch with the values passed.
15 generated,
1416};
1517
1618pub const Value = union(enum) {
......@@ -27,9 +29,12 @@ builder: *std.Build,
2729source: std.Build.FileSource,
2830style: Style,
2931values: std.StringHashMap(Value),
32gen_keys: std.ArrayList([]const u8),
33gen_values: std.ArrayList(Value),
3034max_bytes: usize = 2 * 1024 * 1024,
3135output_dir: []const u8,
32output_basename: []const u8,
36output_path: []const u8,
37output_gen: std.build.GeneratedFile,
3338
3439pub fn create(builder: *std.Build, source: std.Build.FileSource, style: Style) *ConfigHeaderStep {
3540 const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM");
......@@ -40,64 +45,72 @@ pub fn create(builder: *std.Build, source: std.Build.FileSource, style: Style) *
4045 .source = source,
4146 .style = style,
4247 .values = std.StringHashMap(Value).init(builder.allocator),
48 .gen_keys = std.ArrayList([]const u8).init(builder.allocator),
49 .gen_values = std.ArrayList(Value).init(builder.allocator),
4350 .output_dir = undefined,
44 .output_basename = "config.h",
51 .output_path = "config.h",
52 .output_gen = std.build.GeneratedFile{ .step = &self.step },
4553 };
54
4655 switch (source) {
4756 .path => |p| {
48 const basename = std.fs.path.basename(p);
49 if (std.mem.endsWith(u8, basename, ".h.in")) {
50 self.output_basename = basename[0 .. basename.len - 3];
57 self.output_path = p;
58
59 switch (style) {
60 .autoconf, .cmake => {
61 if (std.mem.endsWith(u8, p, ".h.in")) {
62 self.output_path = p[0 .. p.len - 3];
63 }
64 },
65 else => {},
5166 }
5267 },
5368 else => {},
5469 }
70
5571 return self;
5672}
5773
74pub fn getOutputSource(self: *ConfigHeaderStep) std.build.FileSource {
75 return std.build.FileSource{ .generated = &self.output_gen };
76}
77
5878pub fn addValues(self: *ConfigHeaderStep, values: anytype) void {
5979 return addValuesInner(self, values) catch @panic("OOM");
6080}
6181
6282fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void {
6383 inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| {
64 try putValue(self, field.name, field.type, @field(values, field.name));
84 const val = try getValue(self, field.type, @field(values, field.name));
85 switch (self.style) {
86 .generated => {
87 try self.gen_keys.append(field.name);
88 try self.gen_values.append(val);
89 },
90 else => try self.values.put(field.name, val),
91 }
6592 }
6693}
6794
68fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v: T) !void {
95fn getValue(self: *ConfigHeaderStep, comptime T: type, v: T) !Value {
6996 switch (@typeInfo(T)) {
70 .Null => {
71 try self.values.put(field_name, .undef);
72 },
73 .Void => {
74 try self.values.put(field_name, .defined);
75 },
76 .Bool => {
77 try self.values.put(field_name, .{ .boolean = v });
78 },
79 .Int => {
80 try self.values.put(field_name, .{ .int = v });
81 },
82 .ComptimeInt => {
83 try self.values.put(field_name, .{ .int = v });
84 },
85 .EnumLiteral => {
86 try self.values.put(field_name, .{ .ident = @tagName(v) });
87 },
97 .Null => return .undef,
98 .Void => return .defined,
99 .Bool => return .{ .boolean = v },
100 .Int, .ComptimeInt => return .{ .int = v },
101 .EnumLiteral => return .{ .ident = @tagName(v) },
88102 .Optional => {
89103 if (v) |x| {
90 return putValue(self, field_name, @TypeOf(x), x);
104 return getValue(self, @TypeOf(x), x);
91105 } else {
92 try self.values.put(field_name, .undef);
106 return .undef;
93107 }
94108 },
95109 .Pointer => |ptr| {
96110 switch (@typeInfo(ptr.child)) {
97111 .Array => |array| {
98112 if (ptr.size == .One and array.child == u8) {
99 try self.values.put(field_name, .{ .string = v });
100 return;
113 return .{ .string = v };
101114 }
102115 },
103116 else => {},
......@@ -113,7 +126,10 @@ fn make(step: *Step) !void {
113126 const self = @fieldParentPtr(ConfigHeaderStep, "step", step);
114127 const gpa = self.builder.allocator;
115128 const src_path = self.source.getPath(self.builder);
116 const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
129 const contents = switch (self.style) {
130 .generated => src_path,
131 else => try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes),
132 };
117133
118134 // The cache is used here not really as a way to speed things up - because writing
119135 // the data to a file would probably be very fast - but as a way to find a canonical
......@@ -146,8 +162,21 @@ fn make(step: *Step) !void {
146162 self.output_dir = try std.fs.path.join(gpa, &[_][]const u8{
147163 self.builder.cache_root, "o", &hash_basename,
148164 });
149 var dir = std.fs.cwd().makeOpenPath(self.output_dir, .{}) catch |err| {
150 std.debug.print("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) });
165
166 // If output_path has directory parts, deal with them. Example:
167 // output_dir is zig-cache/o/HASH
168 // output_path is libavutil/avconfig.h
169 // We want to open directory zig-cache/o/HASH/libavutil/
170 // but keep output_dir as zig-cache/o/HASH for -I include
171 var outdir = self.output_dir;
172 var outpath = self.output_path;
173 if (std.fs.path.dirname(self.output_path)) |d| {
174 outdir = try std.fs.path.join(gpa, &[_][]const u8{ self.output_dir, d });
175 outpath = std.fs.path.basename(self.output_path);
176 }
177
178 var dir = std.fs.cwd().makeOpenPath(outdir, .{}) catch |err| {
179 std.debug.print("unable to make path {s}: {s}\n", .{ outdir, @errorName(err) });
151180 return err;
152181 };
153182 defer dir.close();
......@@ -164,9 +193,12 @@ fn make(step: *Step) !void {
164193 switch (self.style) {
165194 .autoconf => try render_autoconf(contents, &output, &values_copy, src_path),
166195 .cmake => try render_cmake(contents, &output, &values_copy, src_path),
196 .generated => try render_generated(gpa, &output, &self.gen_keys, &self.gen_values, self.source.getDisplayName()),
167197 }
168198
169 try dir.writeFile(self.output_basename, output.items);
199 try dir.writeFile(outpath, output.items);
200
201 self.output_gen.path = try std.fs.path.join(gpa, &[_][]const u8{ self.output_dir, self.output_path });
170202}
171203
172204fn render_autoconf(
......@@ -297,3 +329,31 @@ fn renderValue(output: *std.ArrayList(u8), name: []const u8, value: Value) !void
297329 },
298330 }
299331}
332
333fn render_generated(
334 gpa: std.mem.Allocator,
335 output: *std.ArrayList(u8),
336 keys: *std.ArrayList([]const u8),
337 values: *std.ArrayList(Value),
338 src_path: []const u8,
339) !void {
340 var include_guard = try gpa.dupe(u8, src_path);
341 defer gpa.free(include_guard);
342
343 for (include_guard) |*ch| {
344 if (ch.* == '.' or std.fs.path.isSep(ch.*)) {
345 ch.* = '_';
346 } else {
347 ch.* = std.ascii.toUpper(ch.*);
348 }
349 }
350
351 try output.writer().print("#ifndef {s}\n", .{include_guard});
352 try output.writer().print("#define {s}\n", .{include_guard});
353
354 for (keys.items) |k, i| {
355 try renderValue(output, k, values.items[i]);
356 }
357
358 try output.writer().print("#endif /* {s} */\n", .{include_guard});
359}