Files
zig/lib/std/Build/Step/ConfigHeader.zig
2026-05-04 20:11:13 -07:00

185 lines
6.2 KiB
Zig

const ConfigHeader = @This();
const std = @import("std");
const Io = std.Io;
const Step = std.Build.Step;
const Allocator = std.mem.Allocator;
const Configuration = std.Build.Configuration;
step: Step,
values: std.array_hash_map.String(Value),
/// This directory contains the generated file under the name `include_path`.
generated_dir: Configuration.GeneratedFileIndex,
style: Style,
max_bytes: usize,
include_path: []const u8,
include_guard_override: ?[]const u8,
pub const base_tag: Step.Tag = .config_header;
pub const Style = union(enum) {
/// A configure format supported by autotools that uses `#undef foo` to
/// mark lines that can be substituted with different values.
autoconf_undef: std.Build.LazyPath,
/// A configure format supported by autotools that uses `@FOO@` output variables.
autoconf_at: std.Build.LazyPath,
/// The configure format supported by CMake. It uses `@FOO@`, `${}` and
/// `#cmakedefine` for template substitution.
cmake: std.Build.LazyPath,
/// Instead of starting with an input file, start with nothing.
blank,
/// Start with nothing, like blank, and output a nasm .asm file.
nasm,
pub fn getPath(style: Style) ?std.Build.LazyPath {
switch (style) {
.autoconf_undef, .autoconf_at, .cmake => |s| return s,
.blank, .nasm => return null,
}
}
};
pub const Value = union(enum) {
undef,
defined,
boolean: bool,
int: i64,
ident: []const u8,
string: []const u8,
};
pub const Options = struct {
style: Style = .blank,
max_bytes: usize = 2 * 1024 * 1024,
include_path: ?[]const u8 = null,
first_ret_addr: ?usize = null,
include_guard_override: ?[]const u8 = null,
};
pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
const graph = owner.graph;
const arena = graph.arena;
const config_header = arena.create(ConfigHeader) catch @panic("OOM");
var include_path: []const u8 = "config.h";
if (options.style.getPath()) |s| default_include_path: {
const wc = &graph.wip_configuration;
const sub_path = switch (s) {
.src_path => |sp| sp.sub_path,
.generated => break :default_include_path,
.cwd_relative => |sub_path| sub_path,
.relative => |r| wc.stringSlice(r.sub_path),
.dependency => |dependency| dependency.sub_path,
};
const basename = std.fs.path.basename(sub_path);
if (std.mem.endsWith(u8, basename, ".h.in")) {
include_path = basename[0 .. basename.len - 3];
}
}
if (options.include_path) |p| {
include_path = p;
}
const name = if (options.style.getPath()) |s|
owner.fmt("configure {t} header {f} to {s}", .{ options.style, s.fmt(graph), include_path })
else
owner.fmt("configure {t} header to {s}", .{ options.style, include_path });
config_header.* = .{
.step = .init(.{
.tag = base_tag,
.name = name,
.owner = owner,
.first_ret_addr = options.first_ret_addr orelse @returnAddress(),
}),
.style = options.style,
.values = .empty,
.max_bytes = options.max_bytes,
.include_path = graph.dupeString(include_path),
.include_guard_override = options.include_guard_override,
.generated_dir = graph.addGeneratedFile(&config_header.step),
};
if (options.style.getPath()) |s| {
s.addStepDependencies(&config_header.step);
}
return config_header;
}
pub fn addIdent(config_header: *ConfigHeader, name: []const u8, value: []const u8) void {
const arena = config_header.step.owner.allocator;
config_header.values.put(arena, name, .{ .ident = value }) catch @panic("OOM");
}
pub fn addValue(config_header: *ConfigHeader, name: []const u8, comptime T: type, value: T) void {
return addValueInner(config_header, name, T, value) catch @panic("OOM");
}
fn addValueInner(config_header: *ConfigHeader, name: []const u8, comptime T: type, value: T) !void {
const arena = config_header.step.owner.allocator;
switch (@typeInfo(T)) {
.null => {
try config_header.values.put(arena, name, .undef);
},
.void => {
try config_header.values.put(arena, name, .defined);
},
.bool => {
try config_header.values.put(arena, name, .{ .boolean = value });
},
.int => {
try config_header.values.put(arena, name, .{ .int = value });
},
.comptime_int => {
try config_header.values.put(arena, name, .{ .int = value });
},
.@"enum", .enum_literal => {
try config_header.values.put(arena, name, .{ .ident = @tagName(value) });
},
.optional => {
if (value) |x| {
return addValueInner(config_header, name, @TypeOf(x), x);
} else {
try config_header.values.put(arena, name, .undef);
}
},
.pointer => |ptr| {
switch (@typeInfo(ptr.child)) {
.array => |array| {
if (ptr.size == .one and array.child == u8) {
try config_header.values.put(arena, name, .{ .string = value });
return;
}
},
.int => {
if (ptr.size == .slice and ptr.child == u8) {
try config_header.values.put(arena, name, .{ .string = value });
return;
}
},
else => {},
}
@compileError("unsupported ConfigHeader value type: " ++ @typeName(T));
},
else => @compileError("unsupported ConfigHeader value type: " ++ @typeName(T)),
}
}
pub fn addValues(config_header: *ConfigHeader, values: anytype) void {
inline for (@typeInfo(@TypeOf(values)).@"struct".fields) |field| {
addValue(config_header, field.name, field.type, @field(values, field.name));
}
}
pub fn getOutputDir(ch: *ConfigHeader) std.Build.LazyPath {
return .{ .generated = .{ .index = ch.generated_dir } };
}
pub fn getOutputFile(ch: *ConfigHeader) std.Build.LazyPath {
return ch.getOutputDir().path(ch.step.owner, ch.include_path);
}