1const ConfigHeader = @This();
2
3const std = @import("std");
4const Io = std.Io;
5const Step = std.Build.Step;
6const Allocator = std.mem.Allocator;
7const Configuration = std.Build.Configuration;
8
9step: Step,
10values: std.array_hash_map.String(Value) = .empty,
11/// This directory contains the generated file under the name `include_path`.
12generated_dir: Configuration.GeneratedFileIndex,
13
14style: Style,
15input_size_limit: ?u64,
16include_path: []const u8,
17include_guard: Configuration.OptionalString,
18
19pub const base_tag: Step.Tag = .config_header;
20
21pub const Style = union(enum) {
22 /// A configure format supported by autotools that uses `#undef foo` to
23 /// mark lines that can be substituted with different values.
24 autoconf_undef: std.Build.LazyPath,
25 /// A configure format supported by autotools that uses `@FOO@` output variables.
26 autoconf_at: std.Build.LazyPath,
27 /// The configure format supported by CMake. It uses `@FOO@`, `${}` and
28 /// `#cmakedefine` for template substitution.
29 cmake: std.Build.LazyPath,
30 /// The configure format supported by Meson. It uses `@FOO@`, and
31 /// `#mesondefine` for template substitution.
32 meson: std.Build.LazyPath,
33 /// Instead of starting with an input file, start with nothing.
34 blank,
35 /// Start with nothing, like blank, and output a nasm .asm file.
36 nasm,
37
38 pub fn getPath(style: Style) ?std.Build.LazyPath {
39 switch (style) {
40 .autoconf_undef, .autoconf_at, .cmake, .meson => |s| return s,
41 .blank, .nasm => return null,
42 }
43 }
44};
45
46pub const Value = union(enum) {
47 undef,
48 defined,
49 boolean: bool,
50 int: i64,
51 ident: []const u8,
52 string: []const u8,
53};
54
55pub const Options = struct {
56 style: Style = .blank,
57 max_bytes: ?u64 = null,
58 include_path: ?[]const u8 = null,
59 include_guard: ?[]const u8 = null,
60 first_ret_addr: ?usize = null,
61};
62
63pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
64 const graph = owner.graph;
65 const arena = graph.arena;
66 const wc = &graph.wip_configuration;
67 const config_header = graph.create(ConfigHeader);
68
69 const include_path: []const u8 = p: {
70 if (options.include_path) |p|
71 break :p graph.dupeString(p);
72
73 if (options.style.getPath()) |s| default: {
74 const sub_path = switch (s) {
75 .src_path => |sp| sp.sub_path,
76 .generated => break :default,
77 .cwd_relative => |sub_path| sub_path,
78 .relative => |r| r.sub_path,
79 .dependency => |dependency| dependency.sub_path,
80 };
81 const basename = Io.Dir.path.basename(sub_path);
82 if (std.mem.endsWith(u8, basename, ".h.in"))
83 break :p graph.dupeString(basename[0 .. basename.len - 3]);
84 }
85 break :p "config.h";
86 };
87
88 const name = if (options.style.getPath()) |s|
89 arena.print("configure {t} header {f} to {s}", .{ options.style, s, include_path }) catch @panic("OOM")
90 else
91 arena.print("configure {t} header to {s}", .{ options.style, include_path }) catch @panic("OOM");
92
93 config_header.* = .{
94 .step = .init(.{
95 .tag = base_tag,
96 .name = name,
97 .owner = owner,
98 .first_ret_addr = options.first_ret_addr orelse @returnAddress(),
99 }),
100 .style = options.style,
101 .input_size_limit = options.max_bytes,
102 .include_path = include_path,
103 .include_guard = if (options.include_guard) |s| .init(wc.addString(s) catch @panic("OOM")) else .none,
104 .generated_dir = graph.addGeneratedFile(&config_header.step),
105 };
106
107 if (options.style.getPath()) |s| {
108 s.addStepDependencies(&config_header.step);
109 }
110 return config_header;
111}
112
113pub fn addIdent(config_header: *ConfigHeader, name: []const u8, value: []const u8) void {
114 const arena = config_header.step.owner.allocator;
115 config_header.values.put(arena, name, .{ .ident = value }) catch @panic("OOM");
116}
117
118pub fn addValue(config_header: *ConfigHeader, name: []const u8, comptime T: type, value: T) void {
119 return addValueInner(config_header, name, T, value) catch @panic("OOM");
120}
121
122fn addValueInner(config_header: *ConfigHeader, name: []const u8, comptime T: type, value: T) !void {
123 const arena = config_header.step.owner.allocator;
124 switch (@typeInfo(T)) {
125 .null => {
126 try config_header.values.put(arena, name, .undef);
127 },
128 .void => {
129 try config_header.values.put(arena, name, .defined);
130 },
131 .bool => {
132 try config_header.values.put(arena, name, .{ .boolean = value });
133 },
134 .int => {
135 try config_header.values.put(arena, name, .{ .int = value });
136 },
137 .comptime_int => {
138 try config_header.values.put(arena, name, .{ .int = value });
139 },
140 .@"enum", .enum_literal => {
141 try config_header.values.put(arena, name, .{ .ident = @tagName(value) });
142 },
143 .optional => {
144 if (value) |x| {
145 return addValueInner(config_header, name, @TypeOf(x), x);
146 } else {
147 try config_header.values.put(arena, name, .undef);
148 }
149 },
150 .pointer => |ptr| {
151 switch (@typeInfo(ptr.child)) {
152 .array => |array| {
153 if (ptr.size == .one and array.child == u8) {
154 try config_header.values.put(arena, name, .{ .string = value });
155 return;
156 }
157 },
158 .int => {
159 if (ptr.size == .slice and ptr.child == u8) {
160 try config_header.values.put(arena, name, .{ .string = value });
161 return;
162 }
163 },
164 else => {},
165 }
166
167 @compileError("unsupported ConfigHeader value type: " ++ @typeName(T));
168 },
169 else => @compileError("unsupported ConfigHeader value type: " ++ @typeName(T)),
170 }
171}
172
173pub fn addValues(config_header: *ConfigHeader, values: anytype) void {
174 const info = @typeInfo(@TypeOf(values)).@"struct";
175 inline for (info.field_names, info.field_types) |field_name, field_type| {
176 addValue(config_header, field_name, field_type, @field(values, field_name));
177 }
178}
179
180pub fn getOutputDir(ch: *ConfigHeader) std.Build.LazyPath {
181 return .{ .generated = .{ .index = ch.generated_dir } };
182}
183
184pub fn getOutputFile(ch: *ConfigHeader) std.Build.LazyPath {
185 return ch.getOutputDir().path(ch.step.owner, ch.include_path);
186}