diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig index 879793f781..a7f747625d 100644 --- a/lib/std/Build/CompileStep.zig +++ b/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, diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig index 58a78b939d..49900c7f6e 100644 --- a/lib/std/Build/ConfigHeaderStep.zig +++ b/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}); +}