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...@@ -442,6 +442,12 @@ pub fn installHeader(a: *CompileStep, src_path: []const u8, dest_rel_path: []con
442 a.installed_headers.append(&install_file.step) catch @panic("OOM");442 a.installed_headers.append(&install_file.step) catch @panic("OOM");
443}443}
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
445pub fn installHeadersDirectory(451pub fn installHeadersDirectory(
446 a: *CompileStep,452 a: *CompileStep,
447 src_dir_path: []const u8,453 src_dir_path: []const u8,
lib/std/Build/ConfigHeaderStep.zig+93-33
...@@ -11,6 +11,8 @@ pub const Style = enum {...@@ -11,6 +11,8 @@ pub const Style = enum {
11 /// The configure format supported by CMake. It uses `@@FOO@@` and11 /// The configure format supported by CMake. It uses `@@FOO@@` and
12 /// `#cmakedefine` for template substitution.12 /// `#cmakedefine` for template substitution.
13 cmake,13 cmake,
14 /// Generate a c header from scratch with the values passed.
15 generated,
14};16};
1517
16pub const Value = union(enum) {18pub const Value = union(enum) {
...@@ -27,9 +29,12 @@ builder: *std.Build,...@@ -27,9 +29,12 @@ builder: *std.Build,
27source: std.Build.FileSource,29source: std.Build.FileSource,
28style: Style,30style: Style,
29values: std.StringHashMap(Value),31values: std.StringHashMap(Value),
32gen_keys: std.ArrayList([]const u8),
33gen_values: std.ArrayList(Value),
30max_bytes: usize = 2 * 1024 * 1024,34max_bytes: usize = 2 * 1024 * 1024,
31output_dir: []const u8,35output_dir: []const u8,
32output_basename: []const u8,36output_path: []const u8,
37output_gen: std.build.GeneratedFile,
3338
34pub fn create(builder: *std.Build, source: std.Build.FileSource, style: Style) *ConfigHeaderStep {39pub fn create(builder: *std.Build, source: std.Build.FileSource, style: Style) *ConfigHeaderStep {
35 const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM");40 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) *...@@ -40,64 +45,72 @@ pub fn create(builder: *std.Build, source: std.Build.FileSource, style: Style) *
40 .source = source,45 .source = source,
41 .style = style,46 .style = style,
42 .values = std.StringHashMap(Value).init(builder.allocator),47 .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),
43 .output_dir = undefined,50 .output_dir = undefined,
44 .output_basename = "config.h",51 .output_path = "config.h",
52 .output_gen = std.build.GeneratedFile{ .step = &self.step },
45 };53 };
54
46 switch (source) {55 switch (source) {
47 .path => |p| {56 .path => |p| {
48 const basename = std.fs.path.basename(p);57 self.output_path = p;
49 if (std.mem.endsWith(u8, basename, ".h.in")) {58
50 self.output_basename = basename[0 .. basename.len - 3];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 => {},
51 }66 }
52 },67 },
53 else => {},68 else => {},
54 }69 }
70
55 return self;71 return self;
56}72}
5773
74pub fn getOutputSource(self: *ConfigHeaderStep) std.build.FileSource {
75 return std.build.FileSource{ .generated = &self.output_gen };
76}
77
58pub fn addValues(self: *ConfigHeaderStep, values: anytype) void {78pub fn addValues(self: *ConfigHeaderStep, values: anytype) void {
59 return addValuesInner(self, values) catch @panic("OOM");79 return addValuesInner(self, values) catch @panic("OOM");
60}80}
6181
62fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void {82fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void {
63 inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| {83 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 }
65 }92 }
66}93}
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 {
69 switch (@typeInfo(T)) {96 switch (@typeInfo(T)) {
70 .Null => {97 .Null => return .undef,
71 try self.values.put(field_name, .undef);98 .Void => return .defined,
72 },99 .Bool => return .{ .boolean = v },
73 .Void => {100 .Int, .ComptimeInt => return .{ .int = v },
74 try self.values.put(field_name, .defined);101 .EnumLiteral => return .{ .ident = @tagName(v) },
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 },
88 .Optional => {102 .Optional => {
89 if (v) |x| {103 if (v) |x| {
90 return putValue(self, field_name, @TypeOf(x), x);104 return getValue(self, @TypeOf(x), x);
91 } else {105 } else {
92 try self.values.put(field_name, .undef);106 return .undef;
93 }107 }
94 },108 },
95 .Pointer => |ptr| {109 .Pointer => |ptr| {
96 switch (@typeInfo(ptr.child)) {110 switch (@typeInfo(ptr.child)) {
97 .Array => |array| {111 .Array => |array| {
98 if (ptr.size == .One and array.child == u8) {112 if (ptr.size == .One and array.child == u8) {
99 try self.values.put(field_name, .{ .string = v });113 return .{ .string = v };
100 return;
101 }114 }
102 },115 },
103 else => {},116 else => {},
...@@ -113,7 +126,10 @@ fn make(step: *Step) !void {...@@ -113,7 +126,10 @@ fn make(step: *Step) !void {
113 const self = @fieldParentPtr(ConfigHeaderStep, "step", step);126 const self = @fieldParentPtr(ConfigHeaderStep, "step", step);
114 const gpa = self.builder.allocator;127 const gpa = self.builder.allocator;
115 const src_path = self.source.getPath(self.builder);128 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
118 // The cache is used here not really as a way to speed things up - because writing134 // The cache is used here not really as a way to speed things up - because writing
119 // the data to a file would probably be very fast - but as a way to find a canonical135 // 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 {...@@ -146,8 +162,21 @@ fn make(step: *Step) !void {
146 self.output_dir = try std.fs.path.join(gpa, &[_][]const u8{162 self.output_dir = try std.fs.path.join(gpa, &[_][]const u8{
147 self.builder.cache_root, "o", &hash_basename,163 self.builder.cache_root, "o", &hash_basename,
148 });164 });
149 var dir = std.fs.cwd().makeOpenPath(self.output_dir, .{}) catch |err| {165
150 std.debug.print("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) });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) });
151 return err;180 return err;
152 };181 };
153 defer dir.close();182 defer dir.close();
...@@ -164,9 +193,12 @@ fn make(step: *Step) !void {...@@ -164,9 +193,12 @@ fn make(step: *Step) !void {
164 switch (self.style) {193 switch (self.style) {
165 .autoconf => try render_autoconf(contents, &output, &values_copy, src_path),194 .autoconf => try render_autoconf(contents, &output, &values_copy, src_path),
166 .cmake => try render_cmake(contents, &output, &values_copy, src_path),195 .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()),
167 }197 }
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 });
170}202}
171203
172fn render_autoconf(204fn render_autoconf(
...@@ -297,3 +329,31 @@ fn renderValue(output: *std.ArrayList(u8), name: []const u8, value: Value) !void...@@ -297,3 +329,31 @@ fn renderValue(output: *std.ArrayList(u8), name: []const u8, value: Value) !void
297 },329 },
298 }330 }
299}331}
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}