aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Build/OptionsStep.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2023-01-31 23:15:59 -0500
committerGitHub <noreply@github.com>2023-01-31 23:15:59 -0500
commitefa25e7d5bca63e83f6a653058c05dacc771d19e (patch)
tree56b34421822584702b1647ab0eb38aec160386b4 /lib/std/Build/OptionsStep.zig
parent6f13a725a3249c7f0a0f5258ac00003cd132bf15 (diff)
parent8d37c6f71c790faecdb6acdd2868823be2bd2496 (diff)
downloadzig-efa25e7d5bca63e83f6a653058c05dacc771d19e.tar.gz
zig-efa25e7d5bca63e83f6a653058c05dacc771d19e.zip
Merge pull request #14498 from ziglang/zig-build-api
Several enhancements to the build system. Many breaking changes to the API. * combine `std.build` and `std.build.Builder` into `std.Build` * eliminate `setTarget` and `setBuildMode`; use an options struct for `b.addExecutable` and friends * implement passing options to dependency packages. closes #14285 * rename `LibExeObjStep` to `CompileStep` * move src.type.CType to std lib, use it from std.Build, this helps with populating config.h files.
Diffstat (limited to 'lib/std/Build/OptionsStep.zig')
-rw-r--r--lib/std/Build/OptionsStep.zig371
1 files changed, 371 insertions, 0 deletions
diff --git a/lib/std/Build/OptionsStep.zig b/lib/std/Build/OptionsStep.zig
new file mode 100644
index 0000000000..a353737512
--- /dev/null
+++ b/lib/std/Build/OptionsStep.zig
@@ -0,0 +1,371 @@
+const std = @import("../std.zig");
+const builtin = @import("builtin");
+const fs = std.fs;
+const Step = std.Build.Step;
+const GeneratedFile = std.Build.GeneratedFile;
+const CompileStep = std.Build.CompileStep;
+const FileSource = std.Build.FileSource;
+
+const OptionsStep = @This();
+
+pub const base_id = .options;
+
+step: Step,
+generated_file: GeneratedFile,
+builder: *std.Build,
+
+contents: std.ArrayList(u8),
+artifact_args: std.ArrayList(OptionArtifactArg),
+file_source_args: std.ArrayList(OptionFileSourceArg),
+
+pub fn create(builder: *std.Build) *OptionsStep {
+ const self = builder.allocator.create(OptionsStep) catch @panic("OOM");
+ self.* = .{
+ .builder = builder,
+ .step = Step.init(.options, "options", builder.allocator, make),
+ .generated_file = undefined,
+ .contents = std.ArrayList(u8).init(builder.allocator),
+ .artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator),
+ .file_source_args = std.ArrayList(OptionFileSourceArg).init(builder.allocator),
+ };
+ self.generated_file = .{ .step = &self.step };
+
+ return self;
+}
+
+pub fn addOption(self: *OptionsStep, comptime T: type, name: []const u8, value: T) void {
+ return addOptionFallible(self, T, name, value) catch @panic("unhandled error");
+}
+
+fn addOptionFallible(self: *OptionsStep, comptime T: type, name: []const u8, value: T) !void {
+ const out = self.contents.writer();
+ switch (T) {
+ []const []const u8 => {
+ try out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)});
+ for (value) |slice| {
+ try out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)});
+ }
+ try out.writeAll("};\n");
+ return;
+ },
+ [:0]const u8 => {
+ try out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) });
+ return;
+ },
+ []const u8 => {
+ try out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) });
+ return;
+ },
+ ?[:0]const u8 => {
+ try out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)});
+ if (value) |payload| {
+ try out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)});
+ } else {
+ try out.writeAll("null;\n");
+ }
+ return;
+ },
+ ?[]const u8 => {
+ try out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)});
+ if (value) |payload| {
+ try out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)});
+ } else {
+ try out.writeAll("null;\n");
+ }
+ return;
+ },
+ std.builtin.Version => {
+ try out.print(
+ \\pub const {}: @import("std").builtin.Version = .{{
+ \\ .major = {d},
+ \\ .minor = {d},
+ \\ .patch = {d},
+ \\}};
+ \\
+ , .{
+ std.zig.fmtId(name),
+
+ value.major,
+ value.minor,
+ value.patch,
+ });
+ return;
+ },
+ std.SemanticVersion => {
+ try out.print(
+ \\pub const {}: @import("std").SemanticVersion = .{{
+ \\ .major = {d},
+ \\ .minor = {d},
+ \\ .patch = {d},
+ \\
+ , .{
+ std.zig.fmtId(name),
+
+ value.major,
+ value.minor,
+ value.patch,
+ });
+ if (value.pre) |some| {
+ try out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)});
+ }
+ if (value.build) |some| {
+ try out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)});
+ }
+ try out.writeAll("};\n");
+ return;
+ },
+ else => {},
+ }
+ switch (@typeInfo(T)) {
+ .Enum => |enum_info| {
+ try out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))});
+ inline for (enum_info.fields) |field| {
+ try out.print(" {},\n", .{std.zig.fmtId(field.name)});
+ }
+ try out.writeAll("};\n");
+ try out.print("pub const {}: {s} = {s}.{s};\n", .{
+ std.zig.fmtId(name),
+ std.zig.fmtId(@typeName(T)),
+ std.zig.fmtId(@typeName(T)),
+ std.zig.fmtId(@tagName(value)),
+ });
+ return;
+ },
+ else => {},
+ }
+ try out.print("pub const {}: {s} = ", .{ std.zig.fmtId(name), @typeName(T) });
+ try printLiteral(out, value, 0);
+ try out.writeAll(";\n");
+}
+
+// TODO: non-recursive?
+fn printLiteral(out: anytype, val: anytype, indent: u8) !void {
+ const T = @TypeOf(val);
+ switch (@typeInfo(T)) {
+ .Array => {
+ try out.print("{s} {{\n", .{@typeName(T)});
+ for (val) |item| {
+ try out.writeByteNTimes(' ', indent + 4);
+ try printLiteral(out, item, indent + 4);
+ try out.writeAll(",\n");
+ }
+ try out.writeByteNTimes(' ', indent);
+ try out.writeAll("}");
+ },
+ .Pointer => |p| {
+ if (p.size != .Slice) {
+ @compileError("Non-slice pointers are not yet supported in build options");
+ }
+ try out.print("&[_]{s} {{\n", .{@typeName(p.child)});
+ for (val) |item| {
+ try out.writeByteNTimes(' ', indent + 4);
+ try printLiteral(out, item, indent + 4);
+ try out.writeAll(",\n");
+ }
+ try out.writeByteNTimes(' ', indent);
+ try out.writeAll("}");
+ },
+ .Optional => {
+ if (val) |inner| {
+ return printLiteral(out, inner, indent);
+ } else {
+ return out.writeAll("null");
+ }
+ },
+ .Void,
+ .Bool,
+ .Int,
+ .ComptimeInt,
+ .Float,
+ .Null,
+ => try out.print("{any}", .{val}),
+ else => @compileError(std.fmt.comptimePrint("`{s}` are not yet supported as build options", .{@tagName(@typeInfo(T))})),
+ }
+}
+
+/// The value is the path in the cache dir.
+/// Adds a dependency automatically.
+pub fn addOptionFileSource(
+ self: *OptionsStep,
+ name: []const u8,
+ source: FileSource,
+) void {
+ self.file_source_args.append(.{
+ .name = name,
+ .source = source.dupe(self.builder),
+ }) catch @panic("OOM");
+ source.addStepDependencies(&self.step);
+}
+
+/// The value is the path in the cache dir.
+/// Adds a dependency automatically.
+pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *CompileStep) void {
+ self.artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch @panic("OOM");
+ self.step.dependOn(&artifact.step);
+}
+
+pub fn getPackage(self: *OptionsStep, package_name: []const u8) std.Build.Pkg {
+ return .{ .name = package_name, .source = self.getSource() };
+}
+
+pub fn getSource(self: *OptionsStep) FileSource {
+ return .{ .generated = &self.generated_file };
+}
+
+fn make(step: *Step) !void {
+ const self = @fieldParentPtr(OptionsStep, "step", step);
+
+ for (self.artifact_args.items) |item| {
+ self.addOption(
+ []const u8,
+ item.name,
+ self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)),
+ );
+ }
+
+ for (self.file_source_args.items) |item| {
+ self.addOption(
+ []const u8,
+ item.name,
+ item.source.getPath(self.builder),
+ );
+ }
+
+ const options_directory = self.builder.pathFromRoot(
+ try fs.path.join(
+ self.builder.allocator,
+ &[_][]const u8{ self.builder.cache_root, "options" },
+ ),
+ );
+
+ try fs.cwd().makePath(options_directory);
+
+ const options_file = try fs.path.join(
+ self.builder.allocator,
+ &[_][]const u8{ options_directory, &self.hashContentsToFileName() },
+ );
+
+ try fs.cwd().writeFile(options_file, self.contents.items);
+
+ self.generated_file.path = options_file;
+}
+
+fn hashContentsToFileName(self: *OptionsStep) [64]u8 {
+ // This implementation is copied from `WriteFileStep.make`
+
+ var hash = std.crypto.hash.blake2.Blake2b384.init(.{});
+
+ // Random bytes to make OptionsStep unique. Refresh this with
+ // new random bytes when OptionsStep implementation is modified
+ // in a non-backwards-compatible way.
+ hash.update("yL0Ya4KkmcCjBlP8");
+ hash.update(self.contents.items);
+
+ var digest: [48]u8 = undefined;
+ hash.final(&digest);
+ var hash_basename: [64]u8 = undefined;
+ _ = fs.base64_encoder.encode(&hash_basename, &digest);
+ return hash_basename;
+}
+
+const OptionArtifactArg = struct {
+ name: []const u8,
+ artifact: *CompileStep,
+};
+
+const OptionFileSourceArg = struct {
+ name: []const u8,
+ source: FileSource,
+};
+
+test "OptionsStep" {
+ if (builtin.os.tag == .wasi) return error.SkipZigTest;
+
+ var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer arena.deinit();
+
+ const host = try std.zig.system.NativeTargetInfo.detect(.{});
+
+ var builder = try std.Build.create(
+ arena.allocator(),
+ "test",
+ "test",
+ "test",
+ "test",
+ host,
+ );
+ defer builder.destroy();
+
+ const options = builder.addOptions();
+
+ // TODO this regressed at some point
+ //const KeywordEnum = enum {
+ // @"0.8.1",
+ //};
+
+ const nested_array = [2][2]u16{
+ [2]u16{ 300, 200 },
+ [2]u16{ 300, 200 },
+ };
+ const nested_slice: []const []const u16 = &[_][]const u16{ &nested_array[0], &nested_array[1] };
+
+ options.addOption(usize, "option1", 1);
+ options.addOption(?usize, "option2", null);
+ options.addOption(?usize, "option3", 3);
+ options.addOption(comptime_int, "option4", 4);
+ options.addOption([]const u8, "string", "zigisthebest");
+ options.addOption(?[]const u8, "optional_string", null);
+ options.addOption([2][2]u16, "nested_array", nested_array);
+ options.addOption([]const []const u16, "nested_slice", nested_slice);
+ //options.addOption(KeywordEnum, "keyword_enum", .@"0.8.1");
+ options.addOption(std.builtin.Version, "version", try std.builtin.Version.parse("0.1.2"));
+ options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar"));
+
+ try std.testing.expectEqualStrings(
+ \\pub const option1: usize = 1;
+ \\pub const option2: ?usize = null;
+ \\pub const option3: ?usize = 3;
+ \\pub const option4: comptime_int = 4;
+ \\pub const string: []const u8 = "zigisthebest";
+ \\pub const optional_string: ?[]const u8 = null;
+ \\pub const nested_array: [2][2]u16 = [2][2]u16 {
+ \\ [2]u16 {
+ \\ 300,
+ \\ 200,
+ \\ },
+ \\ [2]u16 {
+ \\ 300,
+ \\ 200,
+ \\ },
+ \\};
+ \\pub const nested_slice: []const []const u16 = &[_][]const u16 {
+ \\ &[_]u16 {
+ \\ 300,
+ \\ 200,
+ \\ },
+ \\ &[_]u16 {
+ \\ 300,
+ \\ 200,
+ \\ },
+ \\};
+ //\\pub const KeywordEnum = enum {
+ //\\ @"0.8.1",
+ //\\};
+ //\\pub const keyword_enum: KeywordEnum = KeywordEnum.@"0.8.1";
+ \\pub const version: @import("std").builtin.Version = .{
+ \\ .major = 0,
+ \\ .minor = 1,
+ \\ .patch = 2,
+ \\};
+ \\pub const semantic_version: @import("std").SemanticVersion = .{
+ \\ .major = 0,
+ \\ .minor = 1,
+ \\ .patch = 2,
+ \\ .pre = "foo",
+ \\ .build = "bar",
+ \\};
+ \\
+ , options.contents.items);
+
+ _ = try std.zig.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(0));
+}