| 1 | const ConfigHeader = @This(); |
| 2 | |
| 3 | const std = @import("std"); |
| 4 | const Io = std.Io; |
| 5 | const Step = std.Build.Step; |
| 6 | const Allocator = std.mem.Allocator; |
| 7 | const Configuration = std.Build.Configuration; |
| 8 | |
| 9 | step: Step, |
| 10 | values: std.array_hash_map.String(Value) = .empty, |
| 11 | /// This directory contains the generated file under the name `include_path`. |
| 12 | generated_dir: Configuration.GeneratedFileIndex, |
| 13 | |
| 14 | style: Style, |
| 15 | input_size_limit: ?u64, |
| 16 | include_path: []const u8, |
| 17 | include_guard: Configuration.OptionalString, |
| 18 | |
| 19 | pub const base_tag: Step.Tag = .config_header; |
| 20 | |
| 21 | pub 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 | |
| 46 | pub const Value = union(enum) { |
| 47 | undef, |
| 48 | defined, |
| 49 | boolean: bool, |
| 50 | int: i64, |
| 51 | ident: []const u8, |
| 52 | string: []const u8, |
| 53 | }; |
| 54 | |
| 55 | pub 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 | |
| 63 | pub 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 | |
| 113 | pub 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 | |
| 118 | pub 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 | |
| 122 | fn 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 | |
| 173 | pub 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 | |
| 180 | pub fn getOutputDir(ch: *ConfigHeader) std.Build.LazyPath { |
| 181 | return .{ .generated = .{ .index = ch.generated_dir } }; |
| 182 | } |
| 183 | |
| 184 | pub fn getOutputFile(ch: *ConfigHeader) std.Build.LazyPath { |
| 185 | return ch.getOutputDir().path(ch.step.owner, ch.include_path); |
| 186 | } |