diff --git a/build.zig b/build.zig index d727e1cac2..e2665557b8 100644 --- a/build.zig +++ b/build.zig @@ -109,9 +109,13 @@ pub fn build(b: *Builder) !void { b.default_step.dependOn(&exe.step); exe.single_threaded = single_threaded; - exe.addBuildOption(u32, "mem_leak_frames", mem_leak_frames); - exe.addBuildOption(bool, "skip_non_native", skip_non_native); - exe.addBuildOption(bool, "have_llvm", enable_llvm); + const exe_options = b.addOptions(); + exe.addOptions("build_options", exe_options); + + exe_options.addOption(u32, "mem_leak_frames", mem_leak_frames); + exe_options.addOption(bool, "skip_non_native", skip_non_native); + exe_options.addOption(bool, "have_llvm", enable_llvm); + if (enable_llvm) { const cmake_cfg = if (static_llvm) null else findAndParseConfigH(b, config_h_path_option); @@ -218,15 +222,15 @@ pub fn build(b: *Builder) !void { }, } }; - exe.addBuildOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version)); + exe_options.addOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version)); const semver = try std.SemanticVersion.parse(version); - exe.addBuildOption(std.SemanticVersion, "semver", semver); + exe_options.addOption(std.SemanticVersion, "semver", semver); - exe.addBuildOption(bool, "enable_logging", enable_logging); - exe.addBuildOption(bool, "enable_tracy", tracy != null); - exe.addBuildOption(bool, "is_stage1", is_stage1); - exe.addBuildOption(bool, "omit_stage2", omit_stage2); + exe_options.addOption(bool, "enable_logging", enable_logging); + exe_options.addOption(bool, "enable_tracy", tracy != null); + exe_options.addOption(bool, "is_stage1", is_stage1); + exe_options.addOption(bool, "omit_stage2", omit_stage2); if (tracy) |tracy_path| { const client_cpp = fs.path.join( b.allocator, @@ -248,20 +252,23 @@ pub fn build(b: *Builder) !void { const is_darling_enabled = b.option(bool, "enable-darling", "[Experimental] Use Darling to run cross compiled macOS tests") orelse false; const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc"); - test_stage2.addBuildOption(bool, "enable_logging", enable_logging); - test_stage2.addBuildOption(bool, "skip_non_native", skip_non_native); - test_stage2.addBuildOption(bool, "skip_compile_errors", skip_compile_errors); - test_stage2.addBuildOption(bool, "is_stage1", is_stage1); - test_stage2.addBuildOption(bool, "omit_stage2", omit_stage2); - test_stage2.addBuildOption(bool, "have_llvm", enable_llvm); - test_stage2.addBuildOption(bool, "enable_qemu", is_qemu_enabled); - test_stage2.addBuildOption(bool, "enable_wine", is_wine_enabled); - test_stage2.addBuildOption(bool, "enable_wasmtime", is_wasmtime_enabled); - test_stage2.addBuildOption(u32, "mem_leak_frames", mem_leak_frames * 2); - test_stage2.addBuildOption(bool, "enable_darling", is_darling_enabled); - test_stage2.addBuildOption(?[]const u8, "glibc_multi_install_dir", glibc_multi_dir); - test_stage2.addBuildOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version)); - test_stage2.addBuildOption(std.SemanticVersion, "semver", semver); + const test_stage2_options = b.addOptions(); + test_stage2.addOptions("build_options", test_stage2_options); + + test_stage2_options.addOption(bool, "enable_logging", enable_logging); + test_stage2_options.addOption(bool, "skip_non_native", skip_non_native); + test_stage2_options.addOption(bool, "skip_compile_errors", skip_compile_errors); + test_stage2_options.addOption(bool, "is_stage1", is_stage1); + test_stage2_options.addOption(bool, "omit_stage2", omit_stage2); + test_stage2_options.addOption(bool, "have_llvm", enable_llvm); + test_stage2_options.addOption(bool, "enable_qemu", is_qemu_enabled); + test_stage2_options.addOption(bool, "enable_wine", is_wine_enabled); + test_stage2_options.addOption(bool, "enable_wasmtime", is_wasmtime_enabled); + test_stage2_options.addOption(u32, "mem_leak_frames", mem_leak_frames * 2); + test_stage2_options.addOption(bool, "enable_darling", is_darling_enabled); + test_stage2_options.addOption(?[]const u8, "glibc_multi_install_dir", glibc_multi_dir); + test_stage2_options.addOption([:0]const u8, "version", try b.allocator.dupeZ(u8, version)); + test_stage2_options.addOption(std.SemanticVersion, "semver", semver); const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests"); test_stage2_step.dependOn(&test_stage2.step); diff --git a/lib/std/build.zig b/lib/std/build.zig index 1533f93085..afbe580f6d 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -23,6 +23,7 @@ pub const WriteFileStep = @import("build/WriteFileStep.zig"); pub const RunStep = @import("build/RunStep.zig"); pub const CheckFileStep = @import("build/CheckFileStep.zig"); pub const InstallRawStep = @import("build/InstallRawStep.zig"); +pub const OptionsStep = @import("build/OptionsStep.zig"); pub const Builder = struct { install_tls: TopLevelStep, @@ -247,6 +248,10 @@ pub const Builder = struct { return LibExeObjStep.createExecutable(builder, name, root_src); } + pub fn addOptions(self: *Builder) *OptionsStep { + return OptionsStep.create(self); + } + pub fn addObject(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep { return addObjectSource(self, name, convertOptionalPathToFileSource(root_src)); } @@ -1375,16 +1380,6 @@ pub const FileSource = union(enum) { } }; -const BuildOptionArtifactArg = struct { - name: []const u8, - artifact: *LibExeObjStep, -}; - -const BuildOptionFileSourceArg = struct { - name: []const u8, - source: FileSource, -}; - pub const LibExeObjStep = struct { pub const base_id = .lib_exe_obj; @@ -1434,9 +1429,6 @@ pub const LibExeObjStep = struct { out_lib_filename: []const u8, out_pdb_filename: []const u8, packages: ArrayList(Pkg), - build_options_contents: std.ArrayList(u8), - build_options_artifact_args: std.ArrayList(BuildOptionArtifactArg), - build_options_file_source_args: std.ArrayList(BuildOptionFileSourceArg), object_src: []const u8, @@ -1603,9 +1595,6 @@ pub const LibExeObjStep = struct { .rpaths = ArrayList([]const u8).init(builder.allocator), .framework_dirs = ArrayList([]const u8).init(builder.allocator), .object_src = undefined, - .build_options_contents = std.ArrayList(u8).init(builder.allocator), - .build_options_artifact_args = std.ArrayList(BuildOptionArtifactArg).init(builder.allocator), - .build_options_file_source_args = std.ArrayList(BuildOptionFileSourceArg).init(builder.allocator), .c_std = Builder.CStd.C99, .override_lib_dir = null, .main_pkg_path = null, @@ -2038,119 +2027,6 @@ pub const LibExeObjStep = struct { self.linkLibraryOrObject(obj); } - pub fn addBuildOption(self: *LibExeObjStep, comptime T: type, name: []const u8, value: T) void { - const out = self.build_options_contents.writer(); - switch (T) { - []const []const u8 => { - out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}) catch unreachable; - for (value) |slice| { - out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}) catch unreachable; - } - out.writeAll("};\n") catch unreachable; - return; - }, - [:0]const u8 => { - out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; - return; - }, - []const u8 => { - out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; - return; - }, - ?[:0]const u8 => { - out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; - if (value) |payload| { - out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; - } else { - out.writeAll("null;\n") catch unreachable; - } - return; - }, - ?[]const u8 => { - out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; - if (value) |payload| { - out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; - } else { - out.writeAll("null;\n") catch unreachable; - } - return; - }, - std.builtin.Version => { - out.print( - \\pub const {}: @import("std").builtin.Version = .{{ - \\ .major = {d}, - \\ .minor = {d}, - \\ .patch = {d}, - \\}}; - \\ - , .{ - std.zig.fmtId(name), - - value.major, - value.minor, - value.patch, - }) catch unreachable; - }, - std.SemanticVersion => { - out.print( - \\pub const {}: @import("std").SemanticVersion = .{{ - \\ .major = {d}, - \\ .minor = {d}, - \\ .patch = {d}, - \\ - , .{ - std.zig.fmtId(name), - - value.major, - value.minor, - value.patch, - }) catch unreachable; - if (value.pre) |some| { - out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; - } - if (value.build) |some| { - out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; - } - out.writeAll("};\n") catch unreachable; - return; - }, - else => {}, - } - switch (@typeInfo(T)) { - .Enum => |enum_info| { - out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}) catch unreachable; - inline for (enum_info.fields) |field| { - out.print(" {},\n", .{std.zig.fmtId(field.name)}) catch unreachable; - } - out.writeAll("};\n") catch unreachable; - }, - else => {}, - } - out.print("pub const {}: {s} = {};\n", .{ std.zig.fmtId(name), @typeName(T), value }) catch unreachable; - } - - /// The value is the path in the cache dir. - /// Adds a dependency automatically. - pub fn addBuildOptionArtifact(self: *LibExeObjStep, name: []const u8, artifact: *LibExeObjStep) void { - self.build_options_artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch unreachable; - self.step.dependOn(&artifact.step); - } - - /// The value is the path in the cache dir. - /// Adds a dependency automatically. - /// basename refers to the basename of the WriteFileStep - pub fn addBuildOptionFileSource( - self: *LibExeObjStep, - name: []const u8, - source: FileSource, - ) void { - self.build_options_file_source_args.append(.{ - .name = name, - .source = source.dupe(self.builder), - }) catch unreachable; - source.addStepDependencies(&self.step); - } - pub fn addSystemIncludeDir(self: *LibExeObjStep, path: []const u8) void { self.include_dirs.append(IncludeDir{ .raw_path_system = self.builder.dupe(path) }) catch unreachable; } @@ -2176,6 +2052,10 @@ pub const LibExeObjStep = struct { self.addRecursiveBuildDeps(package); } + pub fn addOptions(self: *LibExeObjStep, package_name: []const u8, options: *OptionsStep) void { + self.addPackage(options.getPackage(package_name)); + } + fn addRecursiveBuildDeps(self: *LibExeObjStep, package: Pkg) void { package.path.addStepDependencies(&self.step); if (package.dependencies) |deps| { @@ -2393,41 +2273,6 @@ pub const LibExeObjStep = struct { } } - if (self.build_options_contents.items.len > 0 or - self.build_options_artifact_args.items.len > 0 or - self.build_options_file_source_args.items.len > 0) - { - // Render build artifact and write file options at the last minute, now that the path is known. - // - // Note that pathFromRoot uses resolve path, so this will have - // correct behavior even if getOutputPath is already absolute. - for (self.build_options_artifact_args.items) |item| { - self.addBuildOption( - []const u8, - item.name, - self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)), - ); - } - for (self.build_options_file_source_args.items) |item| { - self.addBuildOption( - []const u8, - item.name, - item.source.getPath(self.builder), - ); - } - - const build_options_file = try fs.path.join( - builder.allocator, - &[_][]const u8{ builder.cache_root, builder.fmt("{s}_build_options.zig", .{self.name}) }, - ); - const path_from_root = builder.pathFromRoot(build_options_file); - try fs.cwd().writeFile(path_from_root, self.build_options_contents.items); - try zig_args.append("--pkg-begin"); - try zig_args.append("build_options"); - try zig_args.append(path_from_root); - try zig_args.append("--pkg-end"); - } - if (self.image_base) |image_base| { try zig_args.append("--image-base"); try zig_args.append(builder.fmt("0x{x}", .{image_base})); @@ -3141,6 +2986,7 @@ pub const Step = struct { run, check_file, install_raw, + options, custom, }; @@ -3312,43 +3158,6 @@ test "Builder.dupePkg()" { try std.testing.expect(dupe_deps[0].path.path.ptr != pkg_dep.path.path.ptr); } -test "LibExeObjStep.addBuildOption" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; - - var arena = std.heap.ArenaAllocator.init(std.testing.allocator); - defer arena.deinit(); - var builder = try Builder.create( - &arena.allocator, - "test", - "test", - "test", - "test", - ); - defer builder.destroy(); - - var exe = builder.addExecutable("not_an_executable", "/not/an/executable.zig"); - exe.addBuildOption(usize, "option1", 1); - exe.addBuildOption(?usize, "option2", null); - exe.addBuildOption([]const u8, "string", "zigisthebest"); - exe.addBuildOption(?[]const u8, "optional_string", null); - exe.addBuildOption(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 string: []const u8 = "zigisthebest"; - \\pub const optional_string: ?[]const u8 = null; - \\pub const semantic_version: @import("std").SemanticVersion = .{ - \\ .major = 0, - \\ .minor = 1, - \\ .patch = 2, - \\ .pre = "foo", - \\ .build = "bar", - \\}; - \\ - , exe.build_options_contents.items); -} - test "LibExeObjStep.addPackage" { if (builtin.os.tag == .wasi) return error.SkipZigTest; diff --git a/lib/std/build/OptionsStep.zig b/lib/std/build/OptionsStep.zig new file mode 100644 index 0000000000..2440fce16d --- /dev/null +++ b/lib/std/build/OptionsStep.zig @@ -0,0 +1,257 @@ +const std = @import("../std.zig"); +const build = std.build; +const fs = std.fs; +const Step = build.Step; +const Builder = build.Builder; +const GeneratedFile = build.GeneratedFile; +const LibExeObjStep = build.LibExeObjStep; +const FileSource = build.FileSource; + +const OptionsStep = @This(); + +step: Step, +generated_file: GeneratedFile, +builder: *Builder, + +contents: std.ArrayList(u8), +artifact_args: std.ArrayList(OptionArtifactArg), +file_source_args: std.ArrayList(OptionFileSourceArg), + +pub fn create(builder: *Builder) *OptionsStep { + const self = builder.allocator.create(OptionsStep) catch unreachable; + 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 { + const out = self.contents.writer(); + switch (T) { + []const []const u8 => { + out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}) catch unreachable; + for (value) |slice| { + out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}) catch unreachable; + } + out.writeAll("};\n") catch unreachable; + return; + }, + [:0]const u8 => { + out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; + return; + }, + []const u8 => { + out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }) catch unreachable; + return; + }, + ?[:0]const u8 => { + out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; + if (value) |payload| { + out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; + } else { + out.writeAll("null;\n") catch unreachable; + } + return; + }, + ?[]const u8 => { + out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}) catch unreachable; + if (value) |payload| { + out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}) catch unreachable; + } else { + out.writeAll("null;\n") catch unreachable; + } + return; + }, + std.builtin.Version => { + out.print( + \\pub const {}: @import("std").builtin.Version = .{{ + \\ .major = {d}, + \\ .minor = {d}, + \\ .patch = {d}, + \\}}; + \\ + , .{ + std.zig.fmtId(name), + + value.major, + value.minor, + value.patch, + }) catch unreachable; + }, + std.SemanticVersion => { + out.print( + \\pub const {}: @import("std").SemanticVersion = .{{ + \\ .major = {d}, + \\ .minor = {d}, + \\ .patch = {d}, + \\ + , .{ + std.zig.fmtId(name), + + value.major, + value.minor, + value.patch, + }) catch unreachable; + if (value.pre) |some| { + out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; + } + if (value.build) |some| { + out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}) catch unreachable; + } + out.writeAll("};\n") catch unreachable; + return; + }, + else => {}, + } + switch (@typeInfo(T)) { + .Enum => |enum_info| { + out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}) catch unreachable; + inline for (enum_info.fields) |field| { + out.print(" {},\n", .{std.zig.fmtId(field.name)}) catch unreachable; + } + out.writeAll("};\n") catch unreachable; + }, + else => {}, + } + out.print("pub const {}: {s} = {};\n", .{ std.zig.fmtId(name), @typeName(T), value }) catch unreachable; +} + +/// 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 unreachable; + 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: *LibExeObjStep) void { + self.artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch unreachable; + self.step.dependOn(&artifact.step); +} + +pub fn getPackage(self: OptionsStep, package_name: []const u8) build.Pkg { + return .{ .name = package_name, .path = 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: *LibExeObjStep, +}; + +const OptionFileSourceArg = struct { + name: []const u8, + source: FileSource, +}; + +test "OptionsStep" { + if (std.builtin.os.tag == .wasi) return error.SkipZigTest; + + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var builder = try Builder.create( + &arena.allocator, + "test", + "test", + "test", + "test", + ); + defer builder.destroy(); + + const options = builder.addOptions(); + + options.addOption(usize, "option1", 1); + options.addOption(?usize, "option2", null); + options.addOption([]const u8, "string", "zigisthebest"); + options.addOption(?[]const u8, "optional_string", null); + 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 string: []const u8 = "zigisthebest"; + \\pub const optional_string: ?[]const u8 = null; + \\pub const semantic_version: @import("std").SemanticVersion = .{ + \\ .major = 0, + \\ .minor = 1, + \\ .patch = 2, + \\ .pre = "foo", + \\ .build = "bar", + \\}; + \\ + , options.contents.items); +}