Commit b04e48566c

David Vanderson <david.vanderson@gmail.com>
2023-01-20 03:30:43
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); ```
1 parent 11cc1c1
Changed files (2)
lib/std/Build/CompileStep.zig
@@ -442,6 +442,12 @@ pub fn installHeader(a: *CompileStep, src_path: []const u8, dest_rel_path: []con
     a.installed_headers.append(&install_file.step) catch @panic("OOM");
 }
 
+pub fn installConfigHeader(a: *CompileStep, config_header: *ConfigHeaderStep) void {
+    const install_file = a.builder.addInstallFileWithDir(config_header.getOutputSource(), .header, config_header.output_path);
+    a.builder.getInstallStep().dependOn(&install_file.step);
+    a.installed_headers.append(&install_file.step) catch unreachable;
+}
+
 pub fn installHeadersDirectory(
     a: *CompileStep,
     src_dir_path: []const u8,
lib/std/Build/ConfigHeaderStep.zig
@@ -11,6 +11,8 @@ pub const Style = enum {
     /// The configure format supported by CMake. It uses `@@FOO@@` and
     /// `#cmakedefine` for template substitution.
     cmake,
+    /// Generate a c header from scratch with the values passed.
+    generated,
 };
 
 pub const Value = union(enum) {
@@ -27,9 +29,12 @@ builder: *std.Build,
 source: std.Build.FileSource,
 style: Style,
 values: std.StringHashMap(Value),
+gen_keys: std.ArrayList([]const u8),
+gen_values: std.ArrayList(Value),
 max_bytes: usize = 2 * 1024 * 1024,
 output_dir: []const u8,
-output_basename: []const u8,
+output_path: []const u8,
+output_gen: std.build.GeneratedFile,
 
 pub fn create(builder: *std.Build, source: std.Build.FileSource, style: Style) *ConfigHeaderStep {
     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) *
         .source = source,
         .style = style,
         .values = std.StringHashMap(Value).init(builder.allocator),
+        .gen_keys = std.ArrayList([]const u8).init(builder.allocator),
+        .gen_values = std.ArrayList(Value).init(builder.allocator),
         .output_dir = undefined,
-        .output_basename = "config.h",
+        .output_path = "config.h",
+        .output_gen = std.build.GeneratedFile{ .step = &self.step },
     };
+
     switch (source) {
         .path => |p| {
-            const basename = std.fs.path.basename(p);
-            if (std.mem.endsWith(u8, basename, ".h.in")) {
-                self.output_basename = basename[0 .. basename.len - 3];
+            self.output_path = p;
+
+            switch (style) {
+                .autoconf, .cmake => {
+                    if (std.mem.endsWith(u8, p, ".h.in")) {
+                        self.output_path = p[0 .. p.len - 3];
+                    }
+                },
+                else => {},
             }
         },
         else => {},
     }
+
     return self;
 }
 
+pub fn getOutputSource(self: *ConfigHeaderStep) std.build.FileSource {
+    return std.build.FileSource{ .generated = &self.output_gen };
+}
+
 pub fn addValues(self: *ConfigHeaderStep, values: anytype) void {
     return addValuesInner(self, values) catch @panic("OOM");
 }
 
 fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void {
     inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| {
-        try putValue(self, field.name, field.type, @field(values, field.name));
+        const val = try getValue(self, field.type, @field(values, field.name));
+        switch (self.style) {
+            .generated => {
+                try self.gen_keys.append(field.name);
+                try self.gen_values.append(val);
+            },
+            else => try self.values.put(field.name, val),
+        }
     }
 }
 
-fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v: T) !void {
+fn getValue(self: *ConfigHeaderStep, comptime T: type, v: T) !Value {
     switch (@typeInfo(T)) {
-        .Null => {
-            try self.values.put(field_name, .undef);
-        },
-        .Void => {
-            try self.values.put(field_name, .defined);
-        },
-        .Bool => {
-            try self.values.put(field_name, .{ .boolean = v });
-        },
-        .Int => {
-            try self.values.put(field_name, .{ .int = v });
-        },
-        .ComptimeInt => {
-            try self.values.put(field_name, .{ .int = v });
-        },
-        .EnumLiteral => {
-            try self.values.put(field_name, .{ .ident = @tagName(v) });
-        },
+        .Null => return .undef,
+        .Void => return .defined,
+        .Bool => return .{ .boolean = v },
+        .Int, .ComptimeInt => return .{ .int = v },
+        .EnumLiteral => return .{ .ident = @tagName(v) },
         .Optional => {
             if (v) |x| {
-                return putValue(self, field_name, @TypeOf(x), x);
+                return getValue(self, @TypeOf(x), x);
             } else {
-                try self.values.put(field_name, .undef);
+                return .undef;
             }
         },
         .Pointer => |ptr| {
             switch (@typeInfo(ptr.child)) {
                 .Array => |array| {
                     if (ptr.size == .One and array.child == u8) {
-                        try self.values.put(field_name, .{ .string = v });
-                        return;
+                        return .{ .string = v };
                     }
                 },
                 else => {},
@@ -113,7 +126,10 @@ fn make(step: *Step) !void {
     const self = @fieldParentPtr(ConfigHeaderStep, "step", step);
     const gpa = self.builder.allocator;
     const src_path = self.source.getPath(self.builder);
-    const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
+    const contents = switch (self.style) {
+        .generated => src_path,
+        else => try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes),
+    };
 
     // The cache is used here not really as a way to speed things up - because writing
     // 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 {
     self.output_dir = try std.fs.path.join(gpa, &[_][]const u8{
         self.builder.cache_root, "o", &hash_basename,
     });
-    var dir = std.fs.cwd().makeOpenPath(self.output_dir, .{}) catch |err| {
-        std.debug.print("unable to make path {s}: {s}\n", .{ self.output_dir, @errorName(err) });
+
+    // If output_path has directory parts, deal with them.  Example:
+    // output_dir is zig-cache/o/HASH
+    // output_path is libavutil/avconfig.h
+    // We want to open directory zig-cache/o/HASH/libavutil/
+    // but keep output_dir as zig-cache/o/HASH for -I include
+    var outdir = self.output_dir;
+    var outpath = self.output_path;
+    if (std.fs.path.dirname(self.output_path)) |d| {
+        outdir = try std.fs.path.join(gpa, &[_][]const u8{ self.output_dir, d });
+        outpath = std.fs.path.basename(self.output_path);
+    }
+
+    var dir = std.fs.cwd().makeOpenPath(outdir, .{}) catch |err| {
+        std.debug.print("unable to make path {s}: {s}\n", .{ outdir, @errorName(err) });
         return err;
     };
     defer dir.close();
@@ -164,9 +193,12 @@ fn make(step: *Step) !void {
     switch (self.style) {
         .autoconf => try render_autoconf(contents, &output, &values_copy, src_path),
         .cmake => try render_cmake(contents, &output, &values_copy, src_path),
+        .generated => try render_generated(gpa, &output, &self.gen_keys, &self.gen_values, self.source.getDisplayName()),
     }
 
-    try dir.writeFile(self.output_basename, output.items);
+    try dir.writeFile(outpath, output.items);
+
+    self.output_gen.path = try std.fs.path.join(gpa, &[_][]const u8{ self.output_dir, self.output_path });
 }
 
 fn render_autoconf(
@@ -297,3 +329,31 @@ fn renderValue(output: *std.ArrayList(u8), name: []const u8, value: Value) !void
         },
     }
 }
+
+fn render_generated(
+    gpa: std.mem.Allocator,
+    output: *std.ArrayList(u8),
+    keys: *std.ArrayList([]const u8),
+    values: *std.ArrayList(Value),
+    src_path: []const u8,
+) !void {
+    var include_guard = try gpa.dupe(u8, src_path);
+    defer gpa.free(include_guard);
+
+    for (include_guard) |*ch| {
+        if (ch.* == '.' or std.fs.path.isSep(ch.*)) {
+            ch.* = '_';
+        } else {
+            ch.* = std.ascii.toUpper(ch.*);
+        }
+    }
+
+    try output.writer().print("#ifndef {s}\n", .{include_guard});
+    try output.writer().print("#define {s}\n", .{include_guard});
+
+    for (keys.items) |k, i| {
+        try renderValue(output, k, values.items[i]);
+    }
+
+    try output.writer().print("#endif /* {s} */\n", .{include_guard});
+}