diff --git a/lib/std/Build.zig b/lib/std/Build.zig index a2c8a22e32bbdeb4360da4a53f383a74f7bd8781..bda50112b609c7f0535daf92376fd383ef79944f 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -29,20 +29,20 @@ pub const Builder = Build; pub const InstallDirectoryOptions = InstallDirStep.Options; pub const Step = @import("Build/Step.zig"); -pub const CheckFileStep = @import("Build/CheckFileStep.zig"); -pub const CheckObjectStep = @import("Build/CheckObjectStep.zig"); -pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig"); -pub const FmtStep = @import("Build/FmtStep.zig"); -pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig"); -pub const InstallDirStep = @import("Build/InstallDirStep.zig"); -pub const InstallFileStep = @import("Build/InstallFileStep.zig"); -pub const ObjCopyStep = @import("Build/ObjCopyStep.zig"); -pub const CompileStep = @import("Build/CompileStep.zig"); -pub const OptionsStep = @import("Build/OptionsStep.zig"); -pub const RemoveDirStep = @import("Build/RemoveDirStep.zig"); -pub const RunStep = @import("Build/RunStep.zig"); -pub const TranslateCStep = @import("Build/TranslateCStep.zig"); -pub const WriteFileStep = @import("Build/WriteFileStep.zig"); +pub const CheckFileStep = @import("Build/Step/CheckFile.zig"); +pub const CheckObjectStep = @import("Build/Step/CheckObject.zig"); +pub const ConfigHeaderStep = @import("Build/Step/ConfigHeader.zig"); +pub const FmtStep = @import("Build/Step/Fmt.zig"); +pub const InstallArtifactStep = @import("Build/Step/InstallArtifact.zig"); +pub const InstallDirStep = @import("Build/Step/InstallDir.zig"); +pub const InstallFileStep = @import("Build/Step/InstallFile.zig"); +pub const ObjCopyStep = @import("Build/Step/ObjCopy.zig"); +pub const CompileStep = @import("Build/Step/Compile.zig"); +pub const OptionsStep = @import("Build/Step/Options.zig"); +pub const RemoveDirStep = @import("Build/Step/RemoveDir.zig"); +pub const RunStep = @import("Build/Step/Run.zig"); +pub const TranslateCStep = @import("Build/Step/TranslateC.zig"); +pub const WriteFileStep = @import("Build/Step/WriteFile.zig"); install_tls: TopLevelStep, uninstall_tls: TopLevelStep, diff --git a/lib/std/Build/CheckFileStep.zig b/lib/std/Build/CheckFileStep.zig deleted file mode 100644 index 1c2b6b77867bd3520ce28ae4934f8926f4a8a2a0..0000000000000000000000000000000000000000 --- a/lib/std/Build/CheckFileStep.zig +++ /dev/null @@ -1,88 +0,0 @@ -//! Fail the build step if a file does not match certain checks. -//! TODO: make this more flexible, supporting more kinds of checks. -//! TODO: generalize the code in std.testing.expectEqualStrings and make this -//! CheckFileStep produce those helpful diagnostics when there is not a match. - -step: Step, -expected_matches: []const []const u8, -expected_exact: ?[]const u8, -source: std.Build.FileSource, -max_bytes: usize = 20 * 1024 * 1024, - -pub const base_id = .check_file; - -pub const Options = struct { - expected_matches: []const []const u8 = &.{}, - expected_exact: ?[]const u8 = null, -}; - -pub fn create( - owner: *std.Build, - source: std.Build.FileSource, - options: Options, -) *CheckFileStep { - const self = owner.allocator.create(CheckFileStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = .check_file, - .name = "CheckFile", - .owner = owner, - .makeFn = make, - }), - .source = source.dupe(owner), - .expected_matches = owner.dupeStrings(options.expected_matches), - .expected_exact = options.expected_exact, - }; - self.source.addStepDependencies(&self.step); - return self; -} - -pub fn setName(self: *CheckFileStep, name: []const u8) void { - self.step.name = name; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const b = step.owner; - const self = @fieldParentPtr(CheckFileStep, "step", step); - - const src_path = self.source.getPath(b); - const contents = fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes) catch |err| { - return step.fail("unable to read '{s}': {s}", .{ - src_path, @errorName(err), - }); - }; - - for (self.expected_matches) |expected_match| { - if (mem.indexOf(u8, contents, expected_match) == null) { - return step.fail( - \\ - \\========= expected to find: =================== - \\{s} - \\========= but file does not contain it: ======= - \\{s} - \\=============================================== - , .{ expected_match, contents }); - } - } - - if (self.expected_exact) |expected_exact| { - if (!mem.eql(u8, expected_exact, contents)) { - return step.fail( - \\ - \\========= expected: ===================== - \\{s} - \\========= but found: ==================== - \\{s} - \\========= from the following file: ====== - \\{s} - , .{ expected_exact, contents, src_path }); - } - } -} - -const CheckFileStep = @This(); -const std = @import("../std.zig"); -const Step = std.Build.Step; -const fs = std.fs; -const mem = std.mem; diff --git a/lib/std/Build/CheckObjectStep.zig b/lib/std/Build/CheckObjectStep.zig deleted file mode 100644 index e79ce9d3df19df820470ecf5118ef8bb1c79b6f5..0000000000000000000000000000000000000000 --- a/lib/std/Build/CheckObjectStep.zig +++ /dev/null @@ -1,1055 +0,0 @@ -const std = @import("../std.zig"); -const assert = std.debug.assert; -const fs = std.fs; -const macho = std.macho; -const math = std.math; -const mem = std.mem; -const testing = std.testing; - -const CheckObjectStep = @This(); - -const Allocator = mem.Allocator; -const Step = std.Build.Step; - -pub const base_id = .check_object; - -step: Step, -source: std.Build.FileSource, -max_bytes: usize = 20 * 1024 * 1024, -checks: std.ArrayList(Check), -dump_symtab: bool = false, -obj_format: std.Target.ObjectFormat, - -pub fn create( - owner: *std.Build, - source: std.Build.FileSource, - obj_format: std.Target.ObjectFormat, -) *CheckObjectStep { - const gpa = owner.allocator; - const self = gpa.create(CheckObjectStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = .check_file, - .name = "CheckObject", - .owner = owner, - .makeFn = make, - }), - .source = source.dupe(owner), - .checks = std.ArrayList(Check).init(gpa), - .obj_format = obj_format, - }; - self.source.addStepDependencies(&self.step); - return self; -} - -/// Runs and (optionally) compares the output of a binary. -/// Asserts `self` was generated from an executable step. -/// TODO this doesn't actually compare, and there's no apparent reason for it -/// to depend on the check object step. I don't see why this function should exist, -/// the caller could just add the run step directly. -pub fn runAndCompare(self: *CheckObjectStep) *std.Build.RunStep { - const dependencies_len = self.step.dependencies.items.len; - assert(dependencies_len > 0); - const exe_step = self.step.dependencies.items[dependencies_len - 1]; - const exe = exe_step.cast(std.Build.CompileStep).?; - const run = self.step.owner.addRunArtifact(exe); - run.skip_foreign_checks = true; - run.step.dependOn(&self.step); - return run; -} - -const SearchPhrase = struct { - string: []const u8, - file_source: ?std.Build.FileSource = null, - - fn resolve(phrase: SearchPhrase, b: *std.Build, step: *Step) []const u8 { - const file_source = phrase.file_source orelse return phrase.string; - return b.fmt("{s} {s}", .{ phrase.string, file_source.getPath2(b, step) }); - } -}; - -/// There two types of actions currently supported: -/// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}` -/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature -/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use -/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. -/// it should be plenty useful in its current form. -/// * `.compute_cmp` - can be used to perform an operation on the extracted global variables -/// using the MatchAction. It currently only supports an addition. The operation is required -/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, -/// to avoid any parsing really). -/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively -/// they could then be added with this simple program `vmaddr entryoff +`. -const Action = struct { - tag: enum { match, not_present, compute_cmp }, - phrase: SearchPhrase, - expected: ?ComputeCompareExpected = null, - - /// Will return true if the `phrase` was found in the `haystack`. - /// Some examples include: - /// - /// LC 0 => will match in its entirety - /// vmaddr {vmaddr} => will match `vmaddr` and then extract the following value as u64 - /// and save under `vmaddr` global name (see `global_vars` param) - /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` - /// in that order with other letters in between - fn match( - act: Action, - b: *std.Build, - step: *Step, - haystack: []const u8, - global_vars: anytype, - ) !bool { - assert(act.tag == .match or act.tag == .not_present); - const phrase = act.phrase.resolve(b, step); - var candidate_var: ?struct { name: []const u8, value: u64 } = null; - var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); - var needle_it = mem.tokenize(u8, mem.trim(u8, phrase, " "), " "); - - while (needle_it.next()) |needle_tok| { - const hay_tok = hay_it.next() orelse return false; - - if (mem.indexOf(u8, needle_tok, "{*}")) |index| { - // We have fuzzy matchers within the search pattern, so we match substrings. - var start = index; - var n_tok = needle_tok; - var h_tok = hay_tok; - while (true) { - n_tok = n_tok[start + 3 ..]; - const inner = if (mem.indexOf(u8, n_tok, "{*}")) |sub_end| - n_tok[0..sub_end] - else - n_tok; - if (mem.indexOf(u8, h_tok, inner) == null) return false; - start = mem.indexOf(u8, n_tok, "{*}") orelse break; - } - } else if (mem.startsWith(u8, needle_tok, "{")) { - const closing_brace = mem.indexOf(u8, needle_tok, "}") orelse return error.MissingClosingBrace; - if (closing_brace != needle_tok.len - 1) return error.ClosingBraceNotLast; - - const name = needle_tok[1..closing_brace]; - if (name.len == 0) return error.MissingBraceValue; - const value = try std.fmt.parseInt(u64, hay_tok, 16); - candidate_var = .{ - .name = name, - .value = value, - }; - } else { - if (!mem.eql(u8, hay_tok, needle_tok)) return false; - } - } - - if (candidate_var) |v| { - try global_vars.putNoClobber(v.name, v.value); - } - - return true; - } - - /// Will return true if the `phrase` is correctly parsed into an RPN program and - /// its reduced, computed value compares using `op` with the expected value, either - /// a literal or another extracted variable. - fn computeCmp(act: Action, b: *std.Build, step: *Step, global_vars: anytype) !bool { - const gpa = step.owner.allocator; - const phrase = act.phrase.resolve(b, step); - var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa); - var values = std.ArrayList(u64).init(gpa); - - var it = mem.tokenize(u8, phrase, " "); - while (it.next()) |next| { - if (mem.eql(u8, next, "+")) { - try op_stack.append(.add); - } else if (mem.eql(u8, next, "-")) { - try op_stack.append(.sub); - } else if (mem.eql(u8, next, "%")) { - try op_stack.append(.mod); - } else if (mem.eql(u8, next, "*")) { - try op_stack.append(.mul); - } else { - const val = std.fmt.parseInt(u64, next, 0) catch blk: { - break :blk global_vars.get(next) orelse { - try step.addError( - \\ - \\========= variable was not extracted: =========== - \\{s} - \\================================================= - , .{next}); - return error.UnknownVariable; - }; - }; - try values.append(val); - } - } - - var op_i: usize = 1; - var reduced: u64 = values.items[0]; - for (op_stack.items) |op| { - const other = values.items[op_i]; - switch (op) { - .add => { - reduced += other; - }, - .sub => { - reduced -= other; - }, - .mod => { - reduced %= other; - }, - .mul => { - reduced *= other; - }, - } - op_i += 1; - } - - const exp_value = switch (act.expected.?.value) { - .variable => |name| global_vars.get(name) orelse { - try step.addError( - \\ - \\========= variable was not extracted: =========== - \\{s} - \\================================================= - , .{name}); - return error.UnknownVariable; - }, - .literal => |x| x, - }; - return math.compare(reduced, act.expected.?.op, exp_value); - } -}; - -const ComputeCompareExpected = struct { - op: math.CompareOperator, - value: union(enum) { - variable: []const u8, - literal: u64, - }, - - pub fn format( - value: @This(), - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - if (fmt.len != 0) std.fmt.invalidFmtError(fmt, value); - _ = options; - try writer.print("{s} ", .{@tagName(value.op)}); - switch (value.value) { - .variable => |name| try writer.writeAll(name), - .literal => |x| try writer.print("{x}", .{x}), - } - } -}; - -const Check = struct { - actions: std.ArrayList(Action), - - fn create(allocator: Allocator) Check { - return .{ - .actions = std.ArrayList(Action).init(allocator), - }; - } - - fn match(self: *Check, phrase: SearchPhrase) void { - self.actions.append(.{ - .tag = .match, - .phrase = phrase, - }) catch @panic("OOM"); - } - - fn notPresent(self: *Check, phrase: SearchPhrase) void { - self.actions.append(.{ - .tag = .not_present, - .phrase = phrase, - }) catch @panic("OOM"); - } - - fn computeCmp(self: *Check, phrase: SearchPhrase, expected: ComputeCompareExpected) void { - self.actions.append(.{ - .tag = .compute_cmp, - .phrase = phrase, - .expected = expected, - }) catch @panic("OOM"); - } -}; - -/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. -pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void { - var new_check = Check.create(self.step.owner.allocator); - new_check.match(.{ .string = self.step.owner.dupe(phrase) }); - self.checks.append(new_check) catch @panic("OOM"); -} - -/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`. -/// Asserts at least one check already exists. -pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { - assert(self.checks.items.len > 0); - const last = &self.checks.items[self.checks.items.len - 1]; - last.match(.{ .string = self.step.owner.dupe(phrase) }); -} - -/// Like `checkNext()` but takes an additional argument `FileSource` which will be -/// resolved to a full search query in `make()`. -pub fn checkNextFileSource( - self: *CheckObjectStep, - phrase: []const u8, - file_source: std.Build.FileSource, -) void { - assert(self.checks.items.len > 0); - const last = &self.checks.items[self.checks.items.len - 1]; - last.match(.{ .string = self.step.owner.dupe(phrase), .file_source = file_source }); -} - -/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)` -/// however ensures there is no matching phrase in the output. -/// Asserts at least one check already exists. -pub fn checkNotPresent(self: *CheckObjectStep, phrase: []const u8) void { - assert(self.checks.items.len > 0); - const last = &self.checks.items[self.checks.items.len - 1]; - last.notPresent(.{ .string = self.step.owner.dupe(phrase) }); -} - -/// Creates a new check checking specifically symbol table parsed and dumped from the object -/// file. -/// Issuing this check will force parsing and dumping of the symbol table. -pub fn checkInSymtab(self: *CheckObjectStep) void { - self.dump_symtab = true; - const symtab_label = switch (self.obj_format) { - .macho => MachODumper.symtab_label, - else => @panic("TODO other parsers"), - }; - self.checkStart(symtab_label); -} - -/// Creates a new standalone, singular check which allows running simple binary operations -/// on the extracted variables. It will then compare the reduced program with the value of -/// the expected variable. -pub fn checkComputeCompare( - self: *CheckObjectStep, - program: []const u8, - expected: ComputeCompareExpected, -) void { - var new_check = Check.create(self.step.owner.allocator); - new_check.computeCmp(.{ .string = self.step.owner.dupe(program) }, expected); - self.checks.append(new_check) catch @panic("OOM"); -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const b = step.owner; - const gpa = b.allocator; - const self = @fieldParentPtr(CheckObjectStep, "step", step); - - const src_path = self.source.getPath(b); - const contents = fs.cwd().readFileAllocOptions( - gpa, - src_path, - self.max_bytes, - null, - @alignOf(u64), - null, - ) catch |err| return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err) }); - - const output = switch (self.obj_format) { - .macho => try MachODumper.parseAndDump(step, contents, .{ - .dump_symtab = self.dump_symtab, - }), - .elf => @panic("TODO elf parser"), - .coff => @panic("TODO coff parser"), - .wasm => try WasmDumper.parseAndDump(step, contents, .{ - .dump_symtab = self.dump_symtab, - }), - else => unreachable, - }; - - var vars = std.StringHashMap(u64).init(gpa); - - for (self.checks.items) |chk| { - var it = mem.tokenize(u8, output, "\r\n"); - for (chk.actions.items) |act| { - switch (act.tag) { - .match => { - while (it.next()) |line| { - if (try act.match(b, step, line, &vars)) break; - } else { - return step.fail( - \\ - \\========= expected to find: ========================== - \\{s} - \\========= but parsed file does not contain it: ======= - \\{s} - \\====================================================== - , .{ act.phrase.resolve(b, step), output }); - } - }, - .not_present => { - while (it.next()) |line| { - if (try act.match(b, step, line, &vars)) { - return step.fail( - \\ - \\========= expected not to find: =================== - \\{s} - \\========= but parsed file does contain it: ======== - \\{s} - \\=================================================== - , .{ act.phrase.resolve(b, step), output }); - } - } - }, - .compute_cmp => { - const res = act.computeCmp(b, step, vars) catch |err| switch (err) { - error.UnknownVariable => { - return step.fail( - \\========= from parsed file: ===================== - \\{s} - \\================================================= - , .{output}); - }, - else => |e| return e, - }; - if (!res) { - return step.fail( - \\ - \\========= comparison failed for action: =========== - \\{s} {} - \\========= from parsed file: ======================= - \\{s} - \\=================================================== - , .{ act.phrase.resolve(b, step), act.expected.?, output }); - } - }, - } - } - } -} - -const Opts = struct { - dump_symtab: bool = false, -}; - -const MachODumper = struct { - const LoadCommandIterator = macho.LoadCommandIterator; - const symtab_label = "symtab"; - - fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { - const gpa = step.owner.allocator; - var stream = std.io.fixedBufferStream(bytes); - const reader = stream.reader(); - - const hdr = try reader.readStruct(macho.mach_header_64); - if (hdr.magic != macho.MH_MAGIC_64) { - return error.InvalidMagicNumber; - } - - var output = std.ArrayList(u8).init(gpa); - const writer = output.writer(); - - var symtab: []const macho.nlist_64 = undefined; - var strtab: []const u8 = undefined; - var sections = std.ArrayList(macho.section_64).init(gpa); - var imports = std.ArrayList([]const u8).init(gpa); - - var it = LoadCommandIterator{ - .ncmds = hdr.ncmds, - .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], - }; - var i: usize = 0; - while (it.next()) |cmd| { - switch (cmd.cmd()) { - .SEGMENT_64 => { - const seg = cmd.cast(macho.segment_command_64).?; - try sections.ensureUnusedCapacity(seg.nsects); - for (cmd.getSections()) |sect| { - sections.appendAssumeCapacity(sect); - } - }, - .SYMTAB => if (opts.dump_symtab) { - const lc = cmd.cast(macho.symtab_command).?; - symtab = @ptrCast( - [*]const macho.nlist_64, - @alignCast(@alignOf(macho.nlist_64), &bytes[lc.symoff]), - )[0..lc.nsyms]; - strtab = bytes[lc.stroff..][0..lc.strsize]; - }, - .LOAD_DYLIB, - .LOAD_WEAK_DYLIB, - .REEXPORT_DYLIB, - => { - try imports.append(cmd.getDylibPathName()); - }, - else => {}, - } - - try dumpLoadCommand(cmd, i, writer); - try writer.writeByte('\n'); - - i += 1; - } - - if (opts.dump_symtab) { - try writer.print("{s}\n", .{symtab_label}); - for (symtab) |sym| { - if (sym.stab()) continue; - const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); - if (sym.sect()) { - const sect = sections.items[sym.n_sect - 1]; - try writer.print("{x} ({s},{s})", .{ - sym.n_value, - sect.segName(), - sect.sectName(), - }); - if (sym.ext()) { - try writer.writeAll(" external"); - } - try writer.print(" {s}\n", .{sym_name}); - } else if (sym.undf()) { - const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); - const import_name = blk: { - if (ordinal <= 0) { - if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF) - break :blk "self import"; - if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE) - break :blk "main executable"; - if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP) - break :blk "flat lookup"; - unreachable; - } - const full_path = imports.items[@bitCast(u16, ordinal) - 1]; - const basename = fs.path.basename(full_path); - assert(basename.len > 0); - const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len; - break :blk basename[0..ext]; - }; - try writer.writeAll("(undefined)"); - if (sym.weakRef()) { - try writer.writeAll(" weak"); - } - if (sym.ext()) { - try writer.writeAll(" external"); - } - try writer.print(" {s} (from {s})\n", .{ - sym_name, - import_name, - }); - } else unreachable; - } - } - - return output.toOwnedSlice(); - } - - fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void { - // print header first - try writer.print( - \\LC {d} - \\cmd {s} - \\cmdsize {d} - , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); - - switch (lc.cmd()) { - .SEGMENT_64 => { - const seg = lc.cast(macho.segment_command_64).?; - try writer.writeByte('\n'); - try writer.print( - \\segname {s} - \\vmaddr {x} - \\vmsize {x} - \\fileoff {x} - \\filesz {x} - , .{ - seg.segName(), - seg.vmaddr, - seg.vmsize, - seg.fileoff, - seg.filesize, - }); - - for (lc.getSections()) |sect| { - try writer.writeByte('\n'); - try writer.print( - \\sectname {s} - \\addr {x} - \\size {x} - \\offset {x} - \\align {x} - , .{ - sect.sectName(), - sect.addr, - sect.size, - sect.offset, - sect.@"align", - }); - } - }, - - .ID_DYLIB, - .LOAD_DYLIB, - .LOAD_WEAK_DYLIB, - .REEXPORT_DYLIB, - => { - const dylib = lc.cast(macho.dylib_command).?; - try writer.writeByte('\n'); - try writer.print( - \\name {s} - \\timestamp {d} - \\current version {x} - \\compatibility version {x} - , .{ - lc.getDylibPathName(), - dylib.dylib.timestamp, - dylib.dylib.current_version, - dylib.dylib.compatibility_version, - }); - }, - - .MAIN => { - const main = lc.cast(macho.entry_point_command).?; - try writer.writeByte('\n'); - try writer.print( - \\entryoff {x} - \\stacksize {x} - , .{ main.entryoff, main.stacksize }); - }, - - .RPATH => { - try writer.writeByte('\n'); - try writer.print( - \\path {s} - , .{ - lc.getRpathPathName(), - }); - }, - - .UUID => { - const uuid = lc.cast(macho.uuid_command).?; - try writer.writeByte('\n'); - try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)}); - }, - - .DATA_IN_CODE, - .FUNCTION_STARTS, - .CODE_SIGNATURE, - => { - const llc = lc.cast(macho.linkedit_data_command).?; - try writer.writeByte('\n'); - try writer.print( - \\dataoff {x} - \\datasize {x} - , .{ llc.dataoff, llc.datasize }); - }, - - .DYLD_INFO_ONLY => { - const dlc = lc.cast(macho.dyld_info_command).?; - try writer.writeByte('\n'); - try writer.print( - \\rebaseoff {x} - \\rebasesize {x} - \\bindoff {x} - \\bindsize {x} - \\weakbindoff {x} - \\weakbindsize {x} - \\lazybindoff {x} - \\lazybindsize {x} - \\exportoff {x} - \\exportsize {x} - , .{ - dlc.rebase_off, - dlc.rebase_size, - dlc.bind_off, - dlc.bind_size, - dlc.weak_bind_off, - dlc.weak_bind_size, - dlc.lazy_bind_off, - dlc.lazy_bind_size, - dlc.export_off, - dlc.export_size, - }); - }, - - .SYMTAB => { - const slc = lc.cast(macho.symtab_command).?; - try writer.writeByte('\n'); - try writer.print( - \\symoff {x} - \\nsyms {x} - \\stroff {x} - \\strsize {x} - , .{ - slc.symoff, - slc.nsyms, - slc.stroff, - slc.strsize, - }); - }, - - .DYSYMTAB => { - const dlc = lc.cast(macho.dysymtab_command).?; - try writer.writeByte('\n'); - try writer.print( - \\ilocalsym {x} - \\nlocalsym {x} - \\iextdefsym {x} - \\nextdefsym {x} - \\iundefsym {x} - \\nundefsym {x} - \\indirectsymoff {x} - \\nindirectsyms {x} - , .{ - dlc.ilocalsym, - dlc.nlocalsym, - dlc.iextdefsym, - dlc.nextdefsym, - dlc.iundefsym, - dlc.nundefsym, - dlc.indirectsymoff, - dlc.nindirectsyms, - }); - }, - - else => {}, - } - } -}; - -const WasmDumper = struct { - const symtab_label = "symbols"; - - fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 { - const gpa = step.owner.allocator; - if (opts.dump_symtab) { - @panic("TODO: Implement symbol table parsing and dumping"); - } - - var fbs = std.io.fixedBufferStream(bytes); - const reader = fbs.reader(); - - const buf = try reader.readBytesNoEof(8); - if (!mem.eql(u8, buf[0..4], &std.wasm.magic)) { - return error.InvalidMagicByte; - } - if (!mem.eql(u8, buf[4..], &std.wasm.version)) { - return error.UnsupportedWasmVersion; - } - - var output = std.ArrayList(u8).init(gpa); - errdefer output.deinit(); - const writer = output.writer(); - - while (reader.readByte()) |current_byte| { - const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch { - return step.fail("Found invalid section id '{d}'", .{current_byte}); - }; - - const section_length = try std.leb.readULEB128(u32, reader); - try parseAndDumpSection(step, section, bytes[fbs.pos..][0..section_length], writer); - fbs.pos += section_length; - } else |_| {} // reached end of stream - - return output.toOwnedSlice(); - } - - fn parseAndDumpSection( - step: *Step, - section: std.wasm.Section, - data: []const u8, - writer: anytype, - ) !void { - var fbs = std.io.fixedBufferStream(data); - const reader = fbs.reader(); - - try writer.print( - \\Section {s} - \\size {d} - , .{ @tagName(section), data.len }); - - switch (section) { - .type, - .import, - .function, - .table, - .memory, - .global, - .@"export", - .element, - .code, - .data, - => { - const entries = try std.leb.readULEB128(u32, reader); - try writer.print("\nentries {d}\n", .{entries}); - try dumpSection(step, section, data[fbs.pos..], entries, writer); - }, - .custom => { - const name_length = try std.leb.readULEB128(u32, reader); - const name = data[fbs.pos..][0..name_length]; - fbs.pos += name_length; - try writer.print("\nname {s}\n", .{name}); - - if (mem.eql(u8, name, "name")) { - try parseDumpNames(step, reader, writer, data); - } else if (mem.eql(u8, name, "producers")) { - try parseDumpProducers(reader, writer, data); - } else if (mem.eql(u8, name, "target_features")) { - try parseDumpFeatures(reader, writer, data); - } - // TODO: Implement parsing and dumping other custom sections (such as relocations) - }, - .start => { - const start = try std.leb.readULEB128(u32, reader); - try writer.print("\nstart {d}\n", .{start}); - }, - else => {}, // skip unknown sections - } - } - - fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { - var fbs = std.io.fixedBufferStream(data); - const reader = fbs.reader(); - - switch (section) { - .type => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - const func_type = try reader.readByte(); - if (func_type != std.wasm.function_type) { - return step.fail("expected function type, found byte '{d}'", .{func_type}); - } - const params = try std.leb.readULEB128(u32, reader); - try writer.print("params {d}\n", .{params}); - var index: u32 = 0; - while (index < params) : (index += 1) { - try parseDumpType(step, std.wasm.Valtype, reader, writer); - } else index = 0; - const returns = try std.leb.readULEB128(u32, reader); - try writer.print("returns {d}\n", .{returns}); - while (index < returns) : (index += 1) { - try parseDumpType(step, std.wasm.Valtype, reader, writer); - } - } - }, - .import => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - const module_name_len = try std.leb.readULEB128(u32, reader); - const module_name = data[fbs.pos..][0..module_name_len]; - fbs.pos += module_name_len; - const name_len = try std.leb.readULEB128(u32, reader); - const name = data[fbs.pos..][0..name_len]; - fbs.pos += name_len; - - const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch { - return step.fail("invalid import kind", .{}); - }; - - try writer.print( - \\module {s} - \\name {s} - \\kind {s} - , .{ module_name, name, @tagName(kind) }); - try writer.writeByte('\n'); - switch (kind) { - .function => { - try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - }, - .memory => { - try parseDumpLimits(reader, writer); - }, - .global => { - try parseDumpType(step, std.wasm.Valtype, reader, writer); - try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)}); - }, - .table => { - try parseDumpType(step, std.wasm.RefType, reader, writer); - try parseDumpLimits(reader, writer); - }, - } - } - }, - .function => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - } - }, - .table => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try parseDumpType(step, std.wasm.RefType, reader, writer); - try parseDumpLimits(reader, writer); - } - }, - .memory => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try parseDumpLimits(reader, writer); - } - }, - .global => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try parseDumpType(step, std.wasm.Valtype, reader, writer); - try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)}); - try parseDumpInit(step, reader, writer); - } - }, - .@"export" => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - const name_len = try std.leb.readULEB128(u32, reader); - const name = data[fbs.pos..][0..name_len]; - fbs.pos += name_len; - const kind_byte = try std.leb.readULEB128(u8, reader); - const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch { - return step.fail("invalid export kind value '{d}'", .{kind_byte}); - }; - const index = try std.leb.readULEB128(u32, reader); - try writer.print( - \\name {s} - \\kind {s} - \\index {d} - , .{ name, @tagName(kind), index }); - try writer.writeByte('\n'); - } - }, - .element => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - try parseDumpInit(step, reader, writer); - - const function_indexes = try std.leb.readULEB128(u32, reader); - var function_index: u32 = 0; - try writer.print("indexes {d}\n", .{function_indexes}); - while (function_index < function_indexes) : (function_index += 1) { - try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); - } - } - }, - .code => {}, // code section is considered opaque to linker - .data => { - var i: u32 = 0; - while (i < entries) : (i += 1) { - const index = try std.leb.readULEB128(u32, reader); - try writer.print("memory index 0x{x}\n", .{index}); - try parseDumpInit(step, reader, writer); - const size = try std.leb.readULEB128(u32, reader); - try writer.print("size {d}\n", .{size}); - try reader.skipBytes(size, .{}); // we do not care about the content of the segments - } - }, - else => unreachable, - } - } - - fn parseDumpType(step: *Step, comptime WasmType: type, reader: anytype, writer: anytype) !void { - const type_byte = try reader.readByte(); - const valtype = std.meta.intToEnum(WasmType, type_byte) catch { - return step.fail("Invalid wasm type value '{d}'", .{type_byte}); - }; - try writer.print("type {s}\n", .{@tagName(valtype)}); - } - - fn parseDumpLimits(reader: anytype, writer: anytype) !void { - const flags = try std.leb.readULEB128(u8, reader); - const min = try std.leb.readULEB128(u32, reader); - - try writer.print("min {x}\n", .{min}); - if (flags != 0) { - try writer.print("max {x}\n", .{try std.leb.readULEB128(u32, reader)}); - } - } - - fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void { - const byte = try std.leb.readULEB128(u8, reader); - const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch { - return step.fail("invalid wasm opcode '{d}'", .{byte}); - }; - switch (opcode) { - .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}), - .i64_const => try writer.print("i64.const {x}\n", .{try std.leb.readILEB128(i64, reader)}), - .f32_const => try writer.print("f32.const {x}\n", .{@bitCast(f32, try reader.readIntLittle(u32))}), - .f64_const => try writer.print("f64.const {x}\n", .{@bitCast(f64, try reader.readIntLittle(u64))}), - .global_get => try writer.print("global.get {x}\n", .{try std.leb.readULEB128(u32, reader)}), - else => unreachable, - } - const end_opcode = try std.leb.readULEB128(u8, reader); - if (end_opcode != std.wasm.opcode(.end)) { - return step.fail("expected 'end' opcode in init expression", .{}); - } - } - - fn parseDumpNames(step: *Step, reader: anytype, writer: anytype, data: []const u8) !void { - while (reader.context.pos < data.len) { - try parseDumpType(step, std.wasm.NameSubsection, reader, writer); - const size = try std.leb.readULEB128(u32, reader); - const entries = try std.leb.readULEB128(u32, reader); - try writer.print( - \\size {d} - \\names {d} - , .{ size, entries }); - try writer.writeByte('\n'); - var i: u32 = 0; - while (i < entries) : (i += 1) { - const index = try std.leb.readULEB128(u32, reader); - const name_len = try std.leb.readULEB128(u32, reader); - const pos = reader.context.pos; - const name = data[pos..][0..name_len]; - reader.context.pos += name_len; - - try writer.print( - \\index {d} - \\name {s} - , .{ index, name }); - try writer.writeByte('\n'); - } - } - } - - fn parseDumpProducers(reader: anytype, writer: anytype, data: []const u8) !void { - const field_count = try std.leb.readULEB128(u32, reader); - try writer.print("fields {d}\n", .{field_count}); - var current_field: u32 = 0; - while (current_field < field_count) : (current_field += 1) { - const field_name_length = try std.leb.readULEB128(u32, reader); - const field_name = data[reader.context.pos..][0..field_name_length]; - reader.context.pos += field_name_length; - - const value_count = try std.leb.readULEB128(u32, reader); - try writer.print( - \\field_name {s} - \\values {d} - , .{ field_name, value_count }); - try writer.writeByte('\n'); - var current_value: u32 = 0; - while (current_value < value_count) : (current_value += 1) { - const value_length = try std.leb.readULEB128(u32, reader); - const value = data[reader.context.pos..][0..value_length]; - reader.context.pos += value_length; - - const version_length = try std.leb.readULEB128(u32, reader); - const version = data[reader.context.pos..][0..version_length]; - reader.context.pos += version_length; - - try writer.print( - \\value_name {s} - \\version {s} - , .{ value, version }); - try writer.writeByte('\n'); - } - } - } - - fn parseDumpFeatures(reader: anytype, writer: anytype, data: []const u8) !void { - const feature_count = try std.leb.readULEB128(u32, reader); - try writer.print("features {d}\n", .{feature_count}); - - var index: u32 = 0; - while (index < feature_count) : (index += 1) { - const prefix_byte = try std.leb.readULEB128(u8, reader); - const name_length = try std.leb.readULEB128(u32, reader); - const feature_name = data[reader.context.pos..][0..name_length]; - reader.context.pos += name_length; - - try writer.print("{c} {s}\n", .{ prefix_byte, feature_name }); - } - } -}; diff --git a/lib/std/Build/CompileStep.zig b/lib/std/Build/CompileStep.zig deleted file mode 100644 index d5a135e24b7883a083a7d1b334236c2ba9bcbc38..0000000000000000000000000000000000000000 --- a/lib/std/Build/CompileStep.zig +++ /dev/null @@ -1,2183 +0,0 @@ -const builtin = @import("builtin"); -const std = @import("../std.zig"); -const mem = std.mem; -const fs = std.fs; -const assert = std.debug.assert; -const panic = std.debug.panic; -const ArrayList = std.ArrayList; -const StringHashMap = std.StringHashMap; -const Sha256 = std.crypto.hash.sha2.Sha256; -const Allocator = mem.Allocator; -const Step = std.Build.Step; -const CrossTarget = std.zig.CrossTarget; -const NativeTargetInfo = std.zig.system.NativeTargetInfo; -const FileSource = std.Build.FileSource; -const PkgConfigPkg = std.Build.PkgConfigPkg; -const PkgConfigError = std.Build.PkgConfigError; -const ExecError = std.Build.ExecError; -const Module = std.Build.Module; -const VcpkgRoot = std.Build.VcpkgRoot; -const InstallDir = std.Build.InstallDir; -const InstallArtifactStep = std.Build.InstallArtifactStep; -const GeneratedFile = std.Build.GeneratedFile; -const ObjCopyStep = std.Build.ObjCopyStep; -const CheckObjectStep = std.Build.CheckObjectStep; -const RunStep = std.Build.RunStep; -const OptionsStep = std.Build.OptionsStep; -const ConfigHeaderStep = std.Build.ConfigHeaderStep; -const CompileStep = @This(); - -pub const base_id: Step.Id = .compile; - -step: Step, -name: []const u8, -target: CrossTarget, -target_info: NativeTargetInfo, -optimize: std.builtin.Mode, -linker_script: ?FileSource = null, -version_script: ?[]const u8 = null, -out_filename: []const u8, -linkage: ?Linkage = null, -version: ?std.builtin.Version, -kind: Kind, -major_only_filename: ?[]const u8, -name_only_filename: ?[]const u8, -strip: ?bool, -unwind_tables: ?bool, -// keep in sync with src/link.zig:CompressDebugSections -compress_debug_sections: enum { none, zlib } = .none, -lib_paths: ArrayList(FileSource), -rpaths: ArrayList(FileSource), -framework_dirs: ArrayList(FileSource), -frameworks: StringHashMap(FrameworkLinkInfo), -verbose_link: bool, -verbose_cc: bool, -emit_analysis: EmitOption = .default, -emit_asm: EmitOption = .default, -emit_bin: EmitOption = .default, -emit_docs: EmitOption = .default, -emit_implib: EmitOption = .default, -emit_llvm_bc: EmitOption = .default, -emit_llvm_ir: EmitOption = .default, -// Lots of things depend on emit_h having a consistent path, -// so it is not an EmitOption for now. -emit_h: bool = false, -bundle_compiler_rt: ?bool = null, -single_threaded: ?bool, -stack_protector: ?bool = null, -disable_stack_probing: bool, -disable_sanitize_c: bool, -sanitize_thread: bool, -rdynamic: bool, -dwarf_format: ?std.dwarf.Format = null, -import_memory: bool = false, -/// For WebAssembly targets, this will allow for undefined symbols to -/// be imported from the host environment. -import_symbols: bool = false, -import_table: bool = false, -export_table: bool = false, -initial_memory: ?u64 = null, -max_memory: ?u64 = null, -shared_memory: bool = false, -global_base: ?u64 = null, -c_std: std.Build.CStd, -zig_lib_dir: ?[]const u8, -main_pkg_path: ?[]const u8, -exec_cmd_args: ?[]const ?[]const u8, -filter: ?[]const u8, -test_evented_io: bool = false, -test_runner: ?[]const u8, -code_model: std.builtin.CodeModel = .default, -wasi_exec_model: ?std.builtin.WasiExecModel = null, -/// Symbols to be exported when compiling to wasm -export_symbol_names: []const []const u8 = &.{}, - -root_src: ?FileSource, -out_h_filename: []const u8, -out_lib_filename: []const u8, -out_pdb_filename: []const u8, -modules: std.StringArrayHashMap(*Module), - -link_objects: ArrayList(LinkObject), -include_dirs: ArrayList(IncludeDir), -c_macros: ArrayList([]const u8), -installed_headers: ArrayList(*Step), -is_linking_libc: bool, -is_linking_libcpp: bool, -vcpkg_bin_path: ?[]const u8 = null, - -/// This may be set in order to override the default install directory -override_dest_dir: ?InstallDir, -installed_path: ?[]const u8, - -/// Base address for an executable image. -image_base: ?u64 = null, - -libc_file: ?FileSource = null, - -valgrind_support: ?bool = null, -each_lib_rpath: ?bool = null, -/// On ELF targets, this will emit a link section called ".note.gnu.build-id" -/// which can be used to coordinate a stripped binary with its debug symbols. -/// As an example, the bloaty project refuses to work unless its inputs have -/// build ids, in order to prevent accidental mismatches. -/// The default is to not include this section because it slows down linking. -build_id: ?bool = null, - -/// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF -/// file. -link_eh_frame_hdr: bool = false, -link_emit_relocs: bool = false, - -/// Place every function in its own section so that unused ones may be -/// safely garbage-collected during the linking phase. -link_function_sections: bool = false, - -/// Remove functions and data that are unreachable by the entry point or -/// exported symbols. -link_gc_sections: ?bool = null, - -/// (Windows) Whether or not to enable ASLR. Maps to the /DYNAMICBASE[:NO] linker argument. -linker_dynamicbase: bool = true, - -linker_allow_shlib_undefined: ?bool = null, - -/// Permit read-only relocations in read-only segments. Disallowed by default. -link_z_notext: bool = false, - -/// Force all relocations to be read-only after processing. -link_z_relro: bool = true, - -/// Allow relocations to be lazily processed after load. -link_z_lazy: bool = false, - -/// Common page size -link_z_common_page_size: ?u64 = null, - -/// Maximum page size -link_z_max_page_size: ?u64 = null, - -/// (Darwin) Install name for the dylib -install_name: ?[]const u8 = null, - -/// (Darwin) Path to entitlements file -entitlements: ?[]const u8 = null, - -/// (Darwin) Size of the pagezero segment. -pagezero_size: ?u64 = null, - -/// (Darwin) Search strategy for searching system libraries. Either `paths_first` or `dylibs_first`. -/// The former lowers to `-search_paths_first` linker option, while the latter to `-search_dylibs_first` -/// option. -/// By default, if no option is specified, the linker assumes `paths_first` as the default -/// search strategy. -search_strategy: ?enum { paths_first, dylibs_first } = null, - -/// (Darwin) Set size of the padding between the end of load commands -/// and start of `__TEXT,__text` section. -headerpad_size: ?u32 = null, - -/// (Darwin) Automatically Set size of the padding between the end of load commands -/// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN. -headerpad_max_install_names: bool = false, - -/// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols. -dead_strip_dylibs: bool = false, - -/// Position Independent Code -force_pic: ?bool = null, - -/// Position Independent Executable -pie: ?bool = null, - -red_zone: ?bool = null, - -omit_frame_pointer: ?bool = null, -dll_export_fns: ?bool = null, - -subsystem: ?std.Target.SubSystem = null, - -entry_symbol_name: ?[]const u8 = null, - -/// List of symbols forced as undefined in the symbol table -/// thus forcing their resolution by the linker. -/// Corresponds to `-u ` for ELF/MachO and `/include:` for COFF/PE. -force_undefined_symbols: std.StringHashMap(void), - -/// Overrides the default stack size -stack_size: ?u64 = null, - -want_lto: ?bool = null, -use_llvm: ?bool, -use_lld: ?bool, - -/// This is an advanced setting that can change the intent of this CompileStep. -/// If this slice has nonzero length, it means that this CompileStep exists to -/// check for compile errors and return *success* if they match, and failure -/// otherwise. -expect_errors: []const []const u8 = &.{}, - -output_path_source: GeneratedFile, -output_lib_path_source: GeneratedFile, -output_h_path_source: GeneratedFile, -output_pdb_path_source: GeneratedFile, -output_dirname_source: GeneratedFile, - -pub const CSourceFiles = struct { - files: []const []const u8, - flags: []const []const u8, -}; - -pub const CSourceFile = struct { - source: FileSource, - args: []const []const u8, - - pub fn dupe(self: CSourceFile, b: *std.Build) CSourceFile { - return .{ - .source = self.source.dupe(b), - .args = b.dupeStrings(self.args), - }; - } -}; - -pub const LinkObject = union(enum) { - static_path: FileSource, - other_step: *CompileStep, - system_lib: SystemLib, - assembly_file: FileSource, - c_source_file: *CSourceFile, - c_source_files: *CSourceFiles, -}; - -pub const SystemLib = struct { - name: []const u8, - needed: bool, - weak: bool, - use_pkg_config: enum { - /// Don't use pkg-config, just pass -lfoo where foo is name. - no, - /// Try to get information on how to link the library from pkg-config. - /// If that fails, fall back to passing -lfoo where foo is name. - yes, - /// Try to get information on how to link the library from pkg-config. - /// If that fails, error out. - force, - }, -}; - -const FrameworkLinkInfo = struct { - needed: bool = false, - weak: bool = false, -}; - -pub const IncludeDir = union(enum) { - raw_path: []const u8, - raw_path_system: []const u8, - other_step: *CompileStep, - config_header_step: *ConfigHeaderStep, -}; - -pub const Options = struct { - name: []const u8, - root_source_file: ?FileSource = null, - target: CrossTarget, - optimize: std.builtin.Mode, - kind: Kind, - linkage: ?Linkage = null, - version: ?std.builtin.Version = null, - max_rss: usize = 0, - filter: ?[]const u8 = null, - test_runner: ?[]const u8 = null, - link_libc: ?bool = null, - single_threaded: ?bool = null, - use_llvm: ?bool = null, - use_lld: ?bool = null, -}; - -pub const Kind = enum { - exe, - lib, - obj, - @"test", -}; - -pub const Linkage = enum { dynamic, static }; - -pub const EmitOption = union(enum) { - default: void, - no_emit: void, - emit: void, - emit_to: []const u8, - - fn getArg(self: @This(), b: *std.Build, arg_name: []const u8) ?[]const u8 { - return switch (self) { - .no_emit => b.fmt("-fno-{s}", .{arg_name}), - .default => null, - .emit => b.fmt("-f{s}", .{arg_name}), - .emit_to => |path| b.fmt("-f{s}={s}", .{ arg_name, path }), - }; - } -}; - -pub fn create(owner: *std.Build, options: Options) *CompileStep { - const name = owner.dupe(options.name); - const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null; - if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) { - panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); - } - - // Avoid the common case of the step name looking like "zig test test". - const name_adjusted = if (options.kind == .@"test" and mem.eql(u8, name, "test")) - "" - else - owner.fmt("{s} ", .{name}); - - const step_name = owner.fmt("{s} {s}{s} {s}", .{ - switch (options.kind) { - .exe => "zig build-exe", - .lib => "zig build-lib", - .obj => "zig build-obj", - .@"test" => "zig test", - }, - name_adjusted, - @tagName(options.optimize), - options.target.zigTriple(owner.allocator) catch @panic("OOM"), - }); - - const target_info = NativeTargetInfo.detect(options.target) catch @panic("unhandled error"); - - const out_filename = std.zig.binNameAlloc(owner.allocator, .{ - .root_name = name, - .target = target_info.target, - .output_mode = switch (options.kind) { - .lib => .Lib, - .obj => .Obj, - .exe, .@"test" => .Exe, - }, - .link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) { - .dynamic => .Dynamic, - .static => .Static, - }) else null, - .version = options.version, - }) catch @panic("OOM"); - - const self = owner.allocator.create(CompileStep) catch @panic("OOM"); - self.* = CompileStep{ - .strip = null, - .unwind_tables = null, - .verbose_link = false, - .verbose_cc = false, - .optimize = options.optimize, - .target = options.target, - .linkage = options.linkage, - .kind = options.kind, - .root_src = root_src, - .name = name, - .frameworks = StringHashMap(FrameworkLinkInfo).init(owner.allocator), - .step = Step.init(.{ - .id = base_id, - .name = step_name, - .owner = owner, - .makeFn = make, - .max_rss = options.max_rss, - }), - .version = options.version, - .out_filename = out_filename, - .out_h_filename = owner.fmt("{s}.h", .{name}), - .out_lib_filename = undefined, - .out_pdb_filename = owner.fmt("{s}.pdb", .{name}), - .major_only_filename = null, - .name_only_filename = null, - .modules = std.StringArrayHashMap(*Module).init(owner.allocator), - .include_dirs = ArrayList(IncludeDir).init(owner.allocator), - .link_objects = ArrayList(LinkObject).init(owner.allocator), - .c_macros = ArrayList([]const u8).init(owner.allocator), - .lib_paths = ArrayList(FileSource).init(owner.allocator), - .rpaths = ArrayList(FileSource).init(owner.allocator), - .framework_dirs = ArrayList(FileSource).init(owner.allocator), - .installed_headers = ArrayList(*Step).init(owner.allocator), - .c_std = std.Build.CStd.C99, - .zig_lib_dir = null, - .main_pkg_path = null, - .exec_cmd_args = null, - .filter = options.filter, - .test_runner = options.test_runner, - .disable_stack_probing = false, - .disable_sanitize_c = false, - .sanitize_thread = false, - .rdynamic = false, - .override_dest_dir = null, - .installed_path = null, - .force_undefined_symbols = StringHashMap(void).init(owner.allocator), - - .output_path_source = GeneratedFile{ .step = &self.step }, - .output_lib_path_source = GeneratedFile{ .step = &self.step }, - .output_h_path_source = GeneratedFile{ .step = &self.step }, - .output_pdb_path_source = GeneratedFile{ .step = &self.step }, - .output_dirname_source = GeneratedFile{ .step = &self.step }, - - .target_info = target_info, - - .is_linking_libc = options.link_libc orelse false, - .is_linking_libcpp = false, - .single_threaded = options.single_threaded, - .use_llvm = options.use_llvm, - .use_lld = options.use_lld, - }; - - if (self.kind == .lib) { - if (self.linkage != null and self.linkage.? == .static) { - self.out_lib_filename = self.out_filename; - } else if (self.version) |version| { - if (target_info.target.isDarwin()) { - self.major_only_filename = owner.fmt("lib{s}.{d}.dylib", .{ - self.name, - version.major, - }); - self.name_only_filename = owner.fmt("lib{s}.dylib", .{self.name}); - self.out_lib_filename = self.out_filename; - } else if (target_info.target.os.tag == .windows) { - self.out_lib_filename = owner.fmt("{s}.lib", .{self.name}); - } else { - self.major_only_filename = owner.fmt("lib{s}.so.{d}", .{ self.name, version.major }); - self.name_only_filename = owner.fmt("lib{s}.so", .{self.name}); - self.out_lib_filename = self.out_filename; - } - } else { - if (target_info.target.isDarwin()) { - self.out_lib_filename = self.out_filename; - } else if (target_info.target.os.tag == .windows) { - self.out_lib_filename = owner.fmt("{s}.lib", .{self.name}); - } else { - self.out_lib_filename = self.out_filename; - } - } - } - - if (root_src) |rs| rs.addStepDependencies(&self.step); - - return self; -} - -pub fn installHeader(cs: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void { - const b = cs.step.owner; - const install_file = b.addInstallHeaderFile(src_path, dest_rel_path); - b.getInstallStep().dependOn(&install_file.step); - cs.installed_headers.append(&install_file.step) catch @panic("OOM"); -} - -pub const InstallConfigHeaderOptions = struct { - install_dir: InstallDir = .header, - dest_rel_path: ?[]const u8 = null, -}; - -pub fn installConfigHeader( - cs: *CompileStep, - config_header: *ConfigHeaderStep, - options: InstallConfigHeaderOptions, -) void { - const dest_rel_path = options.dest_rel_path orelse config_header.include_path; - const b = cs.step.owner; - const install_file = b.addInstallFileWithDir( - .{ .generated = &config_header.output_file }, - options.install_dir, - dest_rel_path, - ); - install_file.step.dependOn(&config_header.step); - b.getInstallStep().dependOn(&install_file.step); - cs.installed_headers.append(&install_file.step) catch @panic("OOM"); -} - -pub fn installHeadersDirectory( - a: *CompileStep, - src_dir_path: []const u8, - dest_rel_path: []const u8, -) void { - return installHeadersDirectoryOptions(a, .{ - .source_dir = src_dir_path, - .install_dir = .header, - .install_subdir = dest_rel_path, - }); -} - -pub fn installHeadersDirectoryOptions( - cs: *CompileStep, - options: std.Build.InstallDirStep.Options, -) void { - const b = cs.step.owner; - const install_dir = b.addInstallDirectory(options); - b.getInstallStep().dependOn(&install_dir.step); - cs.installed_headers.append(&install_dir.step) catch @panic("OOM"); -} - -pub fn installLibraryHeaders(cs: *CompileStep, l: *CompileStep) void { - assert(l.kind == .lib); - const b = cs.step.owner; - const install_step = b.getInstallStep(); - // Copy each element from installed_headers, modifying the builder - // to be the new parent's builder. - for (l.installed_headers.items) |step| { - const step_copy = switch (step.id) { - inline .install_file, .install_dir => |id| blk: { - const T = id.Type(); - const ptr = b.allocator.create(T) catch @panic("OOM"); - ptr.* = step.cast(T).?.*; - ptr.dest_builder = b; - break :blk &ptr.step; - }, - else => unreachable, - }; - cs.installed_headers.append(step_copy) catch @panic("OOM"); - install_step.dependOn(step_copy); - } - cs.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM"); -} - -pub fn addObjCopy(cs: *CompileStep, options: ObjCopyStep.Options) *ObjCopyStep { - const b = cs.step.owner; - var copy = options; - if (copy.basename == null) { - if (options.format) |f| { - copy.basename = b.fmt("{s}.{s}", .{ cs.name, @tagName(f) }); - } else { - copy.basename = cs.name; - } - } - return b.addObjCopy(cs.getOutputSource(), copy); -} - -/// This function would run in the context of the package that created the executable, -/// which is undesirable when running an executable provided by a dependency package. -pub const run = @compileError("deprecated; use std.Build.addRunArtifact"); - -/// This function would install in the context of the package that created the artifact, -/// which is undesirable when installing an artifact provided by a dependency package. -pub const install = @compileError("deprecated; use std.Build.installArtifact"); - -pub fn checkObject(self: *CompileStep) *CheckObjectStep { - return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt); -} - -pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { - const b = self.step.owner; - self.linker_script = source.dupe(b); - source.addStepDependencies(&self.step); -} - -pub fn forceUndefinedSymbol(self: *CompileStep, symbol_name: []const u8) void { - const b = self.step.owner; - self.force_undefined_symbols.put(b.dupe(symbol_name), {}) catch @panic("OOM"); -} - -pub fn linkFramework(self: *CompileStep, framework_name: []const u8) void { - const b = self.step.owner; - self.frameworks.put(b.dupe(framework_name), .{}) catch @panic("OOM"); -} - -pub fn linkFrameworkNeeded(self: *CompileStep, framework_name: []const u8) void { - const b = self.step.owner; - self.frameworks.put(b.dupe(framework_name), .{ - .needed = true, - }) catch @panic("OOM"); -} - -pub fn linkFrameworkWeak(self: *CompileStep, framework_name: []const u8) void { - const b = self.step.owner; - self.frameworks.put(b.dupe(framework_name), .{ - .weak = true, - }) catch @panic("OOM"); -} - -/// Returns whether the library, executable, or object depends on a particular system library. -pub fn dependsOnSystemLibrary(self: CompileStep, name: []const u8) bool { - if (isLibCLibrary(name)) { - return self.is_linking_libc; - } - if (isLibCppLibrary(name)) { - return self.is_linking_libcpp; - } - for (self.link_objects.items) |link_object| { - switch (link_object) { - .system_lib => |lib| if (mem.eql(u8, lib.name, name)) return true, - else => continue, - } - } - return false; -} - -pub fn linkLibrary(self: *CompileStep, lib: *CompileStep) void { - assert(lib.kind == .lib); - self.linkLibraryOrObject(lib); -} - -pub fn isDynamicLibrary(self: *CompileStep) bool { - return self.kind == .lib and self.linkage == Linkage.dynamic; -} - -pub fn isStaticLibrary(self: *CompileStep) bool { - return self.kind == .lib and self.linkage != Linkage.dynamic; -} - -pub fn producesPdbFile(self: *CompileStep) bool { - if (!self.target.isWindows() and !self.target.isUefi()) return false; - if (self.target.getObjectFormat() == .c) return false; - if (self.strip == true) return false; - return self.isDynamicLibrary() or self.kind == .exe or self.kind == .@"test"; -} - -pub fn linkLibC(self: *CompileStep) void { - self.is_linking_libc = true; -} - -pub fn linkLibCpp(self: *CompileStep) void { - self.is_linking_libcpp = true; -} - -/// If the value is omitted, it is set to 1. -/// `name` and `value` need not live longer than the function call. -pub fn defineCMacro(self: *CompileStep, name: []const u8, value: ?[]const u8) void { - const b = self.step.owner; - const macro = std.Build.constructCMacro(b.allocator, name, value); - self.c_macros.append(macro) catch @panic("OOM"); -} - -/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. -pub fn defineCMacroRaw(self: *CompileStep, name_and_value: []const u8) void { - const b = self.step.owner; - self.c_macros.append(b.dupe(name_and_value)) catch @panic("OOM"); -} - -/// This one has no integration with anything, it just puts -lname on the command line. -/// Prefer to use `linkSystemLibrary` instead. -pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(name), - .needed = false, - .weak = false, - .use_pkg_config = .no, - }, - }) catch @panic("OOM"); -} - -/// This one has no integration with anything, it just puts -needed-lname on the command line. -/// Prefer to use `linkSystemLibraryNeeded` instead. -pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(name), - .needed = true, - .weak = false, - .use_pkg_config = .no, - }, - }) catch @panic("OOM"); -} - -/// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the -/// command line. Prefer to use `linkSystemLibraryWeak` instead. -pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(name), - .needed = false, - .weak = true, - .use_pkg_config = .no, - }, - }) catch @panic("OOM"); -} - -/// This links against a system library, exclusively using pkg-config to find the library. -/// Prefer to use `linkSystemLibrary` instead. -pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(lib_name), - .needed = false, - .weak = false, - .use_pkg_config = .force, - }, - }) catch @panic("OOM"); -} - -/// This links against a system library, exclusively using pkg-config to find the library. -/// Prefer to use `linkSystemLibraryNeeded` instead. -pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(lib_name), - .needed = true, - .weak = false, - .use_pkg_config = .force, - }, - }) catch @panic("OOM"); -} - -/// Run pkg-config for the given library name and parse the output, returning the arguments -/// that should be passed to zig to link the given library. -fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u8 { - const b = self.step.owner; - const pkg_name = match: { - // First we have to map the library name to pkg config name. Unfortunately, - // there are several examples where this is not straightforward: - // -lSDL2 -> pkg-config sdl2 - // -lgdk-3 -> pkg-config gdk-3.0 - // -latk-1.0 -> pkg-config atk - const pkgs = try getPkgConfigList(b); - - // Exact match means instant winner. - for (pkgs) |pkg| { - if (mem.eql(u8, pkg.name, lib_name)) { - break :match pkg.name; - } - } - - // Next we'll try ignoring case. - for (pkgs) |pkg| { - if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) { - break :match pkg.name; - } - } - - // Now try appending ".0". - for (pkgs) |pkg| { - if (std.ascii.indexOfIgnoreCase(pkg.name, lib_name)) |pos| { - if (pos != 0) continue; - if (mem.eql(u8, pkg.name[lib_name.len..], ".0")) { - break :match pkg.name; - } - } - } - - // Trimming "-1.0". - if (mem.endsWith(u8, lib_name, "-1.0")) { - const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len]; - for (pkgs) |pkg| { - if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) { - break :match pkg.name; - } - } - } - - return error.PackageNotFound; - }; - - var code: u8 = undefined; - const stdout = if (b.execAllowFail(&[_][]const u8{ - "pkg-config", - pkg_name, - "--cflags", - "--libs", - }, &code, .Ignore)) |stdout| stdout else |err| switch (err) { - error.ProcessTerminated => return error.PkgConfigCrashed, - error.ExecNotSupported => return error.PkgConfigFailed, - error.ExitCodeFailure => return error.PkgConfigFailed, - error.FileNotFound => return error.PkgConfigNotInstalled, - else => return err, - }; - - var zig_args = ArrayList([]const u8).init(b.allocator); - defer zig_args.deinit(); - - var it = mem.tokenize(u8, stdout, " \r\n\t"); - while (it.next()) |tok| { - if (mem.eql(u8, tok, "-I")) { - const dir = it.next() orelse return error.PkgConfigInvalidOutput; - try zig_args.appendSlice(&[_][]const u8{ "-I", dir }); - } else if (mem.startsWith(u8, tok, "-I")) { - try zig_args.append(tok); - } else if (mem.eql(u8, tok, "-L")) { - const dir = it.next() orelse return error.PkgConfigInvalidOutput; - try zig_args.appendSlice(&[_][]const u8{ "-L", dir }); - } else if (mem.startsWith(u8, tok, "-L")) { - try zig_args.append(tok); - } else if (mem.eql(u8, tok, "-l")) { - const lib = it.next() orelse return error.PkgConfigInvalidOutput; - try zig_args.appendSlice(&[_][]const u8{ "-l", lib }); - } else if (mem.startsWith(u8, tok, "-l")) { - try zig_args.append(tok); - } else if (mem.eql(u8, tok, "-D")) { - const macro = it.next() orelse return error.PkgConfigInvalidOutput; - try zig_args.appendSlice(&[_][]const u8{ "-D", macro }); - } else if (mem.startsWith(u8, tok, "-D")) { - try zig_args.append(tok); - } else if (b.debug_pkg_config) { - return self.step.fail("unknown pkg-config flag '{s}'", .{tok}); - } - } - - return zig_args.toOwnedSlice(); -} - -pub fn linkSystemLibrary(self: *CompileStep, name: []const u8) void { - self.linkSystemLibraryInner(name, .{}); -} - -pub fn linkSystemLibraryNeeded(self: *CompileStep, name: []const u8) void { - self.linkSystemLibraryInner(name, .{ .needed = true }); -} - -pub fn linkSystemLibraryWeak(self: *CompileStep, name: []const u8) void { - self.linkSystemLibraryInner(name, .{ .weak = true }); -} - -fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { - needed: bool = false, - weak: bool = false, -}) void { - const b = self.step.owner; - if (isLibCLibrary(name)) { - self.linkLibC(); - return; - } - if (isLibCppLibrary(name)) { - self.linkLibCpp(); - return; - } - - self.link_objects.append(.{ - .system_lib = .{ - .name = b.dupe(name), - .needed = opts.needed, - .weak = opts.weak, - .use_pkg_config = .yes, - }, - }) catch @panic("OOM"); -} - -/// Handy when you have many C/C++ source files and want them all to have the same flags. -pub fn addCSourceFiles(self: *CompileStep, files: []const []const u8, flags: []const []const u8) void { - const b = self.step.owner; - const c_source_files = b.allocator.create(CSourceFiles) catch @panic("OOM"); - - const files_copy = b.dupeStrings(files); - const flags_copy = b.dupeStrings(flags); - - c_source_files.* = .{ - .files = files_copy, - .flags = flags_copy, - }; - self.link_objects.append(.{ .c_source_files = c_source_files }) catch @panic("OOM"); -} - -pub fn addCSourceFile(self: *CompileStep, file: []const u8, flags: []const []const u8) void { - self.addCSourceFileSource(.{ - .args = flags, - .source = .{ .path = file }, - }); -} - -pub fn addCSourceFileSource(self: *CompileStep, source: CSourceFile) void { - const b = self.step.owner; - const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM"); - c_source_file.* = source.dupe(b); - self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM"); - source.source.addStepDependencies(&self.step); -} - -pub fn setVerboseLink(self: *CompileStep, value: bool) void { - self.verbose_link = value; -} - -pub fn setVerboseCC(self: *CompileStep, value: bool) void { - self.verbose_cc = value; -} - -pub fn overrideZigLibDir(self: *CompileStep, dir_path: []const u8) void { - const b = self.step.owner; - self.zig_lib_dir = b.dupePath(dir_path); -} - -pub fn setMainPkgPath(self: *CompileStep, dir_path: []const u8) void { - const b = self.step.owner; - self.main_pkg_path = b.dupePath(dir_path); -} - -pub fn setLibCFile(self: *CompileStep, libc_file: ?FileSource) void { - const b = self.step.owner; - self.libc_file = if (libc_file) |f| f.dupe(b) else null; -} - -/// Returns the generated executable, library or object file. -/// To run an executable built with zig build, use `run`, or create an install step and invoke it. -pub fn getOutputSource(self: *CompileStep) FileSource { - return .{ .generated = &self.output_path_source }; -} - -pub fn getOutputDirectorySource(self: *CompileStep) FileSource { - return .{ .generated = &self.output_dirname_source }; -} - -/// Returns the generated import library. This function can only be called for libraries. -pub fn getOutputLibSource(self: *CompileStep) FileSource { - assert(self.kind == .lib); - return .{ .generated = &self.output_lib_path_source }; -} - -/// Returns the generated header file. -/// This function can only be called for libraries or object files which have `emit_h` set. -pub fn getOutputHSource(self: *CompileStep) FileSource { - assert(self.kind != .exe and self.kind != .@"test"); - assert(self.emit_h); - return .{ .generated = &self.output_h_path_source }; -} - -/// Returns the generated PDB file. This function can only be called for Windows and UEFI. -pub fn getOutputPdbSource(self: *CompileStep) FileSource { - // TODO: Is this right? Isn't PDB for *any* PE/COFF file? - assert(self.target.isWindows() or self.target.isUefi()); - return .{ .generated = &self.output_pdb_path_source }; -} - -pub fn addAssemblyFile(self: *CompileStep, path: []const u8) void { - const b = self.step.owner; - self.link_objects.append(.{ - .assembly_file = .{ .path = b.dupe(path) }, - }) catch @panic("OOM"); -} - -pub fn addAssemblyFileSource(self: *CompileStep, source: FileSource) void { - const b = self.step.owner; - const source_duped = source.dupe(b); - self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM"); - source_duped.addStepDependencies(&self.step); -} - -pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void { - self.addObjectFileSource(.{ .path = source_file }); -} - -pub fn addObjectFileSource(self: *CompileStep, source: FileSource) void { - const b = self.step.owner; - self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM"); - source.addStepDependencies(&self.step); -} - -pub fn addObject(self: *CompileStep, obj: *CompileStep) void { - assert(obj.kind == .obj); - self.linkLibraryOrObject(obj); -} - -pub const addSystemIncludeDir = @compileError("deprecated; use addSystemIncludePath"); -pub const addIncludeDir = @compileError("deprecated; use addIncludePath"); -pub const addLibPath = @compileError("deprecated, use addLibraryPath"); -pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath"); - -pub fn addSystemIncludePath(self: *CompileStep, path: []const u8) void { - const b = self.step.owner; - self.include_dirs.append(IncludeDir{ .raw_path_system = b.dupe(path) }) catch @panic("OOM"); -} - -pub fn addIncludePath(self: *CompileStep, path: []const u8) void { - const b = self.step.owner; - self.include_dirs.append(IncludeDir{ .raw_path = b.dupe(path) }) catch @panic("OOM"); -} - -pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) void { - self.step.dependOn(&config_header.step); - self.include_dirs.append(.{ .config_header_step = config_header }) catch @panic("OOM"); -} - -pub fn addLibraryPath(self: *CompileStep, path: []const u8) void { - const b = self.step.owner; - self.lib_paths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); -} - -pub fn addLibraryPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { - self.lib_paths.append(directory_source) catch @panic("OOM"); - directory_source.addStepDependencies(&self.step); -} - -pub fn addRPath(self: *CompileStep, path: []const u8) void { - const b = self.step.owner; - self.rpaths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); -} - -pub fn addRPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { - self.rpaths.append(directory_source) catch @panic("OOM"); - directory_source.addStepDependencies(&self.step); -} - -pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void { - const b = self.step.owner; - self.framework_dirs.append(.{ .path = b.dupe(dir_path) }) catch @panic("OOM"); -} - -pub fn addFrameworkPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { - self.framework_dirs.append(directory_source) catch @panic("OOM"); - directory_source.addStepDependencies(&self.step); -} - -/// Adds a module to be used with `@import` and exposing it in the current -/// package's module table using `name`. -pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void { - const b = cs.step.owner; - cs.modules.put(b.dupe(name), module) catch @panic("OOM"); - - var done = std.AutoHashMap(*Module, void).init(b.allocator); - defer done.deinit(); - cs.addRecursiveBuildDeps(module, &done) catch @panic("OOM"); -} - -/// Adds a module to be used with `@import` without exposing it in the current -/// package's module table. -pub fn addAnonymousModule(cs: *CompileStep, name: []const u8, options: std.Build.CreateModuleOptions) void { - const b = cs.step.owner; - const module = b.createModule(options); - return addModule(cs, name, module); -} - -pub fn addOptions(cs: *CompileStep, module_name: []const u8, options: *OptionsStep) void { - addModule(cs, module_name, options.createModule()); -} - -fn addRecursiveBuildDeps(cs: *CompileStep, module: *Module, done: *std.AutoHashMap(*Module, void)) !void { - if (done.contains(module)) return; - try done.put(module, {}); - module.source_file.addStepDependencies(&cs.step); - for (module.dependencies.values()) |dep| { - try cs.addRecursiveBuildDeps(dep, done); - } -} - -/// If Vcpkg was found on the system, it will be added to include and lib -/// paths for the specified target. -pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void { - const b = self.step.owner; - // Ideally in the Unattempted case we would call the function recursively - // after findVcpkgRoot and have only one switch statement, but the compiler - // cannot resolve the error set. - switch (b.vcpkg_root) { - .unattempted => { - b.vcpkg_root = if (try findVcpkgRoot(b.allocator)) |root| - VcpkgRoot{ .found = root } - else - .not_found; - }, - .not_found => return error.VcpkgNotFound, - .found => {}, - } - - switch (b.vcpkg_root) { - .unattempted => unreachable, - .not_found => return error.VcpkgNotFound, - .found => |root| { - const allocator = b.allocator; - const triplet = try self.target.vcpkgTriplet(allocator, if (linkage == .static) .Static else .Dynamic); - defer b.allocator.free(triplet); - - const include_path = b.pathJoin(&.{ root, "installed", triplet, "include" }); - errdefer allocator.free(include_path); - try self.include_dirs.append(IncludeDir{ .raw_path = include_path }); - - const lib_path = b.pathJoin(&.{ root, "installed", triplet, "lib" }); - try self.lib_paths.append(.{ .path = lib_path }); - - self.vcpkg_bin_path = b.pathJoin(&.{ root, "installed", triplet, "bin" }); - }, - } -} - -pub fn setExecCmd(self: *CompileStep, args: []const ?[]const u8) void { - const b = self.step.owner; - assert(self.kind == .@"test"); - const duped_args = b.allocator.alloc(?[]u8, args.len) catch @panic("OOM"); - for (args, 0..) |arg, i| { - duped_args[i] = if (arg) |a| b.dupe(a) else null; - } - self.exec_cmd_args = duped_args; -} - -fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void { - self.step.dependOn(&other.step); - self.link_objects.append(.{ .other_step = other }) catch @panic("OOM"); - self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM"); - - for (other.installed_headers.items) |install_step| { - self.step.dependOn(install_step); - } -} - -fn appendModuleArgs( - cs: *CompileStep, - zig_args: *ArrayList([]const u8), -) error{OutOfMemory}!void { - const b = cs.step.owner; - // First, traverse the whole dependency graph and give every module a unique name, ideally one - // named after what it's called somewhere in the graph. It will help here to have both a mapping - // from module to name and a set of all the currently-used names. - var mod_names = std.AutoHashMap(*Module, []const u8).init(b.allocator); - var names = std.StringHashMap(void).init(b.allocator); - - var to_name = std.ArrayList(struct { - name: []const u8, - mod: *Module, - }).init(b.allocator); - { - var it = cs.modules.iterator(); - while (it.next()) |kv| { - // While we're traversing the root dependencies, let's make sure that no module names - // have colons in them, since the CLI forbids it. We handle this for transitive - // dependencies further down. - if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { - @panic("Module names cannot contain colons"); - } - try to_name.append(.{ - .name = kv.key_ptr.*, - .mod = kv.value_ptr.*, - }); - } - } - - while (to_name.popOrNull()) |dep| { - if (mod_names.contains(dep.mod)) continue; - - // We'll use this buffer to store the name we decide on - var buf = try b.allocator.alloc(u8, dep.name.len + 32); - // First, try just the exposed dependency name - @memcpy(buf[0..dep.name.len], dep.name); - var name = buf[0..dep.name.len]; - var n: usize = 0; - while (names.contains(name)) { - // If that failed, append an incrementing number to the end - name = std.fmt.bufPrint(buf, "{s}{}", .{ dep.name, n }) catch unreachable; - n += 1; - } - - try mod_names.put(dep.mod, name); - try names.put(name, {}); - - var it = dep.mod.dependencies.iterator(); - while (it.next()) |kv| { - // Same colon-in-name check as above, but for transitive dependencies. - if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { - @panic("Module names cannot contain colons"); - } - try to_name.append(.{ - .name = kv.key_ptr.*, - .mod = kv.value_ptr.*, - }); - } - } - - // Since the module names given to the CLI are based off of the exposed names, we already know - // that none of the CLI names have colons in them, so there's no need to check that explicitly. - - // Every module in the graph is now named; output their definitions - { - var it = mod_names.iterator(); - while (it.next()) |kv| { - const mod = kv.key_ptr.*; - const name = kv.value_ptr.*; - - const deps_str = try constructDepString(b.allocator, mod_names, mod.dependencies); - const src = mod.builder.pathFromRoot(mod.source_file.getPath(mod.builder)); - try zig_args.append("--mod"); - try zig_args.append(try std.fmt.allocPrint(b.allocator, "{s}:{s}:{s}", .{ name, deps_str, src })); - } - } - - // Lastly, output the root dependencies - const deps_str = try constructDepString(b.allocator, mod_names, cs.modules); - if (deps_str.len > 0) { - try zig_args.append("--deps"); - try zig_args.append(deps_str); - } -} - -fn constructDepString( - allocator: std.mem.Allocator, - mod_names: std.AutoHashMap(*Module, []const u8), - deps: std.StringArrayHashMap(*Module), -) ![]const u8 { - var deps_str = std.ArrayList(u8).init(allocator); - var it = deps.iterator(); - while (it.next()) |kv| { - const expose = kv.key_ptr.*; - const name = mod_names.get(kv.value_ptr.*).?; - if (std.mem.eql(u8, expose, name)) { - try deps_str.writer().print("{s},", .{name}); - } else { - try deps_str.writer().print("{s}={s},", .{ expose, name }); - } - } - if (deps_str.items.len > 0) { - return deps_str.items[0 .. deps_str.items.len - 1]; // omit trailing comma - } else { - return ""; - } -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - const b = step.owner; - const self = @fieldParentPtr(CompileStep, "step", step); - - if (self.root_src == null and self.link_objects.items.len == 0) { - return step.fail("the linker needs one or more objects to link", .{}); - } - - var zig_args = ArrayList([]const u8).init(b.allocator); - defer zig_args.deinit(); - - try zig_args.append(b.zig_exe); - - const cmd = switch (self.kind) { - .lib => "build-lib", - .exe => "build-exe", - .obj => "build-obj", - .@"test" => "test", - }; - try zig_args.append(cmd); - - if (b.reference_trace) |some| { - try zig_args.append(try std.fmt.allocPrint(b.allocator, "-freference-trace={d}", .{some})); - } - - try addFlag(&zig_args, "LLVM", self.use_llvm); - try addFlag(&zig_args, "LLD", self.use_lld); - - if (self.target.ofmt) |ofmt| { - try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)})); - } - - if (self.entry_symbol_name) |entry| { - try zig_args.append("--entry"); - try zig_args.append(entry); - } - - { - var it = self.force_undefined_symbols.keyIterator(); - while (it.next()) |symbol_name| { - try zig_args.append("--force_undefined"); - try zig_args.append(symbol_name.*); - } - } - - if (self.stack_size) |stack_size| { - try zig_args.append("--stack"); - try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size})); - } - - if (self.root_src) |root_src| try zig_args.append(root_src.getPath(b)); - - // We will add link objects from transitive dependencies, but we want to keep - // all link objects in the same order provided. - // This array is used to keep self.link_objects immutable. - var transitive_deps: TransitiveDeps = .{ - .link_objects = ArrayList(LinkObject).init(b.allocator), - .seen_system_libs = StringHashMap(void).init(b.allocator), - .seen_steps = std.AutoHashMap(*const Step, void).init(b.allocator), - .is_linking_libcpp = self.is_linking_libcpp, - .is_linking_libc = self.is_linking_libc, - .frameworks = &self.frameworks, - }; - - try transitive_deps.seen_steps.put(&self.step, {}); - try transitive_deps.add(self.link_objects.items); - - var prev_has_extra_flags = false; - - for (transitive_deps.link_objects.items) |link_object| { - switch (link_object) { - .static_path => |static_path| try zig_args.append(static_path.getPath(b)), - - .other_step => |other| switch (other.kind) { - .exe => @panic("Cannot link with an executable build artifact"), - .@"test" => @panic("Cannot link with a test"), - .obj => { - try zig_args.append(other.getOutputSource().getPath(b)); - }, - .lib => l: { - if (self.isStaticLibrary() and other.isStaticLibrary()) { - // Avoid putting a static library inside a static library. - break :l; - } - - const full_path_lib = other.getOutputLibSource().getPath(b); - try zig_args.append(full_path_lib); - - if (other.linkage == Linkage.dynamic and !self.target.isWindows()) { - if (fs.path.dirname(full_path_lib)) |dirname| { - try zig_args.append("-rpath"); - try zig_args.append(dirname); - } - } - }, - }, - - .system_lib => |system_lib| { - const prefix: []const u8 = prefix: { - if (system_lib.needed) break :prefix "-needed-l"; - if (system_lib.weak) break :prefix "-weak-l"; - break :prefix "-l"; - }; - switch (system_lib.use_pkg_config) { - .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })), - .yes, .force => { - if (self.runPkgConfig(system_lib.name)) |args| { - try zig_args.appendSlice(args); - } else |err| switch (err) { - error.PkgConfigInvalidOutput, - error.PkgConfigCrashed, - error.PkgConfigFailed, - error.PkgConfigNotInstalled, - error.PackageNotFound, - => switch (system_lib.use_pkg_config) { - .yes => { - // pkg-config failed, so fall back to linking the library - // by name directly. - try zig_args.append(b.fmt("{s}{s}", .{ - prefix, - system_lib.name, - })); - }, - .force => { - panic("pkg-config failed for library {s}", .{system_lib.name}); - }, - .no => unreachable, - }, - - else => |e| return e, - } - }, - } - }, - - .assembly_file => |asm_file| { - if (prev_has_extra_flags) { - try zig_args.append("-extra-cflags"); - try zig_args.append("--"); - prev_has_extra_flags = false; - } - try zig_args.append(asm_file.getPath(b)); - }, - - .c_source_file => |c_source_file| { - if (c_source_file.args.len == 0) { - if (prev_has_extra_flags) { - try zig_args.append("-cflags"); - try zig_args.append("--"); - prev_has_extra_flags = false; - } - } else { - try zig_args.append("-cflags"); - for (c_source_file.args) |arg| { - try zig_args.append(arg); - } - try zig_args.append("--"); - } - try zig_args.append(c_source_file.source.getPath(b)); - }, - - .c_source_files => |c_source_files| { - if (c_source_files.flags.len == 0) { - if (prev_has_extra_flags) { - try zig_args.append("-cflags"); - try zig_args.append("--"); - prev_has_extra_flags = false; - } - } else { - try zig_args.append("-cflags"); - for (c_source_files.flags) |flag| { - try zig_args.append(flag); - } - try zig_args.append("--"); - } - for (c_source_files.files) |file| { - try zig_args.append(b.pathFromRoot(file)); - } - }, - } - } - - if (transitive_deps.is_linking_libcpp) { - try zig_args.append("-lc++"); - } - - if (transitive_deps.is_linking_libc) { - try zig_args.append("-lc"); - } - - if (self.image_base) |image_base| { - try zig_args.append("--image-base"); - try zig_args.append(b.fmt("0x{x}", .{image_base})); - } - - if (self.filter) |filter| { - try zig_args.append("--test-filter"); - try zig_args.append(filter); - } - - if (self.test_evented_io) { - try zig_args.append("--test-evented-io"); - } - - if (self.test_runner) |test_runner| { - try zig_args.append("--test-runner"); - try zig_args.append(b.pathFromRoot(test_runner)); - } - - for (b.debug_log_scopes) |log_scope| { - try zig_args.append("--debug-log"); - try zig_args.append(log_scope); - } - - if (b.debug_compile_errors) { - try zig_args.append("--debug-compile-errors"); - } - - if (b.verbose_cimport) try zig_args.append("--verbose-cimport"); - if (b.verbose_air) try zig_args.append("--verbose-air"); - if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path})); - if (b.verbose_llvm_bc) |path| try zig_args.append(b.fmt("--verbose-llvm-bc={s}", .{path})); - if (b.verbose_link or self.verbose_link) try zig_args.append("--verbose-link"); - if (b.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc"); - if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features"); - - if (self.emit_analysis.getArg(b, "emit-analysis")) |arg| try zig_args.append(arg); - if (self.emit_asm.getArg(b, "emit-asm")) |arg| try zig_args.append(arg); - if (self.emit_bin.getArg(b, "emit-bin")) |arg| try zig_args.append(arg); - if (self.emit_docs.getArg(b, "emit-docs")) |arg| try zig_args.append(arg); - if (self.emit_implib.getArg(b, "emit-implib")) |arg| try zig_args.append(arg); - if (self.emit_llvm_bc.getArg(b, "emit-llvm-bc")) |arg| try zig_args.append(arg); - if (self.emit_llvm_ir.getArg(b, "emit-llvm-ir")) |arg| try zig_args.append(arg); - - if (self.emit_h) try zig_args.append("-femit-h"); - - try addFlag(&zig_args, "strip", self.strip); - try addFlag(&zig_args, "unwind-tables", self.unwind_tables); - - if (self.dwarf_format) |dwarf_format| { - try zig_args.append(switch (dwarf_format) { - .@"32" => "-gdwarf32", - .@"64" => "-gdwarf64", - }); - } - - switch (self.compress_debug_sections) { - .none => {}, - .zlib => try zig_args.append("--compress-debug-sections=zlib"), - } - - if (self.link_eh_frame_hdr) { - try zig_args.append("--eh-frame-hdr"); - } - if (self.link_emit_relocs) { - try zig_args.append("--emit-relocs"); - } - if (self.link_function_sections) { - try zig_args.append("-ffunction-sections"); - } - if (self.link_gc_sections) |x| { - try zig_args.append(if (x) "--gc-sections" else "--no-gc-sections"); - } - if (!self.linker_dynamicbase) { - try zig_args.append("--no-dynamicbase"); - } - if (self.linker_allow_shlib_undefined) |x| { - try zig_args.append(if (x) "-fallow-shlib-undefined" else "-fno-allow-shlib-undefined"); - } - if (self.link_z_notext) { - try zig_args.append("-z"); - try zig_args.append("notext"); - } - if (!self.link_z_relro) { - try zig_args.append("-z"); - try zig_args.append("norelro"); - } - if (self.link_z_lazy) { - try zig_args.append("-z"); - try zig_args.append("lazy"); - } - if (self.link_z_common_page_size) |size| { - try zig_args.append("-z"); - try zig_args.append(b.fmt("common-page-size={d}", .{size})); - } - if (self.link_z_max_page_size) |size| { - try zig_args.append("-z"); - try zig_args.append(b.fmt("max-page-size={d}", .{size})); - } - - if (self.libc_file) |libc_file| { - try zig_args.append("--libc"); - try zig_args.append(libc_file.getPath(b)); - } else if (b.libc_file) |libc_file| { - try zig_args.append("--libc"); - try zig_args.append(libc_file); - } - - switch (self.optimize) { - .Debug => {}, // Skip since it's the default. - else => try zig_args.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), - } - - try zig_args.append("--cache-dir"); - try zig_args.append(b.cache_root.path orelse "."); - - try zig_args.append("--global-cache-dir"); - try zig_args.append(b.global_cache_root.path orelse "."); - - try zig_args.append("--name"); - try zig_args.append(self.name); - - if (self.linkage) |some| switch (some) { - .dynamic => try zig_args.append("-dynamic"), - .static => try zig_args.append("-static"), - }; - if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic) { - if (self.version) |version| { - try zig_args.append("--version"); - try zig_args.append(b.fmt("{}", .{version})); - } - - if (self.target.isDarwin()) { - const install_name = self.install_name orelse b.fmt("@rpath/{s}{s}{s}", .{ - self.target.libPrefix(), - self.name, - self.target.dynamicLibSuffix(), - }); - try zig_args.append("-install_name"); - try zig_args.append(install_name); - } - } - - if (self.entitlements) |entitlements| { - try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); - } - if (self.pagezero_size) |pagezero_size| { - const size = try std.fmt.allocPrint(b.allocator, "{x}", .{pagezero_size}); - try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); - } - if (self.search_strategy) |strat| switch (strat) { - .paths_first => try zig_args.append("-search_paths_first"), - .dylibs_first => try zig_args.append("-search_dylibs_first"), - }; - if (self.headerpad_size) |headerpad_size| { - const size = try std.fmt.allocPrint(b.allocator, "{x}", .{headerpad_size}); - try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); - } - if (self.headerpad_max_install_names) { - try zig_args.append("-headerpad_max_install_names"); - } - if (self.dead_strip_dylibs) { - try zig_args.append("-dead_strip_dylibs"); - } - - try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt); - try addFlag(&zig_args, "single-threaded", self.single_threaded); - if (self.disable_stack_probing) { - try zig_args.append("-fno-stack-check"); - } - try addFlag(&zig_args, "stack-protector", self.stack_protector); - if (self.red_zone) |red_zone| { - if (red_zone) { - try zig_args.append("-mred-zone"); - } else { - try zig_args.append("-mno-red-zone"); - } - } - try addFlag(&zig_args, "omit-frame-pointer", self.omit_frame_pointer); - try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns); - - if (self.disable_sanitize_c) { - try zig_args.append("-fno-sanitize-c"); - } - if (self.sanitize_thread) { - try zig_args.append("-fsanitize-thread"); - } - if (self.rdynamic) { - try zig_args.append("-rdynamic"); - } - if (self.import_memory) { - try zig_args.append("--import-memory"); - } - if (self.import_symbols) { - try zig_args.append("--import-symbols"); - } - if (self.import_table) { - try zig_args.append("--import-table"); - } - if (self.export_table) { - try zig_args.append("--export-table"); - } - if (self.initial_memory) |initial_memory| { - try zig_args.append(b.fmt("--initial-memory={d}", .{initial_memory})); - } - if (self.max_memory) |max_memory| { - try zig_args.append(b.fmt("--max-memory={d}", .{max_memory})); - } - if (self.shared_memory) { - try zig_args.append("--shared-memory"); - } - if (self.global_base) |global_base| { - try zig_args.append(b.fmt("--global-base={d}", .{global_base})); - } - - if (self.code_model != .default) { - try zig_args.append("-mcmodel"); - try zig_args.append(@tagName(self.code_model)); - } - if (self.wasi_exec_model) |model| { - try zig_args.append(b.fmt("-mexec-model={s}", .{@tagName(model)})); - } - for (self.export_symbol_names) |symbol_name| { - try zig_args.append(b.fmt("--export={s}", .{symbol_name})); - } - - if (!self.target.isNative()) { - try zig_args.appendSlice(&.{ - "-target", try self.target.zigTriple(b.allocator), - "-mcpu", try std.Build.serializeCpu(b.allocator, self.target.getCpu()), - }); - - if (self.target.dynamic_linker.get()) |dynamic_linker| { - try zig_args.append("--dynamic-linker"); - try zig_args.append(dynamic_linker); - } - } - - if (self.linker_script) |linker_script| { - try zig_args.append("--script"); - try zig_args.append(linker_script.getPath(b)); - } - - if (self.version_script) |version_script| { - try zig_args.append("--version-script"); - try zig_args.append(b.pathFromRoot(version_script)); - } - - if (self.kind == .@"test") { - if (self.exec_cmd_args) |exec_cmd_args| { - for (exec_cmd_args) |cmd_arg| { - if (cmd_arg) |arg| { - try zig_args.append("--test-cmd"); - try zig_args.append(arg); - } else { - try zig_args.append("--test-cmd-bin"); - } - } - } - } - - try self.appendModuleArgs(&zig_args); - - for (self.include_dirs.items) |include_dir| { - switch (include_dir) { - .raw_path => |include_path| { - try zig_args.append("-I"); - try zig_args.append(b.pathFromRoot(include_path)); - }, - .raw_path_system => |include_path| { - if (b.sysroot != null) { - try zig_args.append("-iwithsysroot"); - } else { - try zig_args.append("-isystem"); - } - - const resolved_include_path = b.pathFromRoot(include_path); - - const common_include_path = if (builtin.os.tag == .windows and b.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: { - // We need to check for disk designator and strip it out from dir path so - // that zig/clang can concat resolved_include_path with sysroot. - const disk_designator = fs.path.diskDesignatorWindows(resolved_include_path); - - if (mem.indexOf(u8, resolved_include_path, disk_designator)) |where| { - break :blk resolved_include_path[where + disk_designator.len ..]; - } - - break :blk resolved_include_path; - } else resolved_include_path; - - try zig_args.append(common_include_path); - }, - .other_step => |other| { - if (other.emit_h) { - const h_path = other.getOutputHSource().getPath(b); - try zig_args.append("-isystem"); - try zig_args.append(fs.path.dirname(h_path).?); - } - if (other.installed_headers.items.len > 0) { - try zig_args.append("-I"); - try zig_args.append(b.pathJoin(&.{ - other.step.owner.install_prefix, "include", - })); - } - }, - .config_header_step => |config_header| { - const full_file_path = config_header.output_file.path.?; - const header_dir_path = full_file_path[0 .. full_file_path.len - config_header.include_path.len]; - try zig_args.appendSlice(&.{ "-I", header_dir_path }); - }, - } - } - - for (self.c_macros.items) |c_macro| { - try zig_args.append("-D"); - try zig_args.append(c_macro); - } - - try zig_args.ensureUnusedCapacity(2 * self.lib_paths.items.len); - for (self.lib_paths.items) |lib_path| { - zig_args.appendAssumeCapacity("-L"); - zig_args.appendAssumeCapacity(lib_path.getPath2(b, step)); - } - - try zig_args.ensureUnusedCapacity(2 * self.rpaths.items.len); - for (self.rpaths.items) |rpath| { - zig_args.appendAssumeCapacity("-rpath"); - - if (self.target_info.target.isDarwin()) switch (rpath) { - .path => |path| { - // On Darwin, we should not try to expand special runtime paths such as - // * @executable_path - // * @loader_path - if (mem.startsWith(u8, path, "@executable_path") or - mem.startsWith(u8, path, "@loader_path")) - { - zig_args.appendAssumeCapacity(path); - continue; - } - }, - .generated => {}, - }; - - zig_args.appendAssumeCapacity(rpath.getPath2(b, step)); - } - - for (self.framework_dirs.items) |directory_source| { - if (b.sysroot != null) { - try zig_args.append("-iframeworkwithsysroot"); - } else { - try zig_args.append("-iframework"); - } - try zig_args.append(directory_source.getPath2(b, step)); - try zig_args.append("-F"); - try zig_args.append(directory_source.getPath2(b, step)); - } - - { - var it = self.frameworks.iterator(); - while (it.next()) |entry| { - const name = entry.key_ptr.*; - const info = entry.value_ptr.*; - if (info.needed) { - try zig_args.append("-needed_framework"); - } else if (info.weak) { - try zig_args.append("-weak_framework"); - } else { - try zig_args.append("-framework"); - } - try zig_args.append(name); - } - } - - if (b.sysroot) |sysroot| { - try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot }); - } - - for (b.search_prefixes.items) |search_prefix| { - var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| { - return step.fail("unable to open prefix directory '{s}': {s}", .{ - search_prefix, @errorName(err), - }); - }; - defer prefix_dir.close(); - - // Avoid passing -L and -I flags for nonexistent directories. - // This prevents a warning, that should probably be upgraded to an error in Zig's - // CLI parsing code, when the linker sees an -L directory that does not exist. - - if (prefix_dir.accessZ("lib", .{})) |_| { - try zig_args.appendSlice(&.{ - "-L", try fs.path.join(b.allocator, &.{ search_prefix, "lib" }), - }); - } else |err| switch (err) { - error.FileNotFound => {}, - else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{ - search_prefix, @errorName(e), - }), - } - - if (prefix_dir.accessZ("include", .{})) |_| { - try zig_args.appendSlice(&.{ - "-I", try fs.path.join(b.allocator, &.{ search_prefix, "include" }), - }); - } else |err| switch (err) { - error.FileNotFound => {}, - else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{ - search_prefix, @errorName(e), - }), - } - } - - try addFlag(&zig_args, "valgrind", self.valgrind_support); - try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath); - try addFlag(&zig_args, "build-id", self.build_id); - - if (self.zig_lib_dir) |dir| { - try zig_args.append("--zig-lib-dir"); - try zig_args.append(b.pathFromRoot(dir)); - } else if (b.zig_lib_dir) |dir| { - try zig_args.append("--zig-lib-dir"); - try zig_args.append(dir); - } - - if (self.main_pkg_path) |dir| { - try zig_args.append("--main-pkg-path"); - try zig_args.append(b.pathFromRoot(dir)); - } - - try addFlag(&zig_args, "PIC", self.force_pic); - try addFlag(&zig_args, "PIE", self.pie); - try addFlag(&zig_args, "lto", self.want_lto); - - if (self.subsystem) |subsystem| { - try zig_args.append("--subsystem"); - try zig_args.append(switch (subsystem) { - .Console => "console", - .Windows => "windows", - .Posix => "posix", - .Native => "native", - .EfiApplication => "efi_application", - .EfiBootServiceDriver => "efi_boot_service_driver", - .EfiRom => "efi_rom", - .EfiRuntimeDriver => "efi_runtime_driver", - }); - } - - try zig_args.append("--listen=-"); - - // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux - // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and - // pass that to zig, e.g. via 'zig build-lib @args.rsp' - // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html - var args_length: usize = 0; - for (zig_args.items) |arg| { - args_length += arg.len + 1; // +1 to account for null terminator - } - if (args_length >= 30 * 1024) { - try b.cache_root.handle.makePath("args"); - - const args_to_escape = zig_args.items[2..]; - var escaped_args = try ArrayList([]const u8).initCapacity(b.allocator, args_to_escape.len); - arg_blk: for (args_to_escape) |arg| { - for (arg, 0..) |c, arg_idx| { - if (c == '\\' or c == '"') { - // Slow path for arguments that need to be escaped. We'll need to allocate and copy - var escaped = try ArrayList(u8).initCapacity(b.allocator, arg.len + 1); - const writer = escaped.writer(); - try writer.writeAll(arg[0..arg_idx]); - for (arg[arg_idx..]) |to_escape| { - if (to_escape == '\\' or to_escape == '"') try writer.writeByte('\\'); - try writer.writeByte(to_escape); - } - escaped_args.appendAssumeCapacity(escaped.items); - continue :arg_blk; - } - } - escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument - } - - // Write the args to zig-cache/args/ to avoid conflicts with - // other zig build commands running in parallel. - const partially_quoted = try std.mem.join(b.allocator, "\" \"", escaped_args.items); - const args = try std.mem.concat(b.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" }); - - var args_hash: [Sha256.digest_length]u8 = undefined; - Sha256.hash(args, &args_hash, .{}); - var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined; - _ = try std.fmt.bufPrint( - &args_hex_hash, - "{s}", - .{std.fmt.fmtSliceHexLower(&args_hash)}, - ); - - const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash; - try b.cache_root.handle.writeFile(args_file, args); - - const resolved_args_file = try mem.concat(b.allocator, u8, &.{ - "@", - try b.cache_root.join(b.allocator, &.{args_file}), - }); - - zig_args.shrinkRetainingCapacity(2); - try zig_args.append(resolved_args_file); - } - - const output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) { - error.NeedCompileErrorCheck => { - assert(self.expect_errors.len != 0); - try checkCompileErrors(self); - return; - }, - else => |e| return e, - }; - const output_dir = fs.path.dirname(output_bin_path).?; - - // Update generated files - { - self.output_dirname_source.path = output_dir; - - self.output_path_source.path = b.pathJoin( - &.{ output_dir, self.out_filename }, - ); - - if (self.kind == .lib) { - self.output_lib_path_source.path = b.pathJoin( - &.{ output_dir, self.out_lib_filename }, - ); - } - - if (self.emit_h) { - self.output_h_path_source.path = b.pathJoin( - &.{ output_dir, self.out_h_filename }, - ); - } - - if (self.target.isWindows() or self.target.isUefi()) { - self.output_pdb_path_source.path = b.pathJoin( - &.{ output_dir, self.out_pdb_filename }, - ); - } - } - - if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and - self.version != null and self.target.wantSharedLibSymLinks()) - { - try doAtomicSymLinks( - step, - self.getOutputSource().getPath(b), - self.major_only_filename.?, - self.name_only_filename.?, - ); - } -} - -fn isLibCLibrary(name: []const u8) bool { - const libc_libraries = [_][]const u8{ "c", "m", "dl", "rt", "pthread" }; - for (libc_libraries) |libc_lib_name| { - if (mem.eql(u8, name, libc_lib_name)) - return true; - } - return false; -} - -fn isLibCppLibrary(name: []const u8) bool { - const libcpp_libraries = [_][]const u8{ "c++", "stdc++" }; - for (libcpp_libraries) |libcpp_lib_name| { - if (mem.eql(u8, name, libcpp_lib_name)) - return true; - } - return false; -} - -/// Returned slice must be freed by the caller. -fn findVcpkgRoot(allocator: Allocator) !?[]const u8 { - const appdata_path = try fs.getAppDataDir(allocator, "vcpkg"); - defer allocator.free(appdata_path); - - const path_file = try fs.path.join(allocator, &[_][]const u8{ appdata_path, "vcpkg.path.txt" }); - defer allocator.free(path_file); - - const file = fs.cwd().openFile(path_file, .{}) catch return null; - defer file.close(); - - const size = @intCast(usize, try file.getEndPos()); - const vcpkg_path = try allocator.alloc(u8, size); - const size_read = try file.read(vcpkg_path); - std.debug.assert(size == size_read); - - return vcpkg_path; -} - -pub fn doAtomicSymLinks( - step: *Step, - output_path: []const u8, - filename_major_only: []const u8, - filename_name_only: []const u8, -) !void { - const arena = step.owner.allocator; - const out_dir = fs.path.dirname(output_path) orelse "."; - const out_basename = fs.path.basename(output_path); - // sym link for libfoo.so.1 to libfoo.so.1.2.3 - const major_only_path = try fs.path.join(arena, &.{ out_dir, filename_major_only }); - fs.atomicSymLink(arena, out_basename, major_only_path) catch |err| { - return step.fail("unable to symlink {s} -> {s}: {s}", .{ - major_only_path, out_basename, @errorName(err), - }); - }; - // sym link for libfoo.so to libfoo.so.1 - const name_only_path = try fs.path.join(arena, &.{ out_dir, filename_name_only }); - fs.atomicSymLink(arena, filename_major_only, name_only_path) catch |err| { - return step.fail("Unable to symlink {s} -> {s}: {s}", .{ - name_only_path, filename_major_only, @errorName(err), - }); - }; -} - -fn execPkgConfigList(self: *std.Build, out_code: *u8) (PkgConfigError || ExecError)![]const PkgConfigPkg { - const stdout = try self.execAllowFail(&[_][]const u8{ "pkg-config", "--list-all" }, out_code, .Ignore); - var list = ArrayList(PkgConfigPkg).init(self.allocator); - errdefer list.deinit(); - var line_it = mem.tokenize(u8, stdout, "\r\n"); - while (line_it.next()) |line| { - if (mem.trim(u8, line, " \t").len == 0) continue; - var tok_it = mem.tokenize(u8, line, " \t"); - try list.append(PkgConfigPkg{ - .name = tok_it.next() orelse return error.PkgConfigInvalidOutput, - .desc = tok_it.rest(), - }); - } - return list.toOwnedSlice(); -} - -fn getPkgConfigList(self: *std.Build) ![]const PkgConfigPkg { - if (self.pkg_config_pkg_list) |res| { - return res; - } - var code: u8 = undefined; - if (execPkgConfigList(self, &code)) |list| { - self.pkg_config_pkg_list = list; - return list; - } else |err| { - const result = switch (err) { - error.ProcessTerminated => error.PkgConfigCrashed, - error.ExecNotSupported => error.PkgConfigFailed, - error.ExitCodeFailure => error.PkgConfigFailed, - error.FileNotFound => error.PkgConfigNotInstalled, - error.InvalidName => error.PkgConfigNotInstalled, - error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, - else => return err, - }; - self.pkg_config_pkg_list = result; - return result; - } -} - -fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void { - const cond = opt orelse return; - try args.ensureUnusedCapacity(1); - if (cond) { - args.appendAssumeCapacity("-f" ++ name); - } else { - args.appendAssumeCapacity("-fno-" ++ name); - } -} - -const TransitiveDeps = struct { - link_objects: ArrayList(LinkObject), - seen_system_libs: StringHashMap(void), - seen_steps: std.AutoHashMap(*const Step, void), - is_linking_libcpp: bool, - is_linking_libc: bool, - frameworks: *StringHashMap(FrameworkLinkInfo), - - fn add(td: *TransitiveDeps, link_objects: []const LinkObject) !void { - try td.link_objects.ensureUnusedCapacity(link_objects.len); - - for (link_objects) |link_object| { - try td.link_objects.append(link_object); - switch (link_object) { - .other_step => |other| try addInner(td, other, other.isDynamicLibrary()), - else => {}, - } - } - } - - fn addInner(td: *TransitiveDeps, other: *CompileStep, dyn: bool) !void { - // Inherit dependency on libc and libc++ - td.is_linking_libcpp = td.is_linking_libcpp or other.is_linking_libcpp; - td.is_linking_libc = td.is_linking_libc or other.is_linking_libc; - - // Inherit dependencies on darwin frameworks - if (!dyn) { - var it = other.frameworks.iterator(); - while (it.next()) |framework| { - try td.frameworks.put(framework.key_ptr.*, framework.value_ptr.*); - } - } - - // Inherit dependencies on system libraries and static libraries. - for (other.link_objects.items) |other_link_object| { - switch (other_link_object) { - .system_lib => |system_lib| { - if ((try td.seen_system_libs.fetchPut(system_lib.name, {})) != null) - continue; - - if (dyn) - continue; - - try td.link_objects.append(other_link_object); - }, - .other_step => |inner_other| { - if ((try td.seen_steps.fetchPut(&inner_other.step, {})) != null) - continue; - - if (!dyn) - try td.link_objects.append(other_link_object); - - try addInner(td, inner_other, dyn or inner_other.isDynamicLibrary()); - }, - else => continue, - } - } - } -}; - -fn checkCompileErrors(self: *CompileStep) !void { - // Clear this field so that it does not get printed by the build runner. - const actual_eb = self.step.result_error_bundle; - self.step.result_error_bundle = std.zig.ErrorBundle.empty; - - const arena = self.step.owner.allocator; - - var actual_stderr_list = std.ArrayList(u8).init(arena); - try actual_eb.renderToWriter(.{ - .ttyconf = .no_color, - .include_reference_trace = false, - .include_source_line = false, - }, actual_stderr_list.writer()); - const actual_stderr = try actual_stderr_list.toOwnedSlice(); - - // Render the expected lines into a string that we can compare verbatim. - var expected_generated = std.ArrayList(u8).init(arena); - - var actual_line_it = mem.split(u8, actual_stderr, "\n"); - for (self.expect_errors) |expect_line| { - const actual_line = actual_line_it.next() orelse { - try expected_generated.appendSlice(expect_line); - try expected_generated.append('\n'); - continue; - }; - if (mem.endsWith(u8, actual_line, expect_line)) { - try expected_generated.appendSlice(actual_line); - try expected_generated.append('\n'); - continue; - } - if (mem.startsWith(u8, expect_line, ":?:?: ")) { - if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) { - try expected_generated.appendSlice(actual_line); - try expected_generated.append('\n'); - continue; - } - } - try expected_generated.appendSlice(expect_line); - try expected_generated.append('\n'); - } - - if (mem.eql(u8, expected_generated.items, actual_stderr)) return; - - // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile - return self.step.fail( - \\ - \\========= expected: ===================== - \\{s} - \\========= but found: ==================== - \\{s} - \\========================================= - , .{ expected_generated.items, actual_stderr }); -} diff --git a/lib/std/Build/ConfigHeaderStep.zig b/lib/std/Build/ConfigHeaderStep.zig deleted file mode 100644 index c1849b410e0b1b4fc23804d6ec41458c7195d006..0000000000000000000000000000000000000000 --- a/lib/std/Build/ConfigHeaderStep.zig +++ /dev/null @@ -1,437 +0,0 @@ -pub const Style = union(enum) { - /// The configure format supported by autotools. It uses `#undef foo` to - /// mark lines that can be substituted with different values. - autoconf: std.Build.FileSource, - /// The configure format supported by CMake. It uses `@@FOO@@` and - /// `#cmakedefine` for template substitution. - cmake: std.Build.FileSource, - /// 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 getFileSource(style: Style) ?std.Build.FileSource { - switch (style) { - .autoconf, .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, -}; - -step: Step, -values: std.StringArrayHashMap(Value), -output_file: std.Build.GeneratedFile, - -style: Style, -max_bytes: usize, -include_path: []const u8, - -pub const base_id: Step.Id = .config_header; - -pub const Options = struct { - style: Style = .blank, - max_bytes: usize = 2 * 1024 * 1024, - include_path: ?[]const u8 = null, - first_ret_addr: ?usize = null, -}; - -pub fn create(owner: *std.Build, options: Options) *ConfigHeaderStep { - const self = owner.allocator.create(ConfigHeaderStep) catch @panic("OOM"); - - var include_path: []const u8 = "config.h"; - - if (options.style.getFileSource()) |s| switch (s) { - .path => |p| { - const basename = std.fs.path.basename(p); - if (std.mem.endsWith(u8, basename, ".h.in")) { - include_path = basename[0 .. basename.len - 3]; - } - }, - else => {}, - }; - - if (options.include_path) |p| { - include_path = p; - } - - const name = if (options.style.getFileSource()) |s| - owner.fmt("configure {s} header {s} to {s}", .{ - @tagName(options.style), s.getDisplayName(), include_path, - }) - else - owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path }); - - self.* = .{ - .step = Step.init(.{ - .id = base_id, - .name = name, - .owner = owner, - .makeFn = make, - .first_ret_addr = options.first_ret_addr orelse @returnAddress(), - }), - .style = options.style, - .values = std.StringArrayHashMap(Value).init(owner.allocator), - - .max_bytes = options.max_bytes, - .include_path = include_path, - .output_file = .{ .step = &self.step }, - }; - - return self; -} - -pub fn addValues(self: *ConfigHeaderStep, values: anytype) void { - return addValuesInner(self, values) catch @panic("OOM"); -} - -pub fn getFileSource(self: *ConfigHeaderStep) std.Build.FileSource { - return .{ .generated = &self.output_file }; -} - -fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void { - inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| { - try putValue(self, field.name, field.type, @field(values, field.name)); - } -} - -fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v: T) !void { - switch (@typeInfo(T)) { - .Null => { - try self.values.put(field_name, .undef); - }, - .Void => { - try self.values.put(field_name, .defined); - }, - .Bool => { - try self.values.put(field_name, .{ .boolean = v }); - }, - .Int => { - try self.values.put(field_name, .{ .int = v }); - }, - .ComptimeInt => { - try self.values.put(field_name, .{ .int = v }); - }, - .EnumLiteral => { - try self.values.put(field_name, .{ .ident = @tagName(v) }); - }, - .Optional => { - if (v) |x| { - return putValue(self, field_name, @TypeOf(x), x); - } else { - try self.values.put(field_name, .undef); - } - }, - .Pointer => |ptr| { - switch (@typeInfo(ptr.child)) { - .Array => |array| { - if (ptr.size == .One and array.child == u8) { - try self.values.put(field_name, .{ .string = v }); - return; - } - }, - .Int => { - if (ptr.size == .Slice and ptr.child == u8) { - try self.values.put(field_name, .{ .string = v }); - return; - } - }, - else => {}, - } - - @compileError("unsupported ConfigHeaderStep value type: " ++ @typeName(T)); - }, - else => @compileError("unsupported ConfigHeaderStep value type: " ++ @typeName(T)), - } -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const b = step.owner; - const self = @fieldParentPtr(ConfigHeaderStep, "step", step); - const gpa = b.allocator; - const arena = b.allocator; - - var man = b.cache.obtain(); - defer man.deinit(); - - // Random bytes to make ConfigHeaderStep unique. Refresh this with new - // random bytes when ConfigHeaderStep implementation is modified in a - // non-backwards-compatible way. - man.hash.add(@as(u32, 0xdef08d23)); - - var output = std.ArrayList(u8).init(gpa); - defer output.deinit(); - - const header_text = "This file was generated by ConfigHeaderStep using the Zig Build System."; - const c_generated_line = "/* " ++ header_text ++ " */\n"; - const asm_generated_line = "; " ++ header_text ++ "\n"; - - switch (self.style) { - .autoconf => |file_source| { - try output.appendSlice(c_generated_line); - const src_path = file_source.getPath(b); - const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); - try render_autoconf(step, contents, &output, self.values, src_path); - }, - .cmake => |file_source| { - try output.appendSlice(c_generated_line); - const src_path = file_source.getPath(b); - const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); - try render_cmake(step, contents, &output, self.values, src_path); - }, - .blank => { - try output.appendSlice(c_generated_line); - try render_blank(&output, self.values, self.include_path); - }, - .nasm => { - try output.appendSlice(asm_generated_line); - try render_nasm(&output, self.values); - }, - } - - man.hash.addBytes(output.items); - - if (try step.cacheHit(&man)) { - const digest = man.final(); - self.output_file.path = try b.cache_root.join(arena, &.{ - "o", &digest, self.include_path, - }); - return; - } - - const digest = man.final(); - - // If output_path has directory parts, deal with them. Example: - // output_dir is zig-cache/o/HASH - // output_path is libavutil/avconfig.h - // We want to open directory zig-cache/o/HASH/libavutil/ - // but keep output_dir as zig-cache/o/HASH for -I include - const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, self.include_path }); - const sub_path_dirname = std.fs.path.dirname(sub_path).?; - - b.cache_root.handle.makePath(sub_path_dirname) catch |err| { - return step.fail("unable to make path '{}{s}': {s}", .{ - b.cache_root, sub_path_dirname, @errorName(err), - }); - }; - - b.cache_root.handle.writeFile(sub_path, output.items) catch |err| { - return step.fail("unable to write file '{}{s}': {s}", .{ - b.cache_root, sub_path, @errorName(err), - }); - }; - - self.output_file.path = try b.cache_root.join(arena, &.{sub_path}); - try man.writeManifest(); -} - -fn render_autoconf( - step: *Step, - contents: []const u8, - output: *std.ArrayList(u8), - values: std.StringArrayHashMap(Value), - src_path: []const u8, -) !void { - var values_copy = try values.clone(); - defer values_copy.deinit(); - - var any_errors = false; - var line_index: u32 = 0; - var line_it = std.mem.split(u8, contents, "\n"); - while (line_it.next()) |line| : (line_index += 1) { - if (!std.mem.startsWith(u8, line, "#")) { - try output.appendSlice(line); - try output.appendSlice("\n"); - continue; - } - var it = std.mem.tokenize(u8, line[1..], " \t\r"); - const undef = it.next().?; - if (!std.mem.eql(u8, undef, "undef")) { - try output.appendSlice(line); - try output.appendSlice("\n"); - continue; - } - const name = it.rest(); - const kv = values_copy.fetchSwapRemove(name) orelse { - try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ - src_path, line_index + 1, name, - }); - any_errors = true; - continue; - }; - try renderValueC(output, name, kv.value); - } - - for (values_copy.keys()) |name| { - try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); - any_errors = true; - } - - if (any_errors) { - return error.MakeFailed; - } -} - -fn render_cmake( - step: *Step, - contents: []const u8, - output: *std.ArrayList(u8), - values: std.StringArrayHashMap(Value), - src_path: []const u8, -) !void { - var values_copy = try values.clone(); - defer values_copy.deinit(); - - var any_errors = false; - var line_index: u32 = 0; - var line_it = std.mem.split(u8, contents, "\n"); - while (line_it.next()) |line| : (line_index += 1) { - if (!std.mem.startsWith(u8, line, "#")) { - try output.appendSlice(line); - try output.appendSlice("\n"); - continue; - } - var it = std.mem.tokenize(u8, line[1..], " \t\r"); - const cmakedefine = it.next().?; - if (!std.mem.eql(u8, cmakedefine, "cmakedefine")) { - try output.appendSlice(line); - try output.appendSlice("\n"); - continue; - } - const name = it.next() orelse { - try step.addError("{s}:{d}: error: missing define name", .{ - src_path, line_index + 1, - }); - any_errors = true; - continue; - }; - const kv = values_copy.fetchSwapRemove(name) orelse { - try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ - src_path, line_index + 1, name, - }); - any_errors = true; - continue; - }; - try renderValueC(output, name, kv.value); - } - - for (values_copy.keys()) |name| { - try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); - any_errors = true; - } - - if (any_errors) { - return error.HeaderConfigFailed; - } -} - -fn render_blank( - output: *std.ArrayList(u8), - defines: std.StringArrayHashMap(Value), - include_path: []const u8, -) !void { - const include_guard_name = try output.allocator.dupe(u8, include_path); - for (include_guard_name) |*byte| { - switch (byte.*) { - 'a'...'z' => byte.* = byte.* - 'a' + 'A', - 'A'...'Z', '0'...'9' => continue, - else => byte.* = '_', - } - } - - try output.appendSlice("#ifndef "); - try output.appendSlice(include_guard_name); - try output.appendSlice("\n#define "); - try output.appendSlice(include_guard_name); - try output.appendSlice("\n"); - - const values = defines.values(); - for (defines.keys(), 0..) |name, i| { - try renderValueC(output, name, values[i]); - } - - try output.appendSlice("#endif /* "); - try output.appendSlice(include_guard_name); - try output.appendSlice(" */\n"); -} - -fn render_nasm(output: *std.ArrayList(u8), defines: std.StringArrayHashMap(Value)) !void { - const values = defines.values(); - for (defines.keys(), 0..) |name, i| { - try renderValueNasm(output, name, values[i]); - } -} - -fn renderValueC(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { - switch (value) { - .undef => { - try output.appendSlice("/* #undef "); - try output.appendSlice(name); - try output.appendSlice(" */\n"); - }, - .defined => { - try output.appendSlice("#define "); - try output.appendSlice(name); - try output.appendSlice("\n"); - }, - .boolean => |b| { - try output.appendSlice("#define "); - try output.appendSlice(name); - try output.appendSlice(" "); - try output.appendSlice(if (b) "true\n" else "false\n"); - }, - .int => |i| { - try output.writer().print("#define {s} {d}\n", .{ name, i }); - }, - .ident => |ident| { - try output.writer().print("#define {s} {s}\n", .{ name, ident }); - }, - .string => |string| { - // TODO: use C-specific escaping instead of zig string literals - try output.writer().print("#define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); - }, - } -} - -fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { - switch (value) { - .undef => { - try output.appendSlice("; %undef "); - try output.appendSlice(name); - try output.appendSlice("\n"); - }, - .defined => { - try output.appendSlice("%define "); - try output.appendSlice(name); - try output.appendSlice("\n"); - }, - .boolean => |b| { - try output.appendSlice("%define "); - try output.appendSlice(name); - try output.appendSlice(if (b) " 1\n" else " 0\n"); - }, - .int => |i| { - try output.writer().print("%define {s} {d}\n", .{ name, i }); - }, - .ident => |ident| { - try output.writer().print("%define {s} {s}\n", .{ name, ident }); - }, - .string => |string| { - // TODO: use nasm-specific escaping instead of zig string literals - try output.writer().print("%define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); - }, - } -} - -const std = @import("../std.zig"); -const ConfigHeaderStep = @This(); -const Step = std.Build.Step; diff --git a/lib/std/Build/FmtStep.zig b/lib/std/Build/FmtStep.zig deleted file mode 100644 index 2a823423362d6f3b6366c89241e055f5d1af4e3b..0000000000000000000000000000000000000000 --- a/lib/std/Build/FmtStep.zig +++ /dev/null @@ -1,73 +0,0 @@ -//! This step has two modes: -//! * Modify mode: directly modify source files, formatting them in place. -//! * Check mode: fail the step if a non-conforming file is found. - -step: Step, -paths: []const []const u8, -exclude_paths: []const []const u8, -check: bool, - -pub const base_id = .fmt; - -pub const Options = struct { - paths: []const []const u8 = &.{}, - exclude_paths: []const []const u8 = &.{}, - /// If true, fails the build step when any non-conforming files are encountered. - check: bool = false, -}; - -pub fn create(owner: *std.Build, options: Options) *FmtStep { - const self = owner.allocator.create(FmtStep) catch @panic("OOM"); - const name = if (options.check) "zig fmt --check" else "zig fmt"; - self.* = .{ - .step = Step.init(.{ - .id = base_id, - .name = name, - .owner = owner, - .makeFn = make, - }), - .paths = options.paths, - .exclude_paths = options.exclude_paths, - .check = options.check, - }; - return self; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - // zig fmt is fast enough that no progress is needed. - _ = prog_node; - - // TODO: if check=false, this means we are modifying source files in place, which - // is an operation that could race against other operations also modifying source files - // in place. In this case, this step should obtain a write lock while making those - // modifications. - - const b = step.owner; - const arena = b.allocator; - const self = @fieldParentPtr(FmtStep, "step", step); - - var argv: std.ArrayListUnmanaged([]const u8) = .{}; - try argv.ensureUnusedCapacity(arena, 2 + 1 + self.paths.len + 2 * self.exclude_paths.len); - - argv.appendAssumeCapacity(b.zig_exe); - argv.appendAssumeCapacity("fmt"); - - if (self.check) { - argv.appendAssumeCapacity("--check"); - } - - for (self.paths) |p| { - argv.appendAssumeCapacity(b.pathFromRoot(p)); - } - - for (self.exclude_paths) |p| { - argv.appendAssumeCapacity("--exclude"); - argv.appendAssumeCapacity(b.pathFromRoot(p)); - } - - return step.evalChildProcess(argv.items); -} - -const std = @import("../std.zig"); -const Step = std.Build.Step; -const FmtStep = @This(); diff --git a/lib/std/Build/InstallArtifactStep.zig b/lib/std/Build/InstallArtifactStep.zig deleted file mode 100644 index 50cf6ff323b35a9daae286b7601e042e3a9a5879..0000000000000000000000000000000000000000 --- a/lib/std/Build/InstallArtifactStep.zig +++ /dev/null @@ -1,130 +0,0 @@ -const std = @import("../std.zig"); -const Step = std.Build.Step; -const CompileStep = std.Build.CompileStep; -const InstallDir = std.Build.InstallDir; -const InstallArtifactStep = @This(); -const fs = std.fs; - -pub const base_id = .install_artifact; - -step: Step, -artifact: *CompileStep, -dest_dir: InstallDir, -pdb_dir: ?InstallDir, -h_dir: ?InstallDir, -/// If non-null, adds additional path components relative to dest_dir, and -/// overrides the basename of the CompileStep. -dest_sub_path: ?[]const u8, - -pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { - const self = owner.allocator.create(InstallArtifactStep) catch @panic("OOM"); - self.* = InstallArtifactStep{ - .step = Step.init(.{ - .id = base_id, - .name = owner.fmt("install {s}", .{artifact.name}), - .owner = owner, - .makeFn = make, - }), - .artifact = artifact, - .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { - .obj => @panic("Cannot install a .obj build artifact."), - .exe, .@"test" => InstallDir{ .bin = {} }, - .lib => InstallDir{ .lib = {} }, - }, - .pdb_dir = if (artifact.producesPdbFile()) blk: { - if (artifact.kind == .exe or artifact.kind == .@"test") { - break :blk InstallDir{ .bin = {} }; - } else { - break :blk InstallDir{ .lib = {} }; - } - } else null, - .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null, - .dest_sub_path = null, - }; - self.step.dependOn(&artifact.step); - - owner.pushInstalledFile(self.dest_dir, artifact.out_filename); - if (self.artifact.isDynamicLibrary()) { - if (artifact.major_only_filename) |name| { - owner.pushInstalledFile(.lib, name); - } - if (artifact.name_only_filename) |name| { - owner.pushInstalledFile(.lib, name); - } - if (self.artifact.target.isWindows()) { - owner.pushInstalledFile(.lib, artifact.out_lib_filename); - } - } - if (self.pdb_dir) |pdb_dir| { - owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename); - } - if (self.h_dir) |h_dir| { - owner.pushInstalledFile(h_dir, artifact.out_h_filename); - } - return self; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const self = @fieldParentPtr(InstallArtifactStep, "step", step); - const src_builder = self.artifact.step.owner; - const dest_builder = step.owner; - - const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename; - const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path); - const cwd = fs.cwd(); - - var all_cached = true; - - { - const full_src_path = self.artifact.getOutputSource().getPath(src_builder); - const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { - return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ - full_src_path, full_dest_path, @errorName(err), - }); - }; - all_cached = all_cached and p == .fresh; - } - - if (self.artifact.isDynamicLibrary() and - self.artifact.version != null and - self.artifact.target.wantSharedLibSymLinks()) - { - try CompileStep.doAtomicSymLinks(step, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); - } - if (self.artifact.isDynamicLibrary() and - self.artifact.target.isWindows() and - self.artifact.emit_implib != .no_emit) - { - const full_src_path = self.artifact.getOutputLibSource().getPath(src_builder); - const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename); - const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| { - return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ - full_src_path, full_implib_path, @errorName(err), - }); - }; - all_cached = all_cached and p == .fresh; - } - if (self.pdb_dir) |pdb_dir| { - const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder); - const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename); - const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| { - return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ - full_src_path, full_pdb_path, @errorName(err), - }); - }; - all_cached = all_cached and p == .fresh; - } - if (self.h_dir) |h_dir| { - const full_src_path = self.artifact.getOutputHSource().getPath(src_builder); - const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename); - const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| { - return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ - full_src_path, full_h_path, @errorName(err), - }); - }; - all_cached = all_cached and p == .fresh; - } - self.artifact.installed_path = full_dest_path; - step.result_cached = all_cached; -} diff --git a/lib/std/Build/InstallDirStep.zig b/lib/std/Build/InstallDirStep.zig deleted file mode 100644 index d9ea24891346cf2bb22279f9f6dc15b8aba7ded4..0000000000000000000000000000000000000000 --- a/lib/std/Build/InstallDirStep.zig +++ /dev/null @@ -1,110 +0,0 @@ -const std = @import("../std.zig"); -const mem = std.mem; -const fs = std.fs; -const Step = std.Build.Step; -const InstallDir = std.Build.InstallDir; -const InstallDirStep = @This(); - -step: Step, -options: Options, -/// This is used by the build system when a file being installed comes from one -/// package but is being installed by another. -dest_builder: *std.Build, - -pub const base_id = .install_dir; - -pub const Options = struct { - source_dir: []const u8, - install_dir: InstallDir, - install_subdir: []const u8, - /// File paths which end in any of these suffixes will be excluded - /// from being installed. - exclude_extensions: []const []const u8 = &.{}, - /// File paths which end in any of these suffixes will result in - /// empty files being installed. This is mainly intended for large - /// test.zig files in order to prevent needless installation bloat. - /// However if the files were not present at all, then - /// `@import("test.zig")` would be a compile error. - blank_extensions: []const []const u8 = &.{}, - - fn dupe(self: Options, b: *std.Build) Options { - return .{ - .source_dir = b.dupe(self.source_dir), - .install_dir = self.install_dir.dupe(b), - .install_subdir = b.dupe(self.install_subdir), - .exclude_extensions = b.dupeStrings(self.exclude_extensions), - .blank_extensions = b.dupeStrings(self.blank_extensions), - }; - } -}; - -pub fn init(owner: *std.Build, options: Options) InstallDirStep { - owner.pushInstalledFile(options.install_dir, options.install_subdir); - return .{ - .step = Step.init(.{ - .id = .install_dir, - .name = owner.fmt("install {s}/", .{options.source_dir}), - .owner = owner, - .makeFn = make, - }), - .options = options.dupe(owner), - .dest_builder = owner, - }; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const self = @fieldParentPtr(InstallDirStep, "step", step); - const dest_builder = self.dest_builder; - const arena = dest_builder.allocator; - const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir); - const src_builder = self.step.owner; - var src_dir = src_builder.build_root.handle.openIterableDir(self.options.source_dir, .{}) catch |err| { - return step.fail("unable to open source directory '{}{s}': {s}", .{ - src_builder.build_root, self.options.source_dir, @errorName(err), - }); - }; - defer src_dir.close(); - var it = try src_dir.walk(arena); - var all_cached = true; - next_entry: while (try it.next()) |entry| { - for (self.options.exclude_extensions) |ext| { - if (mem.endsWith(u8, entry.path, ext)) { - continue :next_entry; - } - } - - // relative to src build root - const src_sub_path = try fs.path.join(arena, &.{ self.options.source_dir, entry.path }); - const dest_path = try fs.path.join(arena, &.{ dest_prefix, entry.path }); - const cwd = fs.cwd(); - - switch (entry.kind) { - .Directory => try cwd.makePath(dest_path), - .File => { - for (self.options.blank_extensions) |ext| { - if (mem.endsWith(u8, entry.path, ext)) { - try dest_builder.truncateFile(dest_path); - continue :next_entry; - } - } - - const prev_status = fs.Dir.updateFile( - src_builder.build_root.handle, - src_sub_path, - cwd, - dest_path, - .{}, - ) catch |err| { - return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{ - src_builder.build_root, src_sub_path, dest_path, @errorName(err), - }); - }; - all_cached = all_cached and prev_status == .fresh; - }, - else => continue, - } - } - - step.result_cached = all_cached; -} diff --git a/lib/std/Build/InstallFileStep.zig b/lib/std/Build/InstallFileStep.zig deleted file mode 100644 index 011ad48208531020d6c0e229c4d93a616a428393..0000000000000000000000000000000000000000 --- a/lib/std/Build/InstallFileStep.zig +++ /dev/null @@ -1,57 +0,0 @@ -const std = @import("../std.zig"); -const Step = std.Build.Step; -const FileSource = std.Build.FileSource; -const InstallDir = std.Build.InstallDir; -const InstallFileStep = @This(); -const assert = std.debug.assert; - -pub const base_id = .install_file; - -step: Step, -source: FileSource, -dir: InstallDir, -dest_rel_path: []const u8, -/// This is used by the build system when a file being installed comes from one -/// package but is being installed by another. -dest_builder: *std.Build, - -pub fn create( - owner: *std.Build, - source: FileSource, - dir: InstallDir, - dest_rel_path: []const u8, -) *InstallFileStep { - assert(dest_rel_path.len != 0); - owner.pushInstalledFile(dir, dest_rel_path); - const self = owner.allocator.create(InstallFileStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = base_id, - .name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), - .owner = owner, - .makeFn = make, - }), - .source = source.dupe(owner), - .dir = dir.dupe(owner), - .dest_rel_path = owner.dupePath(dest_rel_path), - .dest_builder = owner, - }; - source.addStepDependencies(&self.step); - return self; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const src_builder = step.owner; - const self = @fieldParentPtr(InstallFileStep, "step", step); - const dest_builder = self.dest_builder; - const full_src_path = self.source.getPath2(src_builder, step); - const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path); - const cwd = std.fs.cwd(); - const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { - return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ - full_src_path, full_dest_path, @errorName(err), - }); - }; - step.result_cached = prev == .fresh; -} diff --git a/lib/std/Build/ObjCopyStep.zig b/lib/std/Build/ObjCopyStep.zig deleted file mode 100644 index 608c56591f935ef519e334266647e1e763286e70..0000000000000000000000000000000000000000 --- a/lib/std/Build/ObjCopyStep.zig +++ /dev/null @@ -1,122 +0,0 @@ -const std = @import("std"); -const ObjCopyStep = @This(); - -const Allocator = std.mem.Allocator; -const ArenaAllocator = std.heap.ArenaAllocator; -const ArrayListUnmanaged = std.ArrayListUnmanaged; -const File = std.fs.File; -const InstallDir = std.Build.InstallDir; -const CompileStep = std.Build.CompileStep; -const Step = std.Build.Step; -const elf = std.elf; -const fs = std.fs; -const io = std.io; -const sort = std.sort; - -pub const base_id: Step.Id = .objcopy; - -pub const RawFormat = enum { - bin, - hex, -}; - -step: Step, -file_source: std.Build.FileSource, -basename: []const u8, -output_file: std.Build.GeneratedFile, - -format: ?RawFormat, -only_section: ?[]const u8, -pad_to: ?u64, - -pub const Options = struct { - basename: ?[]const u8 = null, - format: ?RawFormat = null, - only_section: ?[]const u8 = null, - pad_to: ?u64 = null, -}; - -pub fn create( - owner: *std.Build, - file_source: std.Build.FileSource, - options: Options, -) *ObjCopyStep { - const self = owner.allocator.create(ObjCopyStep) catch @panic("OOM"); - self.* = ObjCopyStep{ - .step = Step.init(.{ - .id = base_id, - .name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}), - .owner = owner, - .makeFn = make, - }), - .file_source = file_source, - .basename = options.basename orelse file_source.getDisplayName(), - .output_file = std.Build.GeneratedFile{ .step = &self.step }, - - .format = options.format, - .only_section = options.only_section, - .pad_to = options.pad_to, - }; - file_source.addStepDependencies(&self.step); - return self; -} - -pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource { - return .{ .generated = &self.output_file }; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - const b = step.owner; - const self = @fieldParentPtr(ObjCopyStep, "step", step); - - var man = b.cache.obtain(); - defer man.deinit(); - - // Random bytes to make ObjCopyStep unique. Refresh this with new random - // bytes when ObjCopyStep implementation is modified incompatibly. - man.hash.add(@as(u32, 0xe18b7baf)); - - const full_src_path = self.file_source.getPath(b); - _ = try man.addFile(full_src_path, null); - man.hash.addOptionalBytes(self.only_section); - man.hash.addOptional(self.pad_to); - man.hash.addOptional(self.format); - - if (try step.cacheHit(&man)) { - // Cache hit, skip subprocess execution. - const digest = man.final(); - self.output_file.path = try b.cache_root.join(b.allocator, &.{ - "o", &digest, self.basename, - }); - return; - } - - const digest = man.final(); - const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename }); - const cache_path = "o" ++ fs.path.sep_str ++ digest; - b.cache_root.handle.makePath(cache_path) catch |err| { - return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); - }; - - var argv = std.ArrayList([]const u8).init(b.allocator); - try argv.appendSlice(&.{ b.zig_exe, "objcopy" }); - - if (self.only_section) |only_section| { - try argv.appendSlice(&.{ "-j", only_section }); - } - if (self.pad_to) |pad_to| { - try argv.appendSlice(&.{ "--pad-to", b.fmt("{d}", .{pad_to}) }); - } - if (self.format) |format| switch (format) { - .bin => try argv.appendSlice(&.{ "-O", "binary" }), - .hex => try argv.appendSlice(&.{ "-O", "hex" }), - }; - - try argv.appendSlice(&.{ full_src_path, full_dest_path }); - - try argv.append("--listen=-"); - _ = try step.evalZigProcess(argv.items, prog_node); - - self.output_file.path = full_dest_path; - try man.writeManifest(); -} diff --git a/lib/std/Build/OptionsStep.zig b/lib/std/Build/OptionsStep.zig deleted file mode 100644 index a0e72e3695f94a61b7d2003c1a1370b9a7890f3d..0000000000000000000000000000000000000000 --- a/lib/std/Build/OptionsStep.zig +++ /dev/null @@ -1,421 +0,0 @@ -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, - -contents: std.ArrayList(u8), -artifact_args: std.ArrayList(OptionArtifactArg), -file_source_args: std.ArrayList(OptionFileSourceArg), - -pub fn create(owner: *std.Build) *OptionsStep { - const self = owner.allocator.create(OptionsStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = base_id, - .name = "options", - .owner = owner, - .makeFn = make, - }), - .generated_file = undefined, - .contents = std.ArrayList(u8).init(owner.allocator), - .artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator), - .file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.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.step.owner), - }) 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.step.owner.dupe(name), .artifact = artifact }) catch @panic("OOM"); - self.step.dependOn(&artifact.step); -} - -pub fn createModule(self: *OptionsStep) *std.Build.Module { - return self.step.owner.createModule(.{ - .source_file = self.getSource(), - .dependencies = &.{}, - }); -} - -pub fn getSource(self: *OptionsStep) FileSource { - return .{ .generated = &self.generated_file }; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - // This step completes so quickly that no progress is necessary. - _ = prog_node; - - const b = step.owner; - const self = @fieldParentPtr(OptionsStep, "step", step); - - for (self.artifact_args.items) |item| { - self.addOption( - []const u8, - item.name, - b.pathFromRoot(item.artifact.getOutputSource().getPath(b)), - ); - } - - for (self.file_source_args.items) |item| { - self.addOption( - []const u8, - item.name, - item.source.getPath(b), - ); - } - - const basename = "options.zig"; - - // Hash contents to file name. - var hash = b.cache.hash; - // Random bytes to make unique. Refresh this with new random bytes when - // implementation is modified in a non-backwards-compatible way. - hash.add(@as(u32, 0x38845ef8)); - hash.addBytes(self.contents.items); - const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename; - - self.generated_file.path = try b.cache_root.join(b.allocator, &.{sub_path}); - - // Optimize for the hot path. Stat the file, and if it already exists, - // cache hit. - if (b.cache_root.handle.access(sub_path, .{})) |_| { - // This is the hot path, success. - step.result_cached = true; - return; - } else |outer_err| switch (outer_err) { - error.FileNotFound => { - const sub_dirname = fs.path.dirname(sub_path).?; - b.cache_root.handle.makePath(sub_dirname) catch |e| { - return step.fail("unable to make path '{}{s}': {s}", .{ - b.cache_root, sub_dirname, @errorName(e), - }); - }; - - const rand_int = std.crypto.random.int(u64); - const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ - std.Build.hex64(rand_int) ++ fs.path.sep_str ++ - basename; - const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?; - - b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| { - return step.fail("unable to make temporary directory '{}{s}': {s}", .{ - b.cache_root, tmp_sub_path_dirname, @errorName(err), - }); - }; - - b.cache_root.handle.writeFile(tmp_sub_path, self.contents.items) catch |err| { - return step.fail("unable to write options to '{}{s}': {s}", .{ - b.cache_root, tmp_sub_path, @errorName(err), - }); - }; - - b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) { - error.PathAlreadyExists => { - // Other process beat us to it. Clean up the temp file. - b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| { - try step.addError("warning: unable to delete temp file '{}{s}': {s}", .{ - b.cache_root, tmp_sub_path, @errorName(e), - }); - }; - step.result_cached = true; - return; - }, - else => { - return step.fail("unable to rename options from '{}{s}' to '{}{s}': {s}", .{ - b.cache_root, tmp_sub_path, - b.cache_root, sub_path, - @errorName(err), - }); - }, - }; - }, - else => |e| return step.fail("unable to access options file '{}{s}': {s}", .{ - b.cache_root, sub_path, @errorName(e), - }), - } -} - -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 cache: std.Build.Cache = .{ - .gpa = arena.allocator(), - .manifest_dir = std.fs.cwd(), - }; - - var builder = try std.Build.create( - arena.allocator(), - "test", - .{ .path = "test", .handle = std.fs.cwd() }, - .{ .path = "test", .handle = std.fs.cwd() }, - .{ .path = "test", .handle = std.fs.cwd() }, - host, - &cache, - ); - 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.Ast.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(0), .zig); -} diff --git a/lib/std/Build/RemoveDirStep.zig b/lib/std/Build/RemoveDirStep.zig deleted file mode 100644 index a5bf3c32565d27912a1fb57779ca17de80cab776..0000000000000000000000000000000000000000 --- a/lib/std/Build/RemoveDirStep.zig +++ /dev/null @@ -1,42 +0,0 @@ -const std = @import("../std.zig"); -const fs = std.fs; -const Step = std.Build.Step; -const RemoveDirStep = @This(); - -pub const base_id = .remove_dir; - -step: Step, -dir_path: []const u8, - -pub fn init(owner: *std.Build, dir_path: []const u8) RemoveDirStep { - return RemoveDirStep{ - .step = Step.init(.{ - .id = .remove_dir, - .name = owner.fmt("RemoveDir {s}", .{dir_path}), - .owner = owner, - .makeFn = make, - }), - .dir_path = owner.dupePath(dir_path), - }; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - // TODO update progress node while walking file system. - // Should the standard library support this use case?? - _ = prog_node; - - const b = step.owner; - const self = @fieldParentPtr(RemoveDirStep, "step", step); - - b.build_root.handle.deleteTree(self.dir_path) catch |err| { - if (b.build_root.path) |base| { - return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{ - base, self.dir_path, @errorName(err), - }); - } else { - return step.fail("unable to recursively delete path '{s}': {s}", .{ - self.dir_path, @errorName(err), - }); - } - }; -} diff --git a/lib/std/Build/RunStep.zig b/lib/std/Build/RunStep.zig deleted file mode 100644 index 5d530c7a25501828773a7d5512f3e4fcfb36f12f..0000000000000000000000000000000000000000 --- a/lib/std/Build/RunStep.zig +++ /dev/null @@ -1,1254 +0,0 @@ -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const Step = std.Build.Step; -const CompileStep = std.Build.CompileStep; -const WriteFileStep = std.Build.WriteFileStep; -const fs = std.fs; -const mem = std.mem; -const process = std.process; -const ArrayList = std.ArrayList; -const EnvMap = process.EnvMap; -const Allocator = mem.Allocator; -const ExecError = std.Build.ExecError; -const assert = std.debug.assert; - -const RunStep = @This(); - -pub const base_id: Step.Id = .run; - -step: Step, - -/// See also addArg and addArgs to modifying this directly -argv: ArrayList(Arg), - -/// Set this to modify the current working directory -/// TODO change this to a Build.Cache.Directory to better integrate with -/// future child process cwd API. -cwd: ?[]const u8, - -/// Override this field to modify the environment, or use setEnvironmentVariable -env_map: ?*EnvMap, - -/// Configures whether the RunStep is considered to have side-effects, and also -/// whether the RunStep will inherit stdio streams, forwarding them to the -/// parent process, in which case will require a global lock to prevent other -/// steps from interfering with stdio while the subprocess associated with this -/// RunStep is running. -/// If the RunStep is determined to not have side-effects, then execution will -/// be skipped if all output files are up-to-date and input files are -/// unchanged. -stdio: StdIo = .infer_from_args, -/// This field must be `null` if stdio is `inherit`. -stdin: ?[]const u8 = null, - -/// Additional file paths relative to build.zig that, when modified, indicate -/// that the RunStep should be re-executed. -/// If the RunStep is determined to have side-effects, this field is ignored -/// and the RunStep is always executed when it appears in the build graph. -extra_file_dependencies: []const []const u8 = &.{}, - -/// After adding an output argument, this step will by default rename itself -/// for a better display name in the build summary. -/// This can be disabled by setting this to false. -rename_step_with_output_arg: bool = true, - -/// If this is true, a RunStep which is configured to check the output of the -/// executed binary will not fail the build if the binary cannot be executed -/// due to being for a foreign binary to the host system which is running the -/// build graph. -/// Command-line arguments such as -fqemu and -fwasmtime may affect whether a -/// binary is detected as foreign, as well as system configuration such as -/// Rosetta (macOS) and binfmt_misc (Linux). -/// If this RunStep is considered to have side-effects, then this flag does -/// nothing. -skip_foreign_checks: bool = false, - -/// If stderr or stdout exceeds this amount, the child process is killed and -/// the step fails. -max_stdio_size: usize = 10 * 1024 * 1024, - -captured_stdout: ?*Output = null, -captured_stderr: ?*Output = null, - -has_side_effects: bool = false, - -pub const StdIo = union(enum) { - /// Whether the RunStep has side-effects will be determined by whether or not one - /// of the args is an output file (added with `addOutputFileArg`). - /// If the RunStep is determined to have side-effects, this is the same as `inherit`. - /// The step will fail if the subprocess crashes or returns a non-zero exit code. - infer_from_args, - /// Causes the RunStep to be considered to have side-effects, and therefore - /// always execute when it appears in the build graph. - /// It also means that this step will obtain a global lock to prevent other - /// steps from running in the meantime. - /// The step will fail if the subprocess crashes or returns a non-zero exit code. - inherit, - /// Causes the RunStep to be considered to *not* have side-effects. The - /// process will be re-executed if any of the input dependencies are - /// modified. The exit code and standard I/O streams will be checked for - /// certain conditions, and the step will succeed or fail based on these - /// conditions. - /// Note that an explicit check for exit code 0 needs to be added to this - /// list if such a check is desirable. - check: std.ArrayList(Check), - /// This RunStep is running a zig unit test binary and will communicate - /// extra metadata over the IPC protocol. - zig_test, - - pub const Check = union(enum) { - expect_stderr_exact: []const u8, - expect_stderr_match: []const u8, - expect_stdout_exact: []const u8, - expect_stdout_match: []const u8, - expect_term: std.process.Child.Term, - }; -}; - -pub const Arg = union(enum) { - artifact: *CompileStep, - file_source: std.Build.FileSource, - directory_source: std.Build.FileSource, - bytes: []u8, - output: *Output, -}; - -pub const Output = struct { - generated_file: std.Build.GeneratedFile, - prefix: []const u8, - basename: []const u8, -}; - -pub fn create(owner: *std.Build, name: []const u8) *RunStep { - const self = owner.allocator.create(RunStep) catch @panic("OOM"); - self.* = .{ - .step = Step.init(.{ - .id = base_id, - .name = name, - .owner = owner, - .makeFn = make, - }), - .argv = ArrayList(Arg).init(owner.allocator), - .cwd = null, - .env_map = null, - }; - return self; -} - -pub fn setName(self: *RunStep, name: []const u8) void { - self.step.name = name; - self.rename_step_with_output_arg = false; -} - -pub fn enableTestRunnerMode(rs: *RunStep) void { - rs.stdio = .zig_test; - rs.addArgs(&.{"--listen=-"}); -} - -pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { - self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM"); - self.step.dependOn(&artifact.step); -} - -/// This provides file path as a command line argument to the command being -/// run, and returns a FileSource which can be used as inputs to other APIs -/// throughout the build system. -pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource { - return addPrefixedOutputFileArg(rs, "", basename); -} - -pub fn addPrefixedOutputFileArg( - rs: *RunStep, - prefix: []const u8, - basename: []const u8, -) std.Build.FileSource { - const b = rs.step.owner; - - const output = b.allocator.create(Output) catch @panic("OOM"); - output.* = .{ - .prefix = prefix, - .basename = basename, - .generated_file = .{ .step = &rs.step }, - }; - rs.argv.append(.{ .output = output }) catch @panic("OOM"); - - if (rs.rename_step_with_output_arg) { - rs.setName(b.fmt("{s} ({s})", .{ rs.step.name, basename })); - } - - return .{ .generated = &output.generated_file }; -} - -pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { - self.argv.append(.{ - .file_source = file_source.dupe(self.step.owner), - }) catch @panic("OOM"); - file_source.addStepDependencies(&self.step); -} - -pub fn addDirectorySourceArg(self: *RunStep, directory_source: std.Build.FileSource) void { - self.argv.append(.{ - .directory_source = directory_source.dupe(self.step.owner), - }) catch @panic("OOM"); - directory_source.addStepDependencies(&self.step); -} - -pub fn addArg(self: *RunStep, arg: []const u8) void { - self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM"); -} - -pub fn addArgs(self: *RunStep, args: []const []const u8) void { - for (args) |arg| { - self.addArg(arg); - } -} - -pub fn clearEnvironment(self: *RunStep) void { - const b = self.step.owner; - const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM"); - new_env_map.* = EnvMap.init(b.allocator); - self.env_map = new_env_map; -} - -pub fn addPathDir(self: *RunStep, search_path: []const u8) void { - const b = self.step.owner; - const env_map = getEnvMapInternal(self); - - const key = "PATH"; - var prev_path = env_map.get(key); - - if (prev_path) |pp| { - const new_path = b.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); - env_map.put(key, new_path) catch @panic("OOM"); - } else { - env_map.put(key, b.dupePath(search_path)) catch @panic("OOM"); - } -} - -pub fn getEnvMap(self: *RunStep) *EnvMap { - return getEnvMapInternal(self); -} - -fn getEnvMapInternal(self: *RunStep) *EnvMap { - const arena = self.step.owner.allocator; - return self.env_map orelse { - const env_map = arena.create(EnvMap) catch @panic("OOM"); - env_map.* = process.getEnvMap(arena) catch @panic("unhandled error"); - self.env_map = env_map; - return env_map; - }; -} - -pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void { - const b = self.step.owner; - const env_map = self.getEnvMap(); - env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); -} - -pub fn removeEnvironmentVariable(self: *RunStep, key: []const u8) void { - self.getEnvMap().remove(key); -} - -/// Adds a check for exact stderr match. Does not add any other checks. -pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { - const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) }; - self.addCheck(new_check); -} - -/// Adds a check for exact stdout match as well as a check for exit code 0, if -/// there is not already an expected termination check. -pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { - const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) }; - self.addCheck(new_check); - if (!self.hasTermCheck()) { - self.expectExitCode(0); - } -} - -pub fn expectExitCode(self: *RunStep, code: u8) void { - const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } }; - self.addCheck(new_check); -} - -pub fn hasTermCheck(self: RunStep) bool { - for (self.stdio.check.items) |check| switch (check) { - .expect_term => return true, - else => continue, - }; - return false; -} - -pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void { - switch (self.stdio) { - .infer_from_args => { - self.stdio = .{ .check = std.ArrayList(StdIo.Check).init(self.step.owner.allocator) }; - self.stdio.check.append(new_check) catch @panic("OOM"); - }, - .check => |*checks| checks.append(new_check) catch @panic("OOM"), - else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"), - } -} - -pub fn captureStdErr(self: *RunStep) std.Build.FileSource { - assert(self.stdio != .inherit); - - if (self.captured_stderr) |output| return .{ .generated = &output.generated_file }; - - const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); - output.* = .{ - .prefix = "", - .basename = "stderr", - .generated_file = .{ .step = &self.step }, - }; - self.captured_stderr = output; - return .{ .generated = &output.generated_file }; -} - -pub fn captureStdOut(self: *RunStep) std.Build.FileSource { - assert(self.stdio != .inherit); - - if (self.captured_stdout) |output| return .{ .generated = &output.generated_file }; - - const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); - output.* = .{ - .prefix = "", - .basename = "stdout", - .generated_file = .{ .step = &self.step }, - }; - self.captured_stdout = output; - return .{ .generated = &output.generated_file }; -} - -/// Returns whether the RunStep has side effects *other than* updating the output arguments. -fn hasSideEffects(self: RunStep) bool { - if (self.has_side_effects) return true; - return switch (self.stdio) { - .infer_from_args => !self.hasAnyOutputArgs(), - .inherit => true, - .check => false, - .zig_test => false, - }; -} - -fn hasAnyOutputArgs(self: RunStep) bool { - if (self.captured_stdout != null) return true; - if (self.captured_stderr != null) return true; - for (self.argv.items) |arg| switch (arg) { - .output => return true, - else => continue, - }; - return false; -} - -fn checksContainStdout(checks: []const StdIo.Check) bool { - for (checks) |check| switch (check) { - .expect_stderr_exact, - .expect_stderr_match, - .expect_term, - => continue, - - .expect_stdout_exact, - .expect_stdout_match, - => return true, - }; - return false; -} - -fn checksContainStderr(checks: []const StdIo.Check) bool { - for (checks) |check| switch (check) { - .expect_stdout_exact, - .expect_stdout_match, - .expect_term, - => continue, - - .expect_stderr_exact, - .expect_stderr_match, - => return true, - }; - return false; -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - const b = step.owner; - const arena = b.allocator; - const self = @fieldParentPtr(RunStep, "step", step); - const has_side_effects = self.hasSideEffects(); - - var argv_list = ArrayList([]const u8).init(arena); - var output_placeholders = ArrayList(struct { - index: usize, - output: *Output, - }).init(arena); - - var man = b.cache.obtain(); - defer man.deinit(); - - for (self.argv.items) |arg| { - switch (arg) { - .bytes => |bytes| { - try argv_list.append(bytes); - man.hash.addBytes(bytes); - }, - .file_source => |file| { - const file_path = file.getPath(b); - try argv_list.append(file_path); - _ = try man.addFile(file_path, null); - }, - .directory_source => |file| { - const file_path = file.getPath(b); - try argv_list.append(file_path); - man.hash.addBytes(file_path); - }, - .artifact => |artifact| { - if (artifact.target.isWindows()) { - // On Windows we don't have rpaths so we have to add .dll search paths to PATH - self.addPathForDynLibs(artifact); - } - const file_path = artifact.installed_path orelse - artifact.getOutputSource().getPath(b); - - try argv_list.append(file_path); - - _ = try man.addFile(file_path, null); - }, - .output => |output| { - man.hash.addBytes(output.prefix); - man.hash.addBytes(output.basename); - // Add a placeholder into the argument list because we need the - // manifest hash to be updated with all arguments before the - // object directory is computed. - try argv_list.append(""); - try output_placeholders.append(.{ - .index = argv_list.items.len - 1, - .output = output, - }); - }, - } - } - - if (self.captured_stdout) |output| { - man.hash.addBytes(output.basename); - } - - if (self.captured_stderr) |output| { - man.hash.addBytes(output.basename); - } - - hashStdIo(&man.hash, self.stdio); - - if (has_side_effects) { - try runCommand(self, argv_list.items, has_side_effects, null, prog_node); - return; - } - - for (self.extra_file_dependencies) |file_path| { - _ = try man.addFile(b.pathFromRoot(file_path), null); - } - - if (try step.cacheHit(&man)) { - // cache hit, skip running command - const digest = man.final(); - for (output_placeholders.items) |placeholder| { - placeholder.output.generated_file.path = try b.cache_root.join(arena, &.{ - "o", &digest, placeholder.output.basename, - }); - } - - if (self.captured_stdout) |output| { - output.generated_file.path = try b.cache_root.join(arena, &.{ - "o", &digest, output.basename, - }); - } - - if (self.captured_stderr) |output| { - output.generated_file.path = try b.cache_root.join(arena, &.{ - "o", &digest, output.basename, - }); - } - - step.result_cached = true; - return; - } - - const digest = man.final(); - - for (output_placeholders.items) |placeholder| { - const output_components = .{ "o", &digest, placeholder.output.basename }; - const output_sub_path = try fs.path.join(arena, &output_components); - const output_sub_dir_path = fs.path.dirname(output_sub_path).?; - b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { - return step.fail("unable to make path '{}{s}': {s}", .{ - b.cache_root, output_sub_dir_path, @errorName(err), - }); - }; - const output_path = try b.cache_root.join(arena, &output_components); - placeholder.output.generated_file.path = output_path; - const cli_arg = if (placeholder.output.prefix.len == 0) - output_path - else - b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path }); - argv_list.items[placeholder.index] = cli_arg; - } - - try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node); - - try step.writeManifest(&man); -} - -fn formatTerm( - term: ?std.process.Child.Term, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, -) !void { - _ = fmt; - _ = options; - if (term) |t| switch (t) { - .Exited => |code| try writer.print("exited with code {}", .{code}), - .Signal => |sig| try writer.print("terminated with signal {}", .{sig}), - .Stopped => |sig| try writer.print("stopped with signal {}", .{sig}), - .Unknown => |code| try writer.print("terminated for unknown reason with code {}", .{code}), - } else { - try writer.writeAll("exited with any code"); - } -} -fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) { - return .{ .data = term }; -} - -fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool { - return if (expected) |e| switch (e) { - .Exited => |expected_code| switch (actual) { - .Exited => |actual_code| expected_code == actual_code, - else => false, - }, - .Signal => |expected_sig| switch (actual) { - .Signal => |actual_sig| expected_sig == actual_sig, - else => false, - }, - .Stopped => |expected_sig| switch (actual) { - .Stopped => |actual_sig| expected_sig == actual_sig, - else => false, - }, - .Unknown => |expected_code| switch (actual) { - .Unknown => |actual_code| expected_code == actual_code, - else => false, - }, - } else switch (actual) { - .Exited => true, - else => false, - }; -} - -fn runCommand( - self: *RunStep, - argv: []const []const u8, - has_side_effects: bool, - digest: ?*const [std.Build.Cache.hex_digest_len]u8, - prog_node: *std.Progress.Node, -) !void { - const step = &self.step; - const b = step.owner; - const arena = b.allocator; - - try step.handleChildProcUnsupported(self.cwd, argv); - try Step.handleVerbose2(step.owner, self.cwd, self.env_map, argv); - - const allow_skip = switch (self.stdio) { - .check, .zig_test => self.skip_foreign_checks, - else => false, - }; - - var interp_argv = std.ArrayList([]const u8).init(b.allocator); - defer interp_argv.deinit(); - - const result = spawnChildAndCollect(self, argv, has_side_effects, prog_node) catch |err| term: { - // InvalidExe: cpu arch mismatch - // FileNotFound: can happen with a wrong dynamic linker path - if (err == error.InvalidExe or err == error.FileNotFound) interpret: { - // TODO: learn the target from the binary directly rather than from - // relying on it being a CompileStep. This will make this logic - // work even for the edge case that the binary was produced by a - // third party. - const exe = switch (self.argv.items[0]) { - .artifact => |exe| exe, - else => break :interpret, - }; - switch (exe.kind) { - .exe, .@"test" => {}, - else => break :interpret, - } - - const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc; - switch (b.host.getExternalExecutor(exe.target_info, .{ - .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, - .link_libc = exe.is_linking_libc, - })) { - .native, .rosetta => { - if (allow_skip) return error.MakeSkipped; - break :interpret; - }, - .wine => |bin_name| { - if (b.enable_wine) { - try interp_argv.append(bin_name); - try interp_argv.appendSlice(argv); - } else { - return failForeign(self, "-fwine", argv[0], exe); - } - }, - .qemu => |bin_name| { - if (b.enable_qemu) { - const glibc_dir_arg = if (need_cross_glibc) - b.glibc_runtimes_dir orelse - return failForeign(self, "--glibc-runtimes", argv[0], exe) - else - null; - - try interp_argv.append(bin_name); - - if (glibc_dir_arg) |dir| { - // TODO look into making this a call to `linuxTriple`. This - // needs the directory to be called "i686" rather than - // "x86" which is why we do it manually here. - const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; - const cpu_arch = exe.target.getCpuArch(); - const os_tag = exe.target.getOsTag(); - const abi = exe.target.getAbi(); - const cpu_arch_name: []const u8 = if (cpu_arch == .x86) - "i686" - else - @tagName(cpu_arch); - const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{ - dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), - }); - - try interp_argv.append("-L"); - try interp_argv.append(full_dir); - } - - try interp_argv.appendSlice(argv); - } else { - return failForeign(self, "-fqemu", argv[0], exe); - } - }, - .darling => |bin_name| { - if (b.enable_darling) { - try interp_argv.append(bin_name); - try interp_argv.appendSlice(argv); - } else { - return failForeign(self, "-fdarling", argv[0], exe); - } - }, - .wasmtime => |bin_name| { - if (b.enable_wasmtime) { - try interp_argv.append(bin_name); - try interp_argv.append("--dir=."); - try interp_argv.append(argv[0]); - try interp_argv.append("--"); - try interp_argv.appendSlice(argv[1..]); - } else { - return failForeign(self, "-fwasmtime", argv[0], exe); - } - }, - .bad_dl => |foreign_dl| { - if (allow_skip) return error.MakeSkipped; - - const host_dl = b.host.dynamic_linker.get() orelse "(none)"; - - return step.fail( - \\the host system is unable to execute binaries from the target - \\ because the host dynamic linker is '{s}', - \\ while the target dynamic linker is '{s}'. - \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step - , .{ host_dl, foreign_dl }); - }, - .bad_os_or_cpu => { - if (allow_skip) return error.MakeSkipped; - - const host_name = try b.host.target.zigTriple(b.allocator); - const foreign_name = try exe.target.zigTriple(b.allocator); - - return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{ - host_name, foreign_name, - }); - }, - } - - if (exe.target.isWindows()) { - // On Windows we don't have rpaths so we have to add .dll search paths to PATH - self.addPathForDynLibs(exe); - } - - try Step.handleVerbose2(step.owner, self.cwd, self.env_map, interp_argv.items); - - break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| { - return step.fail("unable to spawn interpreter {s}: {s}", .{ - interp_argv.items[0], @errorName(e), - }); - }; - } - - return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); - }; - - step.result_duration_ns = result.elapsed_ns; - step.result_peak_rss = result.peak_rss; - step.test_results = result.stdio.test_results; - - // Capture stdout and stderr to GeneratedFile objects. - const Stream = struct { - captured: ?*Output, - is_null: bool, - bytes: []const u8, - }; - for ([_]Stream{ - .{ - .captured = self.captured_stdout, - .is_null = result.stdio.stdout_null, - .bytes = result.stdio.stdout, - }, - .{ - .captured = self.captured_stderr, - .is_null = result.stdio.stderr_null, - .bytes = result.stdio.stderr, - }, - }) |stream| { - if (stream.captured) |output| { - assert(!stream.is_null); - - const output_components = .{ "o", digest.?, output.basename }; - const output_path = try b.cache_root.join(arena, &output_components); - output.generated_file.path = output_path; - - const sub_path = try fs.path.join(arena, &output_components); - const sub_path_dirname = fs.path.dirname(sub_path).?; - b.cache_root.handle.makePath(sub_path_dirname) catch |err| { - return step.fail("unable to make path '{}{s}': {s}", .{ - b.cache_root, sub_path_dirname, @errorName(err), - }); - }; - b.cache_root.handle.writeFile(sub_path, stream.bytes) catch |err| { - return step.fail("unable to write file '{}{s}': {s}", .{ - b.cache_root, sub_path, @errorName(err), - }); - }; - } - } - - const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; - - switch (self.stdio) { - .check => |checks| for (checks.items) |check| switch (check) { - .expect_stderr_exact => |expected_bytes| { - assert(!result.stdio.stderr_null); - if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) { - return step.fail( - \\ - \\========= expected this stderr: ========= - \\{s} - \\========= but found: ==================== - \\{s} - \\========= from the following command: === - \\{s} - , .{ - expected_bytes, - result.stdio.stderr, - try Step.allocPrintCmd(arena, self.cwd, final_argv), - }); - } - }, - .expect_stderr_match => |match| { - assert(!result.stdio.stderr_null); - if (mem.indexOf(u8, result.stdio.stderr, match) == null) { - return step.fail( - \\ - \\========= expected to find in stderr: ========= - \\{s} - \\========= but stderr does not contain it: ===== - \\{s} - \\========= from the following command: ========= - \\{s} - , .{ - match, - result.stdio.stderr, - try Step.allocPrintCmd(arena, self.cwd, final_argv), - }); - } - }, - .expect_stdout_exact => |expected_bytes| { - assert(!result.stdio.stdout_null); - if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) { - return step.fail( - \\ - \\========= expected this stdout: ========= - \\{s} - \\========= but found: ==================== - \\{s} - \\========= from the following command: === - \\{s} - , .{ - expected_bytes, - result.stdio.stdout, - try Step.allocPrintCmd(arena, self.cwd, final_argv), - }); - } - }, - .expect_stdout_match => |match| { - assert(!result.stdio.stdout_null); - if (mem.indexOf(u8, result.stdio.stdout, match) == null) { - return step.fail( - \\ - \\========= expected to find in stdout: ========= - \\{s} - \\========= but stdout does not contain it: ===== - \\{s} - \\========= from the following command: ========= - \\{s} - , .{ - match, - result.stdio.stdout, - try Step.allocPrintCmd(arena, self.cwd, final_argv), - }); - } - }, - .expect_term => |expected_term| { - if (!termMatches(expected_term, result.term)) { - return step.fail("the following command {} (expected {}):\n{s}", .{ - fmtTerm(result.term), - fmtTerm(expected_term), - try Step.allocPrintCmd(arena, self.cwd, final_argv), - }); - } - }, - }, - .zig_test => { - const prefix: []const u8 = p: { - if (result.stdio.test_metadata) |tm| { - if (tm.next_index <= tm.names.len) { - const name = tm.testName(tm.next_index - 1); - break :p b.fmt("while executing test '{s}', ", .{name}); - } - } - break :p ""; - }; - const expected_term: std.process.Child.Term = .{ .Exited = 0 }; - if (!termMatches(expected_term, result.term)) { - return step.fail("{s}the following command {} (expected {}):\n{s}", .{ - prefix, - fmtTerm(result.term), - fmtTerm(expected_term), - try Step.allocPrintCmd(arena, self.cwd, final_argv), - }); - } - if (!result.stdio.test_results.isSuccess()) { - return step.fail( - "{s}the following test command failed:\n{s}", - .{ prefix, try Step.allocPrintCmd(arena, self.cwd, final_argv) }, - ); - } - }, - else => { - try step.handleChildProcessTerm(result.term, self.cwd, final_argv); - }, - } -} - -const ChildProcResult = struct { - term: std.process.Child.Term, - elapsed_ns: u64, - peak_rss: usize, - - stdio: StdIoResult, -}; - -fn spawnChildAndCollect( - self: *RunStep, - argv: []const []const u8, - has_side_effects: bool, - prog_node: *std.Progress.Node, -) !ChildProcResult { - const b = self.step.owner; - const arena = b.allocator; - - var child = std.process.Child.init(argv, arena); - if (self.cwd) |cwd| { - child.cwd = b.pathFromRoot(cwd); - } else { - child.cwd = b.build_root.path; - child.cwd_dir = b.build_root.handle; - } - child.env_map = self.env_map orelse b.env_map; - child.request_resource_usage_statistics = true; - - child.stdin_behavior = switch (self.stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Ignore, - .inherit => .Inherit, - .check => .Ignore, - .zig_test => .Pipe, - }; - child.stdout_behavior = switch (self.stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Ignore, - .inherit => .Inherit, - .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore, - .zig_test => .Pipe, - }; - child.stderr_behavior = switch (self.stdio) { - .infer_from_args => if (has_side_effects) .Inherit else .Pipe, - .inherit => .Inherit, - .check => .Pipe, - .zig_test => .Pipe, - }; - if (self.captured_stdout != null) child.stdout_behavior = .Pipe; - if (self.captured_stderr != null) child.stderr_behavior = .Pipe; - if (self.stdin != null) { - assert(child.stdin_behavior != .Inherit); - child.stdin_behavior = .Pipe; - } - - try child.spawn(); - var timer = try std.time.Timer.start(); - - const result = if (self.stdio == .zig_test) - evalZigTest(self, &child, prog_node) - else - evalGeneric(self, &child); - - const term = try child.wait(); - const elapsed_ns = timer.read(); - - return .{ - .stdio = try result, - .term = term, - .elapsed_ns = elapsed_ns, - .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, - }; -} - -const StdIoResult = struct { - // These use boolean flags instead of optionals as a workaround for - // https://github.com/ziglang/zig/issues/14783 - stdout: []const u8, - stderr: []const u8, - stdout_null: bool, - stderr_null: bool, - test_results: Step.TestResults, - test_metadata: ?TestMetadata, -}; - -fn evalZigTest( - self: *RunStep, - child: *std.process.Child, - prog_node: *std.Progress.Node, -) !StdIoResult { - const gpa = self.step.owner.allocator; - const arena = self.step.owner.allocator; - - var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, - }); - defer poller.deinit(); - - try sendMessage(child.stdin.?, .query_test_metadata); - - const Header = std.zig.Server.Message.Header; - - const stdout = poller.fifo(.stdout); - const stderr = poller.fifo(.stderr); - - var fail_count: u32 = 0; - var skip_count: u32 = 0; - var leak_count: u32 = 0; - var test_count: u32 = 0; - - var metadata: ?TestMetadata = null; - - var sub_prog_node: ?std.Progress.Node = null; - defer if (sub_prog_node) |*n| n.end(); - - poll: while (true) { - while (stdout.readableLength() < @sizeOf(Header)) { - if (!(try poller.poll())) break :poll; - } - const header = stdout.reader().readStruct(Header) catch unreachable; - while (stdout.readableLength() < header.bytes_len) { - if (!(try poller.poll())) break :poll; - } - const body = stdout.readableSliceOfLen(header.bytes_len); - - switch (header.tag) { - .zig_version => { - if (!std.mem.eql(u8, builtin.zig_version_string, body)) { - return self.step.fail( - "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", - .{ builtin.zig_version_string, body }, - ); - } - }, - .test_metadata => { - const TmHdr = std.zig.Server.Message.TestMetadata; - const tm_hdr = @ptrCast(*align(1) const TmHdr, body); - test_count = tm_hdr.tests_len; - - const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)]; - const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)]; - const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)]; - const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len]; - - const names = std.mem.bytesAsSlice(u32, names_bytes); - const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes); - const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes); - const names_aligned = try arena.alloc(u32, names.len); - for (names_aligned, names) |*dest, src| dest.* = src; - - const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len); - for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src; - - const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len); - for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src; - - prog_node.setEstimatedTotalItems(names.len); - metadata = .{ - .string_bytes = try arena.dupe(u8, string_bytes), - .names = names_aligned, - .async_frame_lens = async_frame_lens_aligned, - .expected_panic_msgs = expected_panic_msgs_aligned, - .next_index = 0, - .prog_node = prog_node, - }; - - try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); - }, - .test_results => { - const md = metadata.?; - - const TrHdr = std.zig.Server.Message.TestResults; - const tr_hdr = @ptrCast(*align(1) const TrHdr, body); - fail_count += @boolToInt(tr_hdr.flags.fail); - skip_count += @boolToInt(tr_hdr.flags.skip); - leak_count += @boolToInt(tr_hdr.flags.leak); - - if (tr_hdr.flags.fail or tr_hdr.flags.leak) { - const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0); - const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n"); - const label = if (tr_hdr.flags.fail) "failed" else "leaked"; - if (msg.len > 0) { - try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg }); - } else { - try self.step.addError("'{s}' {s}", .{ name, label }); - } - stderr.discard(msg.len); - } - - try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); - }, - else => {}, // ignore other messages - } - - stdout.discard(body.len); - } - - if (stderr.readableLength() > 0) { - const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n"); - if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg); - } - - // Send EOF to stdin. - child.stdin.?.close(); - child.stdin = null; - - return .{ - .stdout = &.{}, - .stderr = &.{}, - .stdout_null = true, - .stderr_null = true, - .test_results = .{ - .test_count = test_count, - .fail_count = fail_count, - .skip_count = skip_count, - .leak_count = leak_count, - }, - .test_metadata = metadata, - }; -} - -const TestMetadata = struct { - names: []const u32, - async_frame_lens: []const u32, - expected_panic_msgs: []const u32, - string_bytes: []const u8, - next_index: u32, - prog_node: *std.Progress.Node, - - fn testName(tm: TestMetadata, index: u32) []const u8 { - return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); - } -}; - -fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { - while (metadata.next_index < metadata.names.len) { - const i = metadata.next_index; - metadata.next_index += 1; - - if (metadata.async_frame_lens[i] != 0) continue; - if (metadata.expected_panic_msgs[i] != 0) continue; - - const name = metadata.testName(i); - if (sub_prog_node.*) |*n| n.end(); - sub_prog_node.* = metadata.prog_node.start(name, 0); - - try sendRunTestMessage(in, i); - return; - } else { - try sendMessage(in, .exit); - } -} - -fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { - const header: std.zig.Client.Message.Header = .{ - .tag = tag, - .bytes_len = 0, - }; - try file.writeAll(std.mem.asBytes(&header)); -} - -fn sendRunTestMessage(file: std.fs.File, index: u32) !void { - const header: std.zig.Client.Message.Header = .{ - .tag = .run_test, - .bytes_len = 4, - }; - const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index); - try file.writeAll(full_msg); -} - -fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult { - const arena = self.step.owner.allocator; - - if (self.stdin) |stdin| { - child.stdin.?.writeAll(stdin) catch |err| { - return self.step.fail("unable to write stdin: {s}", .{@errorName(err)}); - }; - child.stdin.?.close(); - child.stdin = null; - } - - // These are not optionals, as a workaround for - // https://github.com/ziglang/zig/issues/14783 - var stdout_bytes: []const u8 = undefined; - var stderr_bytes: []const u8 = undefined; - var stdout_null = true; - var stderr_null = true; - - if (child.stdout) |stdout| { - if (child.stderr) |stderr| { - var poller = std.io.poll(arena, enum { stdout, stderr }, .{ - .stdout = stdout, - .stderr = stderr, - }); - defer poller.deinit(); - - while (try poller.poll()) { - if (poller.fifo(.stdout).count > self.max_stdio_size) - return error.StdoutStreamTooLong; - if (poller.fifo(.stderr).count > self.max_stdio_size) - return error.StderrStreamTooLong; - } - - stdout_bytes = try poller.fifo(.stdout).toOwnedSlice(); - stderr_bytes = try poller.fifo(.stderr).toOwnedSlice(); - stdout_null = false; - stderr_null = false; - } else { - stdout_bytes = try stdout.reader().readAllAlloc(arena, self.max_stdio_size); - stdout_null = false; - } - } else if (child.stderr) |stderr| { - stderr_bytes = try stderr.reader().readAllAlloc(arena, self.max_stdio_size); - stderr_null = false; - } - - if (!stderr_null and stderr_bytes.len > 0) { - // Treat stderr as an error message. - const stderr_is_diagnostic = self.captured_stderr == null and switch (self.stdio) { - .check => |checks| !checksContainStderr(checks.items), - else => true, - }; - if (stderr_is_diagnostic) { - try self.step.result_error_msgs.append(arena, stderr_bytes); - } - } - - return .{ - .stdout = stdout_bytes, - .stderr = stderr_bytes, - .stdout_null = stdout_null, - .stderr_null = stderr_null, - .test_results = .{}, - .test_metadata = null, - }; -} - -fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void { - const b = self.step.owner; - for (artifact.link_objects.items) |link_object| { - switch (link_object) { - .other_step => |other| { - if (other.target.isWindows() and other.isDynamicLibrary()) { - addPathDir(self, fs.path.dirname(other.getOutputSource().getPath(b)).?); - addPathForDynLibs(self, other); - } - }, - else => {}, - } - } -} - -fn failForeign( - self: *RunStep, - suggested_flag: []const u8, - argv0: []const u8, - exe: *CompileStep, -) error{ MakeFailed, MakeSkipped, OutOfMemory } { - switch (self.stdio) { - .check, .zig_test => { - if (self.skip_foreign_checks) - return error.MakeSkipped; - - const b = self.step.owner; - const host_name = try b.host.target.zigTriple(b.allocator); - const foreign_name = try exe.target.zigTriple(b.allocator); - - return self.step.fail( - \\unable to spawn foreign binary '{s}' ({s}) on host system ({s}) - \\ consider using {s} or enabling skip_foreign_checks in the Run step - , .{ argv0, foreign_name, host_name, suggested_flag }); - }, - else => { - return self.step.fail("unable to spawn foreign binary '{s}'", .{argv0}); - }, - } -} - -fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { - switch (stdio) { - .infer_from_args, .inherit, .zig_test => {}, - .check => |checks| for (checks.items) |check| { - hh.add(@as(std.meta.Tag(StdIo.Check), check)); - switch (check) { - .expect_stderr_exact, - .expect_stderr_match, - .expect_stdout_exact, - .expect_stdout_match, - => |s| hh.addBytes(s), - - .expect_term => |term| { - hh.add(@as(std.meta.Tag(std.process.Child.Term), term)); - switch (term) { - .Exited => |x| hh.add(x), - .Signal, .Stopped, .Unknown => |x| hh.add(x), - } - }, - } - }, - } -} diff --git a/lib/std/Build/Step/CheckFile.zig b/lib/std/Build/Step/CheckFile.zig new file mode 100644 index 0000000000000000000000000000000000000000..ad8b1a25f04cb3f783ed6d6a6b37647abb0dbf12 --- /dev/null +++ b/lib/std/Build/Step/CheckFile.zig @@ -0,0 +1,87 @@ +//! Fail the build step if a file does not match certain checks. +//! TODO: make this more flexible, supporting more kinds of checks. +//! TODO: generalize the code in std.testing.expectEqualStrings and make this +//! CheckFileStep produce those helpful diagnostics when there is not a match. +const CheckFileStep = @This(); +const std = @import("std"); +const Step = std.Build.Step; +const fs = std.fs; +const mem = std.mem; + +step: Step, +expected_matches: []const []const u8, +expected_exact: ?[]const u8, +source: std.Build.FileSource, +max_bytes: usize = 20 * 1024 * 1024, + +pub const base_id = .check_file; + +pub const Options = struct { + expected_matches: []const []const u8 = &.{}, + expected_exact: ?[]const u8 = null, +}; + +pub fn create( + owner: *std.Build, + source: std.Build.FileSource, + options: Options, +) *CheckFileStep { + const self = owner.allocator.create(CheckFileStep) catch @panic("OOM"); + self.* = .{ + .step = Step.init(.{ + .id = .check_file, + .name = "CheckFile", + .owner = owner, + .makeFn = make, + }), + .source = source.dupe(owner), + .expected_matches = owner.dupeStrings(options.expected_matches), + .expected_exact = options.expected_exact, + }; + self.source.addStepDependencies(&self.step); + return self; +} + +pub fn setName(self: *CheckFileStep, name: []const u8) void { + self.step.name = name; +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const b = step.owner; + const self = @fieldParentPtr(CheckFileStep, "step", step); + + const src_path = self.source.getPath(b); + const contents = fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes) catch |err| { + return step.fail("unable to read '{s}': {s}", .{ + src_path, @errorName(err), + }); + }; + + for (self.expected_matches) |expected_match| { + if (mem.indexOf(u8, contents, expected_match) == null) { + return step.fail( + \\ + \\========= expected to find: =================== + \\{s} + \\========= but file does not contain it: ======= + \\{s} + \\=============================================== + , .{ expected_match, contents }); + } + } + + if (self.expected_exact) |expected_exact| { + if (!mem.eql(u8, expected_exact, contents)) { + return step.fail( + \\ + \\========= expected: ===================== + \\{s} + \\========= but found: ==================== + \\{s} + \\========= from the following file: ====== + \\{s} + , .{ expected_exact, contents, src_path }); + } + } +} diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig new file mode 100644 index 0000000000000000000000000000000000000000..431f74eccc7b672f1bfdab183c080f61e1ba7caf --- /dev/null +++ b/lib/std/Build/Step/CheckObject.zig @@ -0,0 +1,1055 @@ +const std = @import("std"); +const assert = std.debug.assert; +const fs = std.fs; +const macho = std.macho; +const math = std.math; +const mem = std.mem; +const testing = std.testing; + +const CheckObjectStep = @This(); + +const Allocator = mem.Allocator; +const Step = std.Build.Step; + +pub const base_id = .check_object; + +step: Step, +source: std.Build.FileSource, +max_bytes: usize = 20 * 1024 * 1024, +checks: std.ArrayList(Check), +dump_symtab: bool = false, +obj_format: std.Target.ObjectFormat, + +pub fn create( + owner: *std.Build, + source: std.Build.FileSource, + obj_format: std.Target.ObjectFormat, +) *CheckObjectStep { + const gpa = owner.allocator; + const self = gpa.create(CheckObjectStep) catch @panic("OOM"); + self.* = .{ + .step = Step.init(.{ + .id = .check_file, + .name = "CheckObject", + .owner = owner, + .makeFn = make, + }), + .source = source.dupe(owner), + .checks = std.ArrayList(Check).init(gpa), + .obj_format = obj_format, + }; + self.source.addStepDependencies(&self.step); + return self; +} + +/// Runs and (optionally) compares the output of a binary. +/// Asserts `self` was generated from an executable step. +/// TODO this doesn't actually compare, and there's no apparent reason for it +/// to depend on the check object step. I don't see why this function should exist, +/// the caller could just add the run step directly. +pub fn runAndCompare(self: *CheckObjectStep) *std.Build.RunStep { + const dependencies_len = self.step.dependencies.items.len; + assert(dependencies_len > 0); + const exe_step = self.step.dependencies.items[dependencies_len - 1]; + const exe = exe_step.cast(std.Build.CompileStep).?; + const run = self.step.owner.addRunArtifact(exe); + run.skip_foreign_checks = true; + run.step.dependOn(&self.step); + return run; +} + +const SearchPhrase = struct { + string: []const u8, + file_source: ?std.Build.FileSource = null, + + fn resolve(phrase: SearchPhrase, b: *std.Build, step: *Step) []const u8 { + const file_source = phrase.file_source orelse return phrase.string; + return b.fmt("{s} {s}", .{ phrase.string, file_source.getPath2(b, step) }); + } +}; + +/// There two types of actions currently supported: +/// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}` +/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature +/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use +/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. +/// it should be plenty useful in its current form. +/// * `.compute_cmp` - can be used to perform an operation on the extracted global variables +/// using the MatchAction. It currently only supports an addition. The operation is required +/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, +/// to avoid any parsing really). +/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively +/// they could then be added with this simple program `vmaddr entryoff +`. +const Action = struct { + tag: enum { match, not_present, compute_cmp }, + phrase: SearchPhrase, + expected: ?ComputeCompareExpected = null, + + /// Will return true if the `phrase` was found in the `haystack`. + /// Some examples include: + /// + /// LC 0 => will match in its entirety + /// vmaddr {vmaddr} => will match `vmaddr` and then extract the following value as u64 + /// and save under `vmaddr` global name (see `global_vars` param) + /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` + /// in that order with other letters in between + fn match( + act: Action, + b: *std.Build, + step: *Step, + haystack: []const u8, + global_vars: anytype, + ) !bool { + assert(act.tag == .match or act.tag == .not_present); + const phrase = act.phrase.resolve(b, step); + var candidate_var: ?struct { name: []const u8, value: u64 } = null; + var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); + var needle_it = mem.tokenize(u8, mem.trim(u8, phrase, " "), " "); + + while (needle_it.next()) |needle_tok| { + const hay_tok = hay_it.next() orelse return false; + + if (mem.indexOf(u8, needle_tok, "{*}")) |index| { + // We have fuzzy matchers within the search pattern, so we match substrings. + var start = index; + var n_tok = needle_tok; + var h_tok = hay_tok; + while (true) { + n_tok = n_tok[start + 3 ..]; + const inner = if (mem.indexOf(u8, n_tok, "{*}")) |sub_end| + n_tok[0..sub_end] + else + n_tok; + if (mem.indexOf(u8, h_tok, inner) == null) return false; + start = mem.indexOf(u8, n_tok, "{*}") orelse break; + } + } else if (mem.startsWith(u8, needle_tok, "{")) { + const closing_brace = mem.indexOf(u8, needle_tok, "}") orelse return error.MissingClosingBrace; + if (closing_brace != needle_tok.len - 1) return error.ClosingBraceNotLast; + + const name = needle_tok[1..closing_brace]; + if (name.len == 0) return error.MissingBraceValue; + const value = try std.fmt.parseInt(u64, hay_tok, 16); + candidate_var = .{ + .name = name, + .value = value, + }; + } else { + if (!mem.eql(u8, hay_tok, needle_tok)) return false; + } + } + + if (candidate_var) |v| { + try global_vars.putNoClobber(v.name, v.value); + } + + return true; + } + + /// Will return true if the `phrase` is correctly parsed into an RPN program and + /// its reduced, computed value compares using `op` with the expected value, either + /// a literal or another extracted variable. + fn computeCmp(act: Action, b: *std.Build, step: *Step, global_vars: anytype) !bool { + const gpa = step.owner.allocator; + const phrase = act.phrase.resolve(b, step); + var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa); + var values = std.ArrayList(u64).init(gpa); + + var it = mem.tokenize(u8, phrase, " "); + while (it.next()) |next| { + if (mem.eql(u8, next, "+")) { + try op_stack.append(.add); + } else if (mem.eql(u8, next, "-")) { + try op_stack.append(.sub); + } else if (mem.eql(u8, next, "%")) { + try op_stack.append(.mod); + } else if (mem.eql(u8, next, "*")) { + try op_stack.append(.mul); + } else { + const val = std.fmt.parseInt(u64, next, 0) catch blk: { + break :blk global_vars.get(next) orelse { + try step.addError( + \\ + \\========= variable was not extracted: =========== + \\{s} + \\================================================= + , .{next}); + return error.UnknownVariable; + }; + }; + try values.append(val); + } + } + + var op_i: usize = 1; + var reduced: u64 = values.items[0]; + for (op_stack.items) |op| { + const other = values.items[op_i]; + switch (op) { + .add => { + reduced += other; + }, + .sub => { + reduced -= other; + }, + .mod => { + reduced %= other; + }, + .mul => { + reduced *= other; + }, + } + op_i += 1; + } + + const exp_value = switch (act.expected.?.value) { + .variable => |name| global_vars.get(name) orelse { + try step.addError( + \\ + \\========= variable was not extracted: =========== + \\{s} + \\================================================= + , .{name}); + return error.UnknownVariable; + }, + .literal => |x| x, + }; + return math.compare(reduced, act.expected.?.op, exp_value); + } +}; + +const ComputeCompareExpected = struct { + op: math.CompareOperator, + value: union(enum) { + variable: []const u8, + literal: u64, + }, + + pub fn format( + value: @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + if (fmt.len != 0) std.fmt.invalidFmtError(fmt, value); + _ = options; + try writer.print("{s} ", .{@tagName(value.op)}); + switch (value.value) { + .variable => |name| try writer.writeAll(name), + .literal => |x| try writer.print("{x}", .{x}), + } + } +}; + +const Check = struct { + actions: std.ArrayList(Action), + + fn create(allocator: Allocator) Check { + return .{ + .actions = std.ArrayList(Action).init(allocator), + }; + } + + fn match(self: *Check, phrase: SearchPhrase) void { + self.actions.append(.{ + .tag = .match, + .phrase = phrase, + }) catch @panic("OOM"); + } + + fn notPresent(self: *Check, phrase: SearchPhrase) void { + self.actions.append(.{ + .tag = .not_present, + .phrase = phrase, + }) catch @panic("OOM"); + } + + fn computeCmp(self: *Check, phrase: SearchPhrase, expected: ComputeCompareExpected) void { + self.actions.append(.{ + .tag = .compute_cmp, + .phrase = phrase, + .expected = expected, + }) catch @panic("OOM"); + } +}; + +/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. +pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void { + var new_check = Check.create(self.step.owner.allocator); + new_check.match(.{ .string = self.step.owner.dupe(phrase) }); + self.checks.append(new_check) catch @panic("OOM"); +} + +/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`. +/// Asserts at least one check already exists. +pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { + assert(self.checks.items.len > 0); + const last = &self.checks.items[self.checks.items.len - 1]; + last.match(.{ .string = self.step.owner.dupe(phrase) }); +} + +/// Like `checkNext()` but takes an additional argument `FileSource` which will be +/// resolved to a full search query in `make()`. +pub fn checkNextFileSource( + self: *CheckObjectStep, + phrase: []const u8, + file_source: std.Build.FileSource, +) void { + assert(self.checks.items.len > 0); + const last = &self.checks.items[self.checks.items.len - 1]; + last.match(.{ .string = self.step.owner.dupe(phrase), .file_source = file_source }); +} + +/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)` +/// however ensures there is no matching phrase in the output. +/// Asserts at least one check already exists. +pub fn checkNotPresent(self: *CheckObjectStep, phrase: []const u8) void { + assert(self.checks.items.len > 0); + const last = &self.checks.items[self.checks.items.len - 1]; + last.notPresent(.{ .string = self.step.owner.dupe(phrase) }); +} + +/// Creates a new check checking specifically symbol table parsed and dumped from the object +/// file. +/// Issuing this check will force parsing and dumping of the symbol table. +pub fn checkInSymtab(self: *CheckObjectStep) void { + self.dump_symtab = true; + const symtab_label = switch (self.obj_format) { + .macho => MachODumper.symtab_label, + else => @panic("TODO other parsers"), + }; + self.checkStart(symtab_label); +} + +/// Creates a new standalone, singular check which allows running simple binary operations +/// on the extracted variables. It will then compare the reduced program with the value of +/// the expected variable. +pub fn checkComputeCompare( + self: *CheckObjectStep, + program: []const u8, + expected: ComputeCompareExpected, +) void { + var new_check = Check.create(self.step.owner.allocator); + new_check.computeCmp(.{ .string = self.step.owner.dupe(program) }, expected); + self.checks.append(new_check) catch @panic("OOM"); +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const b = step.owner; + const gpa = b.allocator; + const self = @fieldParentPtr(CheckObjectStep, "step", step); + + const src_path = self.source.getPath(b); + const contents = fs.cwd().readFileAllocOptions( + gpa, + src_path, + self.max_bytes, + null, + @alignOf(u64), + null, + ) catch |err| return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err) }); + + const output = switch (self.obj_format) { + .macho => try MachODumper.parseAndDump(step, contents, .{ + .dump_symtab = self.dump_symtab, + }), + .elf => @panic("TODO elf parser"), + .coff => @panic("TODO coff parser"), + .wasm => try WasmDumper.parseAndDump(step, contents, .{ + .dump_symtab = self.dump_symtab, + }), + else => unreachable, + }; + + var vars = std.StringHashMap(u64).init(gpa); + + for (self.checks.items) |chk| { + var it = mem.tokenize(u8, output, "\r\n"); + for (chk.actions.items) |act| { + switch (act.tag) { + .match => { + while (it.next()) |line| { + if (try act.match(b, step, line, &vars)) break; + } else { + return step.fail( + \\ + \\========= expected to find: ========================== + \\{s} + \\========= but parsed file does not contain it: ======= + \\{s} + \\====================================================== + , .{ act.phrase.resolve(b, step), output }); + } + }, + .not_present => { + while (it.next()) |line| { + if (try act.match(b, step, line, &vars)) { + return step.fail( + \\ + \\========= expected not to find: =================== + \\{s} + \\========= but parsed file does contain it: ======== + \\{s} + \\=================================================== + , .{ act.phrase.resolve(b, step), output }); + } + } + }, + .compute_cmp => { + const res = act.computeCmp(b, step, vars) catch |err| switch (err) { + error.UnknownVariable => { + return step.fail( + \\========= from parsed file: ===================== + \\{s} + \\================================================= + , .{output}); + }, + else => |e| return e, + }; + if (!res) { + return step.fail( + \\ + \\========= comparison failed for action: =========== + \\{s} {} + \\========= from parsed file: ======================= + \\{s} + \\=================================================== + , .{ act.phrase.resolve(b, step), act.expected.?, output }); + } + }, + } + } + } +} + +const Opts = struct { + dump_symtab: bool = false, +}; + +const MachODumper = struct { + const LoadCommandIterator = macho.LoadCommandIterator; + const symtab_label = "symtab"; + + fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { + const gpa = step.owner.allocator; + var stream = std.io.fixedBufferStream(bytes); + const reader = stream.reader(); + + const hdr = try reader.readStruct(macho.mach_header_64); + if (hdr.magic != macho.MH_MAGIC_64) { + return error.InvalidMagicNumber; + } + + var output = std.ArrayList(u8).init(gpa); + const writer = output.writer(); + + var symtab: []const macho.nlist_64 = undefined; + var strtab: []const u8 = undefined; + var sections = std.ArrayList(macho.section_64).init(gpa); + var imports = std.ArrayList([]const u8).init(gpa); + + var it = LoadCommandIterator{ + .ncmds = hdr.ncmds, + .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], + }; + var i: usize = 0; + while (it.next()) |cmd| { + switch (cmd.cmd()) { + .SEGMENT_64 => { + const seg = cmd.cast(macho.segment_command_64).?; + try sections.ensureUnusedCapacity(seg.nsects); + for (cmd.getSections()) |sect| { + sections.appendAssumeCapacity(sect); + } + }, + .SYMTAB => if (opts.dump_symtab) { + const lc = cmd.cast(macho.symtab_command).?; + symtab = @ptrCast( + [*]const macho.nlist_64, + @alignCast(@alignOf(macho.nlist_64), &bytes[lc.symoff]), + )[0..lc.nsyms]; + strtab = bytes[lc.stroff..][0..lc.strsize]; + }, + .LOAD_DYLIB, + .LOAD_WEAK_DYLIB, + .REEXPORT_DYLIB, + => { + try imports.append(cmd.getDylibPathName()); + }, + else => {}, + } + + try dumpLoadCommand(cmd, i, writer); + try writer.writeByte('\n'); + + i += 1; + } + + if (opts.dump_symtab) { + try writer.print("{s}\n", .{symtab_label}); + for (symtab) |sym| { + if (sym.stab()) continue; + const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); + if (sym.sect()) { + const sect = sections.items[sym.n_sect - 1]; + try writer.print("{x} ({s},{s})", .{ + sym.n_value, + sect.segName(), + sect.sectName(), + }); + if (sym.ext()) { + try writer.writeAll(" external"); + } + try writer.print(" {s}\n", .{sym_name}); + } else if (sym.undf()) { + const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); + const import_name = blk: { + if (ordinal <= 0) { + if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF) + break :blk "self import"; + if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE) + break :blk "main executable"; + if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP) + break :blk "flat lookup"; + unreachable; + } + const full_path = imports.items[@bitCast(u16, ordinal) - 1]; + const basename = fs.path.basename(full_path); + assert(basename.len > 0); + const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len; + break :blk basename[0..ext]; + }; + try writer.writeAll("(undefined)"); + if (sym.weakRef()) { + try writer.writeAll(" weak"); + } + if (sym.ext()) { + try writer.writeAll(" external"); + } + try writer.print(" {s} (from {s})\n", .{ + sym_name, + import_name, + }); + } else unreachable; + } + } + + return output.toOwnedSlice(); + } + + fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void { + // print header first + try writer.print( + \\LC {d} + \\cmd {s} + \\cmdsize {d} + , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); + + switch (lc.cmd()) { + .SEGMENT_64 => { + const seg = lc.cast(macho.segment_command_64).?; + try writer.writeByte('\n'); + try writer.print( + \\segname {s} + \\vmaddr {x} + \\vmsize {x} + \\fileoff {x} + \\filesz {x} + , .{ + seg.segName(), + seg.vmaddr, + seg.vmsize, + seg.fileoff, + seg.filesize, + }); + + for (lc.getSections()) |sect| { + try writer.writeByte('\n'); + try writer.print( + \\sectname {s} + \\addr {x} + \\size {x} + \\offset {x} + \\align {x} + , .{ + sect.sectName(), + sect.addr, + sect.size, + sect.offset, + sect.@"align", + }); + } + }, + + .ID_DYLIB, + .LOAD_DYLIB, + .LOAD_WEAK_DYLIB, + .REEXPORT_DYLIB, + => { + const dylib = lc.cast(macho.dylib_command).?; + try writer.writeByte('\n'); + try writer.print( + \\name {s} + \\timestamp {d} + \\current version {x} + \\compatibility version {x} + , .{ + lc.getDylibPathName(), + dylib.dylib.timestamp, + dylib.dylib.current_version, + dylib.dylib.compatibility_version, + }); + }, + + .MAIN => { + const main = lc.cast(macho.entry_point_command).?; + try writer.writeByte('\n'); + try writer.print( + \\entryoff {x} + \\stacksize {x} + , .{ main.entryoff, main.stacksize }); + }, + + .RPATH => { + try writer.writeByte('\n'); + try writer.print( + \\path {s} + , .{ + lc.getRpathPathName(), + }); + }, + + .UUID => { + const uuid = lc.cast(macho.uuid_command).?; + try writer.writeByte('\n'); + try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)}); + }, + + .DATA_IN_CODE, + .FUNCTION_STARTS, + .CODE_SIGNATURE, + => { + const llc = lc.cast(macho.linkedit_data_command).?; + try writer.writeByte('\n'); + try writer.print( + \\dataoff {x} + \\datasize {x} + , .{ llc.dataoff, llc.datasize }); + }, + + .DYLD_INFO_ONLY => { + const dlc = lc.cast(macho.dyld_info_command).?; + try writer.writeByte('\n'); + try writer.print( + \\rebaseoff {x} + \\rebasesize {x} + \\bindoff {x} + \\bindsize {x} + \\weakbindoff {x} + \\weakbindsize {x} + \\lazybindoff {x} + \\lazybindsize {x} + \\exportoff {x} + \\exportsize {x} + , .{ + dlc.rebase_off, + dlc.rebase_size, + dlc.bind_off, + dlc.bind_size, + dlc.weak_bind_off, + dlc.weak_bind_size, + dlc.lazy_bind_off, + dlc.lazy_bind_size, + dlc.export_off, + dlc.export_size, + }); + }, + + .SYMTAB => { + const slc = lc.cast(macho.symtab_command).?; + try writer.writeByte('\n'); + try writer.print( + \\symoff {x} + \\nsyms {x} + \\stroff {x} + \\strsize {x} + , .{ + slc.symoff, + slc.nsyms, + slc.stroff, + slc.strsize, + }); + }, + + .DYSYMTAB => { + const dlc = lc.cast(macho.dysymtab_command).?; + try writer.writeByte('\n'); + try writer.print( + \\ilocalsym {x} + \\nlocalsym {x} + \\iextdefsym {x} + \\nextdefsym {x} + \\iundefsym {x} + \\nundefsym {x} + \\indirectsymoff {x} + \\nindirectsyms {x} + , .{ + dlc.ilocalsym, + dlc.nlocalsym, + dlc.iextdefsym, + dlc.nextdefsym, + dlc.iundefsym, + dlc.nundefsym, + dlc.indirectsymoff, + dlc.nindirectsyms, + }); + }, + + else => {}, + } + } +}; + +const WasmDumper = struct { + const symtab_label = "symbols"; + + fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 { + const gpa = step.owner.allocator; + if (opts.dump_symtab) { + @panic("TODO: Implement symbol table parsing and dumping"); + } + + var fbs = std.io.fixedBufferStream(bytes); + const reader = fbs.reader(); + + const buf = try reader.readBytesNoEof(8); + if (!mem.eql(u8, buf[0..4], &std.wasm.magic)) { + return error.InvalidMagicByte; + } + if (!mem.eql(u8, buf[4..], &std.wasm.version)) { + return error.UnsupportedWasmVersion; + } + + var output = std.ArrayList(u8).init(gpa); + errdefer output.deinit(); + const writer = output.writer(); + + while (reader.readByte()) |current_byte| { + const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch { + return step.fail("Found invalid section id '{d}'", .{current_byte}); + }; + + const section_length = try std.leb.readULEB128(u32, reader); + try parseAndDumpSection(step, section, bytes[fbs.pos..][0..section_length], writer); + fbs.pos += section_length; + } else |_| {} // reached end of stream + + return output.toOwnedSlice(); + } + + fn parseAndDumpSection( + step: *Step, + section: std.wasm.Section, + data: []const u8, + writer: anytype, + ) !void { + var fbs = std.io.fixedBufferStream(data); + const reader = fbs.reader(); + + try writer.print( + \\Section {s} + \\size {d} + , .{ @tagName(section), data.len }); + + switch (section) { + .type, + .import, + .function, + .table, + .memory, + .global, + .@"export", + .element, + .code, + .data, + => { + const entries = try std.leb.readULEB128(u32, reader); + try writer.print("\nentries {d}\n", .{entries}); + try dumpSection(step, section, data[fbs.pos..], entries, writer); + }, + .custom => { + const name_length = try std.leb.readULEB128(u32, reader); + const name = data[fbs.pos..][0..name_length]; + fbs.pos += name_length; + try writer.print("\nname {s}\n", .{name}); + + if (mem.eql(u8, name, "name")) { + try parseDumpNames(step, reader, writer, data); + } else if (mem.eql(u8, name, "producers")) { + try parseDumpProducers(reader, writer, data); + } else if (mem.eql(u8, name, "target_features")) { + try parseDumpFeatures(reader, writer, data); + } + // TODO: Implement parsing and dumping other custom sections (such as relocations) + }, + .start => { + const start = try std.leb.readULEB128(u32, reader); + try writer.print("\nstart {d}\n", .{start}); + }, + else => {}, // skip unknown sections + } + } + + fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { + var fbs = std.io.fixedBufferStream(data); + const reader = fbs.reader(); + + switch (section) { + .type => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + const func_type = try reader.readByte(); + if (func_type != std.wasm.function_type) { + return step.fail("expected function type, found byte '{d}'", .{func_type}); + } + const params = try std.leb.readULEB128(u32, reader); + try writer.print("params {d}\n", .{params}); + var index: u32 = 0; + while (index < params) : (index += 1) { + try parseDumpType(step, std.wasm.Valtype, reader, writer); + } else index = 0; + const returns = try std.leb.readULEB128(u32, reader); + try writer.print("returns {d}\n", .{returns}); + while (index < returns) : (index += 1) { + try parseDumpType(step, std.wasm.Valtype, reader, writer); + } + } + }, + .import => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + const module_name_len = try std.leb.readULEB128(u32, reader); + const module_name = data[fbs.pos..][0..module_name_len]; + fbs.pos += module_name_len; + const name_len = try std.leb.readULEB128(u32, reader); + const name = data[fbs.pos..][0..name_len]; + fbs.pos += name_len; + + const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch { + return step.fail("invalid import kind", .{}); + }; + + try writer.print( + \\module {s} + \\name {s} + \\kind {s} + , .{ module_name, name, @tagName(kind) }); + try writer.writeByte('\n'); + switch (kind) { + .function => { + try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); + }, + .memory => { + try parseDumpLimits(reader, writer); + }, + .global => { + try parseDumpType(step, std.wasm.Valtype, reader, writer); + try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)}); + }, + .table => { + try parseDumpType(step, std.wasm.RefType, reader, writer); + try parseDumpLimits(reader, writer); + }, + } + } + }, + .function => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); + } + }, + .table => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try parseDumpType(step, std.wasm.RefType, reader, writer); + try parseDumpLimits(reader, writer); + } + }, + .memory => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try parseDumpLimits(reader, writer); + } + }, + .global => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try parseDumpType(step, std.wasm.Valtype, reader, writer); + try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)}); + try parseDumpInit(step, reader, writer); + } + }, + .@"export" => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + const name_len = try std.leb.readULEB128(u32, reader); + const name = data[fbs.pos..][0..name_len]; + fbs.pos += name_len; + const kind_byte = try std.leb.readULEB128(u8, reader); + const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch { + return step.fail("invalid export kind value '{d}'", .{kind_byte}); + }; + const index = try std.leb.readULEB128(u32, reader); + try writer.print( + \\name {s} + \\kind {s} + \\index {d} + , .{ name, @tagName(kind), index }); + try writer.writeByte('\n'); + } + }, + .element => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)}); + try parseDumpInit(step, reader, writer); + + const function_indexes = try std.leb.readULEB128(u32, reader); + var function_index: u32 = 0; + try writer.print("indexes {d}\n", .{function_indexes}); + while (function_index < function_indexes) : (function_index += 1) { + try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); + } + } + }, + .code => {}, // code section is considered opaque to linker + .data => { + var i: u32 = 0; + while (i < entries) : (i += 1) { + const index = try std.leb.readULEB128(u32, reader); + try writer.print("memory index 0x{x}\n", .{index}); + try parseDumpInit(step, reader, writer); + const size = try std.leb.readULEB128(u32, reader); + try writer.print("size {d}\n", .{size}); + try reader.skipBytes(size, .{}); // we do not care about the content of the segments + } + }, + else => unreachable, + } + } + + fn parseDumpType(step: *Step, comptime WasmType: type, reader: anytype, writer: anytype) !void { + const type_byte = try reader.readByte(); + const valtype = std.meta.intToEnum(WasmType, type_byte) catch { + return step.fail("Invalid wasm type value '{d}'", .{type_byte}); + }; + try writer.print("type {s}\n", .{@tagName(valtype)}); + } + + fn parseDumpLimits(reader: anytype, writer: anytype) !void { + const flags = try std.leb.readULEB128(u8, reader); + const min = try std.leb.readULEB128(u32, reader); + + try writer.print("min {x}\n", .{min}); + if (flags != 0) { + try writer.print("max {x}\n", .{try std.leb.readULEB128(u32, reader)}); + } + } + + fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void { + const byte = try std.leb.readULEB128(u8, reader); + const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch { + return step.fail("invalid wasm opcode '{d}'", .{byte}); + }; + switch (opcode) { + .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}), + .i64_const => try writer.print("i64.const {x}\n", .{try std.leb.readILEB128(i64, reader)}), + .f32_const => try writer.print("f32.const {x}\n", .{@bitCast(f32, try reader.readIntLittle(u32))}), + .f64_const => try writer.print("f64.const {x}\n", .{@bitCast(f64, try reader.readIntLittle(u64))}), + .global_get => try writer.print("global.get {x}\n", .{try std.leb.readULEB128(u32, reader)}), + else => unreachable, + } + const end_opcode = try std.leb.readULEB128(u8, reader); + if (end_opcode != std.wasm.opcode(.end)) { + return step.fail("expected 'end' opcode in init expression", .{}); + } + } + + fn parseDumpNames(step: *Step, reader: anytype, writer: anytype, data: []const u8) !void { + while (reader.context.pos < data.len) { + try parseDumpType(step, std.wasm.NameSubsection, reader, writer); + const size = try std.leb.readULEB128(u32, reader); + const entries = try std.leb.readULEB128(u32, reader); + try writer.print( + \\size {d} + \\names {d} + , .{ size, entries }); + try writer.writeByte('\n'); + var i: u32 = 0; + while (i < entries) : (i += 1) { + const index = try std.leb.readULEB128(u32, reader); + const name_len = try std.leb.readULEB128(u32, reader); + const pos = reader.context.pos; + const name = data[pos..][0..name_len]; + reader.context.pos += name_len; + + try writer.print( + \\index {d} + \\name {s} + , .{ index, name }); + try writer.writeByte('\n'); + } + } + } + + fn parseDumpProducers(reader: anytype, writer: anytype, data: []const u8) !void { + const field_count = try std.leb.readULEB128(u32, reader); + try writer.print("fields {d}\n", .{field_count}); + var current_field: u32 = 0; + while (current_field < field_count) : (current_field += 1) { + const field_name_length = try std.leb.readULEB128(u32, reader); + const field_name = data[reader.context.pos..][0..field_name_length]; + reader.context.pos += field_name_length; + + const value_count = try std.leb.readULEB128(u32, reader); + try writer.print( + \\field_name {s} + \\values {d} + , .{ field_name, value_count }); + try writer.writeByte('\n'); + var current_value: u32 = 0; + while (current_value < value_count) : (current_value += 1) { + const value_length = try std.leb.readULEB128(u32, reader); + const value = data[reader.context.pos..][0..value_length]; + reader.context.pos += value_length; + + const version_length = try std.leb.readULEB128(u32, reader); + const version = data[reader.context.pos..][0..version_length]; + reader.context.pos += version_length; + + try writer.print( + \\value_name {s} + \\version {s} + , .{ value, version }); + try writer.writeByte('\n'); + } + } + } + + fn parseDumpFeatures(reader: anytype, writer: anytype, data: []const u8) !void { + const feature_count = try std.leb.readULEB128(u32, reader); + try writer.print("features {d}\n", .{feature_count}); + + var index: u32 = 0; + while (index < feature_count) : (index += 1) { + const prefix_byte = try std.leb.readULEB128(u8, reader); + const name_length = try std.leb.readULEB128(u32, reader); + const feature_name = data[reader.context.pos..][0..name_length]; + reader.context.pos += name_length; + + try writer.print("{c} {s}\n", .{ prefix_byte, feature_name }); + } + } +}; diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig new file mode 100644 index 0000000000000000000000000000000000000000..7627c4e6d0eeeae639e055bca190497c7c6e3a4c --- /dev/null +++ b/lib/std/Build/Step/Compile.zig @@ -0,0 +1,2183 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const mem = std.mem; +const fs = std.fs; +const assert = std.debug.assert; +const panic = std.debug.panic; +const ArrayList = std.ArrayList; +const StringHashMap = std.StringHashMap; +const Sha256 = std.crypto.hash.sha2.Sha256; +const Allocator = mem.Allocator; +const Step = std.Build.Step; +const CrossTarget = std.zig.CrossTarget; +const NativeTargetInfo = std.zig.system.NativeTargetInfo; +const FileSource = std.Build.FileSource; +const PkgConfigPkg = std.Build.PkgConfigPkg; +const PkgConfigError = std.Build.PkgConfigError; +const ExecError = std.Build.ExecError; +const Module = std.Build.Module; +const VcpkgRoot = std.Build.VcpkgRoot; +const InstallDir = std.Build.InstallDir; +const InstallArtifactStep = std.Build.InstallArtifactStep; +const GeneratedFile = std.Build.GeneratedFile; +const ObjCopyStep = std.Build.ObjCopyStep; +const CheckObjectStep = std.Build.CheckObjectStep; +const RunStep = std.Build.RunStep; +const OptionsStep = std.Build.OptionsStep; +const ConfigHeaderStep = std.Build.ConfigHeaderStep; +const CompileStep = @This(); + +pub const base_id: Step.Id = .compile; + +step: Step, +name: []const u8, +target: CrossTarget, +target_info: NativeTargetInfo, +optimize: std.builtin.Mode, +linker_script: ?FileSource = null, +version_script: ?[]const u8 = null, +out_filename: []const u8, +linkage: ?Linkage = null, +version: ?std.builtin.Version, +kind: Kind, +major_only_filename: ?[]const u8, +name_only_filename: ?[]const u8, +strip: ?bool, +unwind_tables: ?bool, +// keep in sync with src/link.zig:CompressDebugSections +compress_debug_sections: enum { none, zlib } = .none, +lib_paths: ArrayList(FileSource), +rpaths: ArrayList(FileSource), +framework_dirs: ArrayList(FileSource), +frameworks: StringHashMap(FrameworkLinkInfo), +verbose_link: bool, +verbose_cc: bool, +emit_analysis: EmitOption = .default, +emit_asm: EmitOption = .default, +emit_bin: EmitOption = .default, +emit_docs: EmitOption = .default, +emit_implib: EmitOption = .default, +emit_llvm_bc: EmitOption = .default, +emit_llvm_ir: EmitOption = .default, +// Lots of things depend on emit_h having a consistent path, +// so it is not an EmitOption for now. +emit_h: bool = false, +bundle_compiler_rt: ?bool = null, +single_threaded: ?bool, +stack_protector: ?bool = null, +disable_stack_probing: bool, +disable_sanitize_c: bool, +sanitize_thread: bool, +rdynamic: bool, +dwarf_format: ?std.dwarf.Format = null, +import_memory: bool = false, +/// For WebAssembly targets, this will allow for undefined symbols to +/// be imported from the host environment. +import_symbols: bool = false, +import_table: bool = false, +export_table: bool = false, +initial_memory: ?u64 = null, +max_memory: ?u64 = null, +shared_memory: bool = false, +global_base: ?u64 = null, +c_std: std.Build.CStd, +zig_lib_dir: ?[]const u8, +main_pkg_path: ?[]const u8, +exec_cmd_args: ?[]const ?[]const u8, +filter: ?[]const u8, +test_evented_io: bool = false, +test_runner: ?[]const u8, +code_model: std.builtin.CodeModel = .default, +wasi_exec_model: ?std.builtin.WasiExecModel = null, +/// Symbols to be exported when compiling to wasm +export_symbol_names: []const []const u8 = &.{}, + +root_src: ?FileSource, +out_h_filename: []const u8, +out_lib_filename: []const u8, +out_pdb_filename: []const u8, +modules: std.StringArrayHashMap(*Module), + +link_objects: ArrayList(LinkObject), +include_dirs: ArrayList(IncludeDir), +c_macros: ArrayList([]const u8), +installed_headers: ArrayList(*Step), +is_linking_libc: bool, +is_linking_libcpp: bool, +vcpkg_bin_path: ?[]const u8 = null, + +/// This may be set in order to override the default install directory +override_dest_dir: ?InstallDir, +installed_path: ?[]const u8, + +/// Base address for an executable image. +image_base: ?u64 = null, + +libc_file: ?FileSource = null, + +valgrind_support: ?bool = null, +each_lib_rpath: ?bool = null, +/// On ELF targets, this will emit a link section called ".note.gnu.build-id" +/// which can be used to coordinate a stripped binary with its debug symbols. +/// As an example, the bloaty project refuses to work unless its inputs have +/// build ids, in order to prevent accidental mismatches. +/// The default is to not include this section because it slows down linking. +build_id: ?bool = null, + +/// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF +/// file. +link_eh_frame_hdr: bool = false, +link_emit_relocs: bool = false, + +/// Place every function in its own section so that unused ones may be +/// safely garbage-collected during the linking phase. +link_function_sections: bool = false, + +/// Remove functions and data that are unreachable by the entry point or +/// exported symbols. +link_gc_sections: ?bool = null, + +/// (Windows) Whether or not to enable ASLR. Maps to the /DYNAMICBASE[:NO] linker argument. +linker_dynamicbase: bool = true, + +linker_allow_shlib_undefined: ?bool = null, + +/// Permit read-only relocations in read-only segments. Disallowed by default. +link_z_notext: bool = false, + +/// Force all relocations to be read-only after processing. +link_z_relro: bool = true, + +/// Allow relocations to be lazily processed after load. +link_z_lazy: bool = false, + +/// Common page size +link_z_common_page_size: ?u64 = null, + +/// Maximum page size +link_z_max_page_size: ?u64 = null, + +/// (Darwin) Install name for the dylib +install_name: ?[]const u8 = null, + +/// (Darwin) Path to entitlements file +entitlements: ?[]const u8 = null, + +/// (Darwin) Size of the pagezero segment. +pagezero_size: ?u64 = null, + +/// (Darwin) Search strategy for searching system libraries. Either `paths_first` or `dylibs_first`. +/// The former lowers to `-search_paths_first` linker option, while the latter to `-search_dylibs_first` +/// option. +/// By default, if no option is specified, the linker assumes `paths_first` as the default +/// search strategy. +search_strategy: ?enum { paths_first, dylibs_first } = null, + +/// (Darwin) Set size of the padding between the end of load commands +/// and start of `__TEXT,__text` section. +headerpad_size: ?u32 = null, + +/// (Darwin) Automatically Set size of the padding between the end of load commands +/// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN. +headerpad_max_install_names: bool = false, + +/// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols. +dead_strip_dylibs: bool = false, + +/// Position Independent Code +force_pic: ?bool = null, + +/// Position Independent Executable +pie: ?bool = null, + +red_zone: ?bool = null, + +omit_frame_pointer: ?bool = null, +dll_export_fns: ?bool = null, + +subsystem: ?std.Target.SubSystem = null, + +entry_symbol_name: ?[]const u8 = null, + +/// List of symbols forced as undefined in the symbol table +/// thus forcing their resolution by the linker. +/// Corresponds to `-u ` for ELF/MachO and `/include:` for COFF/PE. +force_undefined_symbols: std.StringHashMap(void), + +/// Overrides the default stack size +stack_size: ?u64 = null, + +want_lto: ?bool = null, +use_llvm: ?bool, +use_lld: ?bool, + +/// This is an advanced setting that can change the intent of this CompileStep. +/// If this slice has nonzero length, it means that this CompileStep exists to +/// check for compile errors and return *success* if they match, and failure +/// otherwise. +expect_errors: []const []const u8 = &.{}, + +output_path_source: GeneratedFile, +output_lib_path_source: GeneratedFile, +output_h_path_source: GeneratedFile, +output_pdb_path_source: GeneratedFile, +output_dirname_source: GeneratedFile, + +pub const CSourceFiles = struct { + files: []const []const u8, + flags: []const []const u8, +}; + +pub const CSourceFile = struct { + source: FileSource, + args: []const []const u8, + + pub fn dupe(self: CSourceFile, b: *std.Build) CSourceFile { + return .{ + .source = self.source.dupe(b), + .args = b.dupeStrings(self.args), + }; + } +}; + +pub const LinkObject = union(enum) { + static_path: FileSource, + other_step: *CompileStep, + system_lib: SystemLib, + assembly_file: FileSource, + c_source_file: *CSourceFile, + c_source_files: *CSourceFiles, +}; + +pub const SystemLib = struct { + name: []const u8, + needed: bool, + weak: bool, + use_pkg_config: enum { + /// Don't use pkg-config, just pass -lfoo where foo is name. + no, + /// Try to get information on how to link the library from pkg-config. + /// If that fails, fall back to passing -lfoo where foo is name. + yes, + /// Try to get information on how to link the library from pkg-config. + /// If that fails, error out. + force, + }, +}; + +const FrameworkLinkInfo = struct { + needed: bool = false, + weak: bool = false, +}; + +pub const IncludeDir = union(enum) { + raw_path: []const u8, + raw_path_system: []const u8, + other_step: *CompileStep, + config_header_step: *ConfigHeaderStep, +}; + +pub const Options = struct { + name: []const u8, + root_source_file: ?FileSource = null, + target: CrossTarget, + optimize: std.builtin.Mode, + kind: Kind, + linkage: ?Linkage = null, + version: ?std.builtin.Version = null, + max_rss: usize = 0, + filter: ?[]const u8 = null, + test_runner: ?[]const u8 = null, + link_libc: ?bool = null, + single_threaded: ?bool = null, + use_llvm: ?bool = null, + use_lld: ?bool = null, +}; + +pub const Kind = enum { + exe, + lib, + obj, + @"test", +}; + +pub const Linkage = enum { dynamic, static }; + +pub const EmitOption = union(enum) { + default: void, + no_emit: void, + emit: void, + emit_to: []const u8, + + fn getArg(self: @This(), b: *std.Build, arg_name: []const u8) ?[]const u8 { + return switch (self) { + .no_emit => b.fmt("-fno-{s}", .{arg_name}), + .default => null, + .emit => b.fmt("-f{s}", .{arg_name}), + .emit_to => |path| b.fmt("-f{s}={s}", .{ arg_name, path }), + }; + } +}; + +pub fn create(owner: *std.Build, options: Options) *CompileStep { + const name = owner.dupe(options.name); + const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null; + if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) { + panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); + } + + // Avoid the common case of the step name looking like "zig test test". + const name_adjusted = if (options.kind == .@"test" and mem.eql(u8, name, "test")) + "" + else + owner.fmt("{s} ", .{name}); + + const step_name = owner.fmt("{s} {s}{s} {s}", .{ + switch (options.kind) { + .exe => "zig build-exe", + .lib => "zig build-lib", + .obj => "zig build-obj", + .@"test" => "zig test", + }, + name_adjusted, + @tagName(options.optimize), + options.target.zigTriple(owner.allocator) catch @panic("OOM"), + }); + + const target_info = NativeTargetInfo.detect(options.target) catch @panic("unhandled error"); + + const out_filename = std.zig.binNameAlloc(owner.allocator, .{ + .root_name = name, + .target = target_info.target, + .output_mode = switch (options.kind) { + .lib => .Lib, + .obj => .Obj, + .exe, .@"test" => .Exe, + }, + .link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) { + .dynamic => .Dynamic, + .static => .Static, + }) else null, + .version = options.version, + }) catch @panic("OOM"); + + const self = owner.allocator.create(CompileStep) catch @panic("OOM"); + self.* = CompileStep{ + .strip = null, + .unwind_tables = null, + .verbose_link = false, + .verbose_cc = false, + .optimize = options.optimize, + .target = options.target, + .linkage = options.linkage, + .kind = options.kind, + .root_src = root_src, + .name = name, + .frameworks = StringHashMap(FrameworkLinkInfo).init(owner.allocator), + .step = Step.init(.{ + .id = base_id, + .name = step_name, + .owner = owner, + .makeFn = make, + .max_rss = options.max_rss, + }), + .version = options.version, + .out_filename = out_filename, + .out_h_filename = owner.fmt("{s}.h", .{name}), + .out_lib_filename = undefined, + .out_pdb_filename = owner.fmt("{s}.pdb", .{name}), + .major_only_filename = null, + .name_only_filename = null, + .modules = std.StringArrayHashMap(*Module).init(owner.allocator), + .include_dirs = ArrayList(IncludeDir).init(owner.allocator), + .link_objects = ArrayList(LinkObject).init(owner.allocator), + .c_macros = ArrayList([]const u8).init(owner.allocator), + .lib_paths = ArrayList(FileSource).init(owner.allocator), + .rpaths = ArrayList(FileSource).init(owner.allocator), + .framework_dirs = ArrayList(FileSource).init(owner.allocator), + .installed_headers = ArrayList(*Step).init(owner.allocator), + .c_std = std.Build.CStd.C99, + .zig_lib_dir = null, + .main_pkg_path = null, + .exec_cmd_args = null, + .filter = options.filter, + .test_runner = options.test_runner, + .disable_stack_probing = false, + .disable_sanitize_c = false, + .sanitize_thread = false, + .rdynamic = false, + .override_dest_dir = null, + .installed_path = null, + .force_undefined_symbols = StringHashMap(void).init(owner.allocator), + + .output_path_source = GeneratedFile{ .step = &self.step }, + .output_lib_path_source = GeneratedFile{ .step = &self.step }, + .output_h_path_source = GeneratedFile{ .step = &self.step }, + .output_pdb_path_source = GeneratedFile{ .step = &self.step }, + .output_dirname_source = GeneratedFile{ .step = &self.step }, + + .target_info = target_info, + + .is_linking_libc = options.link_libc orelse false, + .is_linking_libcpp = false, + .single_threaded = options.single_threaded, + .use_llvm = options.use_llvm, + .use_lld = options.use_lld, + }; + + if (self.kind == .lib) { + if (self.linkage != null and self.linkage.? == .static) { + self.out_lib_filename = self.out_filename; + } else if (self.version) |version| { + if (target_info.target.isDarwin()) { + self.major_only_filename = owner.fmt("lib{s}.{d}.dylib", .{ + self.name, + version.major, + }); + self.name_only_filename = owner.fmt("lib{s}.dylib", .{self.name}); + self.out_lib_filename = self.out_filename; + } else if (target_info.target.os.tag == .windows) { + self.out_lib_filename = owner.fmt("{s}.lib", .{self.name}); + } else { + self.major_only_filename = owner.fmt("lib{s}.so.{d}", .{ self.name, version.major }); + self.name_only_filename = owner.fmt("lib{s}.so", .{self.name}); + self.out_lib_filename = self.out_filename; + } + } else { + if (target_info.target.isDarwin()) { + self.out_lib_filename = self.out_filename; + } else if (target_info.target.os.tag == .windows) { + self.out_lib_filename = owner.fmt("{s}.lib", .{self.name}); + } else { + self.out_lib_filename = self.out_filename; + } + } + } + + if (root_src) |rs| rs.addStepDependencies(&self.step); + + return self; +} + +pub fn installHeader(cs: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void { + const b = cs.step.owner; + const install_file = b.addInstallHeaderFile(src_path, dest_rel_path); + b.getInstallStep().dependOn(&install_file.step); + cs.installed_headers.append(&install_file.step) catch @panic("OOM"); +} + +pub const InstallConfigHeaderOptions = struct { + install_dir: InstallDir = .header, + dest_rel_path: ?[]const u8 = null, +}; + +pub fn installConfigHeader( + cs: *CompileStep, + config_header: *ConfigHeaderStep, + options: InstallConfigHeaderOptions, +) void { + const dest_rel_path = options.dest_rel_path orelse config_header.include_path; + const b = cs.step.owner; + const install_file = b.addInstallFileWithDir( + .{ .generated = &config_header.output_file }, + options.install_dir, + dest_rel_path, + ); + install_file.step.dependOn(&config_header.step); + b.getInstallStep().dependOn(&install_file.step); + cs.installed_headers.append(&install_file.step) catch @panic("OOM"); +} + +pub fn installHeadersDirectory( + a: *CompileStep, + src_dir_path: []const u8, + dest_rel_path: []const u8, +) void { + return installHeadersDirectoryOptions(a, .{ + .source_dir = src_dir_path, + .install_dir = .header, + .install_subdir = dest_rel_path, + }); +} + +pub fn installHeadersDirectoryOptions( + cs: *CompileStep, + options: std.Build.InstallDirStep.Options, +) void { + const b = cs.step.owner; + const install_dir = b.addInstallDirectory(options); + b.getInstallStep().dependOn(&install_dir.step); + cs.installed_headers.append(&install_dir.step) catch @panic("OOM"); +} + +pub fn installLibraryHeaders(cs: *CompileStep, l: *CompileStep) void { + assert(l.kind == .lib); + const b = cs.step.owner; + const install_step = b.getInstallStep(); + // Copy each element from installed_headers, modifying the builder + // to be the new parent's builder. + for (l.installed_headers.items) |step| { + const step_copy = switch (step.id) { + inline .install_file, .install_dir => |id| blk: { + const T = id.Type(); + const ptr = b.allocator.create(T) catch @panic("OOM"); + ptr.* = step.cast(T).?.*; + ptr.dest_builder = b; + break :blk &ptr.step; + }, + else => unreachable, + }; + cs.installed_headers.append(step_copy) catch @panic("OOM"); + install_step.dependOn(step_copy); + } + cs.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM"); +} + +pub fn addObjCopy(cs: *CompileStep, options: ObjCopyStep.Options) *ObjCopyStep { + const b = cs.step.owner; + var copy = options; + if (copy.basename == null) { + if (options.format) |f| { + copy.basename = b.fmt("{s}.{s}", .{ cs.name, @tagName(f) }); + } else { + copy.basename = cs.name; + } + } + return b.addObjCopy(cs.getOutputSource(), copy); +} + +/// This function would run in the context of the package that created the executable, +/// which is undesirable when running an executable provided by a dependency package. +pub const run = @compileError("deprecated; use std.Build.addRunArtifact"); + +/// This function would install in the context of the package that created the artifact, +/// which is undesirable when installing an artifact provided by a dependency package. +pub const install = @compileError("deprecated; use std.Build.installArtifact"); + +pub fn checkObject(self: *CompileStep) *CheckObjectStep { + return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt); +} + +pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { + const b = self.step.owner; + self.linker_script = source.dupe(b); + source.addStepDependencies(&self.step); +} + +pub fn forceUndefinedSymbol(self: *CompileStep, symbol_name: []const u8) void { + const b = self.step.owner; + self.force_undefined_symbols.put(b.dupe(symbol_name), {}) catch @panic("OOM"); +} + +pub fn linkFramework(self: *CompileStep, framework_name: []const u8) void { + const b = self.step.owner; + self.frameworks.put(b.dupe(framework_name), .{}) catch @panic("OOM"); +} + +pub fn linkFrameworkNeeded(self: *CompileStep, framework_name: []const u8) void { + const b = self.step.owner; + self.frameworks.put(b.dupe(framework_name), .{ + .needed = true, + }) catch @panic("OOM"); +} + +pub fn linkFrameworkWeak(self: *CompileStep, framework_name: []const u8) void { + const b = self.step.owner; + self.frameworks.put(b.dupe(framework_name), .{ + .weak = true, + }) catch @panic("OOM"); +} + +/// Returns whether the library, executable, or object depends on a particular system library. +pub fn dependsOnSystemLibrary(self: CompileStep, name: []const u8) bool { + if (isLibCLibrary(name)) { + return self.is_linking_libc; + } + if (isLibCppLibrary(name)) { + return self.is_linking_libcpp; + } + for (self.link_objects.items) |link_object| { + switch (link_object) { + .system_lib => |lib| if (mem.eql(u8, lib.name, name)) return true, + else => continue, + } + } + return false; +} + +pub fn linkLibrary(self: *CompileStep, lib: *CompileStep) void { + assert(lib.kind == .lib); + self.linkLibraryOrObject(lib); +} + +pub fn isDynamicLibrary(self: *CompileStep) bool { + return self.kind == .lib and self.linkage == Linkage.dynamic; +} + +pub fn isStaticLibrary(self: *CompileStep) bool { + return self.kind == .lib and self.linkage != Linkage.dynamic; +} + +pub fn producesPdbFile(self: *CompileStep) bool { + if (!self.target.isWindows() and !self.target.isUefi()) return false; + if (self.target.getObjectFormat() == .c) return false; + if (self.strip == true) return false; + return self.isDynamicLibrary() or self.kind == .exe or self.kind == .@"test"; +} + +pub fn linkLibC(self: *CompileStep) void { + self.is_linking_libc = true; +} + +pub fn linkLibCpp(self: *CompileStep) void { + self.is_linking_libcpp = true; +} + +/// If the value is omitted, it is set to 1. +/// `name` and `value` need not live longer than the function call. +pub fn defineCMacro(self: *CompileStep, name: []const u8, value: ?[]const u8) void { + const b = self.step.owner; + const macro = std.Build.constructCMacro(b.allocator, name, value); + self.c_macros.append(macro) catch @panic("OOM"); +} + +/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. +pub fn defineCMacroRaw(self: *CompileStep, name_and_value: []const u8) void { + const b = self.step.owner; + self.c_macros.append(b.dupe(name_and_value)) catch @panic("OOM"); +} + +/// This one has no integration with anything, it just puts -lname on the command line. +/// Prefer to use `linkSystemLibrary` instead. +pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void { + const b = self.step.owner; + self.link_objects.append(.{ + .system_lib = .{ + .name = b.dupe(name), + .needed = false, + .weak = false, + .use_pkg_config = .no, + }, + }) catch @panic("OOM"); +} + +/// This one has no integration with anything, it just puts -needed-lname on the command line. +/// Prefer to use `linkSystemLibraryNeeded` instead. +pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void { + const b = self.step.owner; + self.link_objects.append(.{ + .system_lib = .{ + .name = b.dupe(name), + .needed = true, + .weak = false, + .use_pkg_config = .no, + }, + }) catch @panic("OOM"); +} + +/// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the +/// command line. Prefer to use `linkSystemLibraryWeak` instead. +pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void { + const b = self.step.owner; + self.link_objects.append(.{ + .system_lib = .{ + .name = b.dupe(name), + .needed = false, + .weak = true, + .use_pkg_config = .no, + }, + }) catch @panic("OOM"); +} + +/// This links against a system library, exclusively using pkg-config to find the library. +/// Prefer to use `linkSystemLibrary` instead. +pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { + const b = self.step.owner; + self.link_objects.append(.{ + .system_lib = .{ + .name = b.dupe(lib_name), + .needed = false, + .weak = false, + .use_pkg_config = .force, + }, + }) catch @panic("OOM"); +} + +/// This links against a system library, exclusively using pkg-config to find the library. +/// Prefer to use `linkSystemLibraryNeeded` instead. +pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { + const b = self.step.owner; + self.link_objects.append(.{ + .system_lib = .{ + .name = b.dupe(lib_name), + .needed = true, + .weak = false, + .use_pkg_config = .force, + }, + }) catch @panic("OOM"); +} + +/// Run pkg-config for the given library name and parse the output, returning the arguments +/// that should be passed to zig to link the given library. +fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u8 { + const b = self.step.owner; + const pkg_name = match: { + // First we have to map the library name to pkg config name. Unfortunately, + // there are several examples where this is not straightforward: + // -lSDL2 -> pkg-config sdl2 + // -lgdk-3 -> pkg-config gdk-3.0 + // -latk-1.0 -> pkg-config atk + const pkgs = try getPkgConfigList(b); + + // Exact match means instant winner. + for (pkgs) |pkg| { + if (mem.eql(u8, pkg.name, lib_name)) { + break :match pkg.name; + } + } + + // Next we'll try ignoring case. + for (pkgs) |pkg| { + if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) { + break :match pkg.name; + } + } + + // Now try appending ".0". + for (pkgs) |pkg| { + if (std.ascii.indexOfIgnoreCase(pkg.name, lib_name)) |pos| { + if (pos != 0) continue; + if (mem.eql(u8, pkg.name[lib_name.len..], ".0")) { + break :match pkg.name; + } + } + } + + // Trimming "-1.0". + if (mem.endsWith(u8, lib_name, "-1.0")) { + const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len]; + for (pkgs) |pkg| { + if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) { + break :match pkg.name; + } + } + } + + return error.PackageNotFound; + }; + + var code: u8 = undefined; + const stdout = if (b.execAllowFail(&[_][]const u8{ + "pkg-config", + pkg_name, + "--cflags", + "--libs", + }, &code, .Ignore)) |stdout| stdout else |err| switch (err) { + error.ProcessTerminated => return error.PkgConfigCrashed, + error.ExecNotSupported => return error.PkgConfigFailed, + error.ExitCodeFailure => return error.PkgConfigFailed, + error.FileNotFound => return error.PkgConfigNotInstalled, + else => return err, + }; + + var zig_args = ArrayList([]const u8).init(b.allocator); + defer zig_args.deinit(); + + var it = mem.tokenize(u8, stdout, " \r\n\t"); + while (it.next()) |tok| { + if (mem.eql(u8, tok, "-I")) { + const dir = it.next() orelse return error.PkgConfigInvalidOutput; + try zig_args.appendSlice(&[_][]const u8{ "-I", dir }); + } else if (mem.startsWith(u8, tok, "-I")) { + try zig_args.append(tok); + } else if (mem.eql(u8, tok, "-L")) { + const dir = it.next() orelse return error.PkgConfigInvalidOutput; + try zig_args.appendSlice(&[_][]const u8{ "-L", dir }); + } else if (mem.startsWith(u8, tok, "-L")) { + try zig_args.append(tok); + } else if (mem.eql(u8, tok, "-l")) { + const lib = it.next() orelse return error.PkgConfigInvalidOutput; + try zig_args.appendSlice(&[_][]const u8{ "-l", lib }); + } else if (mem.startsWith(u8, tok, "-l")) { + try zig_args.append(tok); + } else if (mem.eql(u8, tok, "-D")) { + const macro = it.next() orelse return error.PkgConfigInvalidOutput; + try zig_args.appendSlice(&[_][]const u8{ "-D", macro }); + } else if (mem.startsWith(u8, tok, "-D")) { + try zig_args.append(tok); + } else if (b.debug_pkg_config) { + return self.step.fail("unknown pkg-config flag '{s}'", .{tok}); + } + } + + return zig_args.toOwnedSlice(); +} + +pub fn linkSystemLibrary(self: *CompileStep, name: []const u8) void { + self.linkSystemLibraryInner(name, .{}); +} + +pub fn linkSystemLibraryNeeded(self: *CompileStep, name: []const u8) void { + self.linkSystemLibraryInner(name, .{ .needed = true }); +} + +pub fn linkSystemLibraryWeak(self: *CompileStep, name: []const u8) void { + self.linkSystemLibraryInner(name, .{ .weak = true }); +} + +fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { + needed: bool = false, + weak: bool = false, +}) void { + const b = self.step.owner; + if (isLibCLibrary(name)) { + self.linkLibC(); + return; + } + if (isLibCppLibrary(name)) { + self.linkLibCpp(); + return; + } + + self.link_objects.append(.{ + .system_lib = .{ + .name = b.dupe(name), + .needed = opts.needed, + .weak = opts.weak, + .use_pkg_config = .yes, + }, + }) catch @panic("OOM"); +} + +/// Handy when you have many C/C++ source files and want them all to have the same flags. +pub fn addCSourceFiles(self: *CompileStep, files: []const []const u8, flags: []const []const u8) void { + const b = self.step.owner; + const c_source_files = b.allocator.create(CSourceFiles) catch @panic("OOM"); + + const files_copy = b.dupeStrings(files); + const flags_copy = b.dupeStrings(flags); + + c_source_files.* = .{ + .files = files_copy, + .flags = flags_copy, + }; + self.link_objects.append(.{ .c_source_files = c_source_files }) catch @panic("OOM"); +} + +pub fn addCSourceFile(self: *CompileStep, file: []const u8, flags: []const []const u8) void { + self.addCSourceFileSource(.{ + .args = flags, + .source = .{ .path = file }, + }); +} + +pub fn addCSourceFileSource(self: *CompileStep, source: CSourceFile) void { + const b = self.step.owner; + const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM"); + c_source_file.* = source.dupe(b); + self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM"); + source.source.addStepDependencies(&self.step); +} + +pub fn setVerboseLink(self: *CompileStep, value: bool) void { + self.verbose_link = value; +} + +pub fn setVerboseCC(self: *CompileStep, value: bool) void { + self.verbose_cc = value; +} + +pub fn overrideZigLibDir(self: *CompileStep, dir_path: []const u8) void { + const b = self.step.owner; + self.zig_lib_dir = b.dupePath(dir_path); +} + +pub fn setMainPkgPath(self: *CompileStep, dir_path: []const u8) void { + const b = self.step.owner; + self.main_pkg_path = b.dupePath(dir_path); +} + +pub fn setLibCFile(self: *CompileStep, libc_file: ?FileSource) void { + const b = self.step.owner; + self.libc_file = if (libc_file) |f| f.dupe(b) else null; +} + +/// Returns the generated executable, library or object file. +/// To run an executable built with zig build, use `run`, or create an install step and invoke it. +pub fn getOutputSource(self: *CompileStep) FileSource { + return .{ .generated = &self.output_path_source }; +} + +pub fn getOutputDirectorySource(self: *CompileStep) FileSource { + return .{ .generated = &self.output_dirname_source }; +} + +/// Returns the generated import library. This function can only be called for libraries. +pub fn getOutputLibSource(self: *CompileStep) FileSource { + assert(self.kind == .lib); + return .{ .generated = &self.output_lib_path_source }; +} + +/// Returns the generated header file. +/// This function can only be called for libraries or object files which have `emit_h` set. +pub fn getOutputHSource(self: *CompileStep) FileSource { + assert(self.kind != .exe and self.kind != .@"test"); + assert(self.emit_h); + return .{ .generated = &self.output_h_path_source }; +} + +/// Returns the generated PDB file. This function can only be called for Windows and UEFI. +pub fn getOutputPdbSource(self: *CompileStep) FileSource { + // TODO: Is this right? Isn't PDB for *any* PE/COFF file? + assert(self.target.isWindows() or self.target.isUefi()); + return .{ .generated = &self.output_pdb_path_source }; +} + +pub fn addAssemblyFile(self: *CompileStep, path: []const u8) void { + const b = self.step.owner; + self.link_objects.append(.{ + .assembly_file = .{ .path = b.dupe(path) }, + }) catch @panic("OOM"); +} + +pub fn addAssemblyFileSource(self: *CompileStep, source: FileSource) void { + const b = self.step.owner; + const source_duped = source.dupe(b); + self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM"); + source_duped.addStepDependencies(&self.step); +} + +pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void { + self.addObjectFileSource(.{ .path = source_file }); +} + +pub fn addObjectFileSource(self: *CompileStep, source: FileSource) void { + const b = self.step.owner; + self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM"); + source.addStepDependencies(&self.step); +} + +pub fn addObject(self: *CompileStep, obj: *CompileStep) void { + assert(obj.kind == .obj); + self.linkLibraryOrObject(obj); +} + +pub const addSystemIncludeDir = @compileError("deprecated; use addSystemIncludePath"); +pub const addIncludeDir = @compileError("deprecated; use addIncludePath"); +pub const addLibPath = @compileError("deprecated, use addLibraryPath"); +pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath"); + +pub fn addSystemIncludePath(self: *CompileStep, path: []const u8) void { + const b = self.step.owner; + self.include_dirs.append(IncludeDir{ .raw_path_system = b.dupe(path) }) catch @panic("OOM"); +} + +pub fn addIncludePath(self: *CompileStep, path: []const u8) void { + const b = self.step.owner; + self.include_dirs.append(IncludeDir{ .raw_path = b.dupe(path) }) catch @panic("OOM"); +} + +pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) void { + self.step.dependOn(&config_header.step); + self.include_dirs.append(.{ .config_header_step = config_header }) catch @panic("OOM"); +} + +pub fn addLibraryPath(self: *CompileStep, path: []const u8) void { + const b = self.step.owner; + self.lib_paths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); +} + +pub fn addLibraryPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { + self.lib_paths.append(directory_source) catch @panic("OOM"); + directory_source.addStepDependencies(&self.step); +} + +pub fn addRPath(self: *CompileStep, path: []const u8) void { + const b = self.step.owner; + self.rpaths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); +} + +pub fn addRPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { + self.rpaths.append(directory_source) catch @panic("OOM"); + directory_source.addStepDependencies(&self.step); +} + +pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void { + const b = self.step.owner; + self.framework_dirs.append(.{ .path = b.dupe(dir_path) }) catch @panic("OOM"); +} + +pub fn addFrameworkPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { + self.framework_dirs.append(directory_source) catch @panic("OOM"); + directory_source.addStepDependencies(&self.step); +} + +/// Adds a module to be used with `@import` and exposing it in the current +/// package's module table using `name`. +pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void { + const b = cs.step.owner; + cs.modules.put(b.dupe(name), module) catch @panic("OOM"); + + var done = std.AutoHashMap(*Module, void).init(b.allocator); + defer done.deinit(); + cs.addRecursiveBuildDeps(module, &done) catch @panic("OOM"); +} + +/// Adds a module to be used with `@import` without exposing it in the current +/// package's module table. +pub fn addAnonymousModule(cs: *CompileStep, name: []const u8, options: std.Build.CreateModuleOptions) void { + const b = cs.step.owner; + const module = b.createModule(options); + return addModule(cs, name, module); +} + +pub fn addOptions(cs: *CompileStep, module_name: []const u8, options: *OptionsStep) void { + addModule(cs, module_name, options.createModule()); +} + +fn addRecursiveBuildDeps(cs: *CompileStep, module: *Module, done: *std.AutoHashMap(*Module, void)) !void { + if (done.contains(module)) return; + try done.put(module, {}); + module.source_file.addStepDependencies(&cs.step); + for (module.dependencies.values()) |dep| { + try cs.addRecursiveBuildDeps(dep, done); + } +} + +/// If Vcpkg was found on the system, it will be added to include and lib +/// paths for the specified target. +pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void { + const b = self.step.owner; + // Ideally in the Unattempted case we would call the function recursively + // after findVcpkgRoot and have only one switch statement, but the compiler + // cannot resolve the error set. + switch (b.vcpkg_root) { + .unattempted => { + b.vcpkg_root = if (try findVcpkgRoot(b.allocator)) |root| + VcpkgRoot{ .found = root } + else + .not_found; + }, + .not_found => return error.VcpkgNotFound, + .found => {}, + } + + switch (b.vcpkg_root) { + .unattempted => unreachable, + .not_found => return error.VcpkgNotFound, + .found => |root| { + const allocator = b.allocator; + const triplet = try self.target.vcpkgTriplet(allocator, if (linkage == .static) .Static else .Dynamic); + defer b.allocator.free(triplet); + + const include_path = b.pathJoin(&.{ root, "installed", triplet, "include" }); + errdefer allocator.free(include_path); + try self.include_dirs.append(IncludeDir{ .raw_path = include_path }); + + const lib_path = b.pathJoin(&.{ root, "installed", triplet, "lib" }); + try self.lib_paths.append(.{ .path = lib_path }); + + self.vcpkg_bin_path = b.pathJoin(&.{ root, "installed", triplet, "bin" }); + }, + } +} + +pub fn setExecCmd(self: *CompileStep, args: []const ?[]const u8) void { + const b = self.step.owner; + assert(self.kind == .@"test"); + const duped_args = b.allocator.alloc(?[]u8, args.len) catch @panic("OOM"); + for (args, 0..) |arg, i| { + duped_args[i] = if (arg) |a| b.dupe(a) else null; + } + self.exec_cmd_args = duped_args; +} + +fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void { + self.step.dependOn(&other.step); + self.link_objects.append(.{ .other_step = other }) catch @panic("OOM"); + self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM"); + + for (other.installed_headers.items) |install_step| { + self.step.dependOn(install_step); + } +} + +fn appendModuleArgs( + cs: *CompileStep, + zig_args: *ArrayList([]const u8), +) error{OutOfMemory}!void { + const b = cs.step.owner; + // First, traverse the whole dependency graph and give every module a unique name, ideally one + // named after what it's called somewhere in the graph. It will help here to have both a mapping + // from module to name and a set of all the currently-used names. + var mod_names = std.AutoHashMap(*Module, []const u8).init(b.allocator); + var names = std.StringHashMap(void).init(b.allocator); + + var to_name = std.ArrayList(struct { + name: []const u8, + mod: *Module, + }).init(b.allocator); + { + var it = cs.modules.iterator(); + while (it.next()) |kv| { + // While we're traversing the root dependencies, let's make sure that no module names + // have colons in them, since the CLI forbids it. We handle this for transitive + // dependencies further down. + if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { + @panic("Module names cannot contain colons"); + } + try to_name.append(.{ + .name = kv.key_ptr.*, + .mod = kv.value_ptr.*, + }); + } + } + + while (to_name.popOrNull()) |dep| { + if (mod_names.contains(dep.mod)) continue; + + // We'll use this buffer to store the name we decide on + var buf = try b.allocator.alloc(u8, dep.name.len + 32); + // First, try just the exposed dependency name + @memcpy(buf[0..dep.name.len], dep.name); + var name = buf[0..dep.name.len]; + var n: usize = 0; + while (names.contains(name)) { + // If that failed, append an incrementing number to the end + name = std.fmt.bufPrint(buf, "{s}{}", .{ dep.name, n }) catch unreachable; + n += 1; + } + + try mod_names.put(dep.mod, name); + try names.put(name, {}); + + var it = dep.mod.dependencies.iterator(); + while (it.next()) |kv| { + // Same colon-in-name check as above, but for transitive dependencies. + if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { + @panic("Module names cannot contain colons"); + } + try to_name.append(.{ + .name = kv.key_ptr.*, + .mod = kv.value_ptr.*, + }); + } + } + + // Since the module names given to the CLI are based off of the exposed names, we already know + // that none of the CLI names have colons in them, so there's no need to check that explicitly. + + // Every module in the graph is now named; output their definitions + { + var it = mod_names.iterator(); + while (it.next()) |kv| { + const mod = kv.key_ptr.*; + const name = kv.value_ptr.*; + + const deps_str = try constructDepString(b.allocator, mod_names, mod.dependencies); + const src = mod.builder.pathFromRoot(mod.source_file.getPath(mod.builder)); + try zig_args.append("--mod"); + try zig_args.append(try std.fmt.allocPrint(b.allocator, "{s}:{s}:{s}", .{ name, deps_str, src })); + } + } + + // Lastly, output the root dependencies + const deps_str = try constructDepString(b.allocator, mod_names, cs.modules); + if (deps_str.len > 0) { + try zig_args.append("--deps"); + try zig_args.append(deps_str); + } +} + +fn constructDepString( + allocator: std.mem.Allocator, + mod_names: std.AutoHashMap(*Module, []const u8), + deps: std.StringArrayHashMap(*Module), +) ![]const u8 { + var deps_str = std.ArrayList(u8).init(allocator); + var it = deps.iterator(); + while (it.next()) |kv| { + const expose = kv.key_ptr.*; + const name = mod_names.get(kv.value_ptr.*).?; + if (std.mem.eql(u8, expose, name)) { + try deps_str.writer().print("{s},", .{name}); + } else { + try deps_str.writer().print("{s}={s},", .{ expose, name }); + } + } + if (deps_str.items.len > 0) { + return deps_str.items[0 .. deps_str.items.len - 1]; // omit trailing comma + } else { + return ""; + } +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + const b = step.owner; + const self = @fieldParentPtr(CompileStep, "step", step); + + if (self.root_src == null and self.link_objects.items.len == 0) { + return step.fail("the linker needs one or more objects to link", .{}); + } + + var zig_args = ArrayList([]const u8).init(b.allocator); + defer zig_args.deinit(); + + try zig_args.append(b.zig_exe); + + const cmd = switch (self.kind) { + .lib => "build-lib", + .exe => "build-exe", + .obj => "build-obj", + .@"test" => "test", + }; + try zig_args.append(cmd); + + if (b.reference_trace) |some| { + try zig_args.append(try std.fmt.allocPrint(b.allocator, "-freference-trace={d}", .{some})); + } + + try addFlag(&zig_args, "LLVM", self.use_llvm); + try addFlag(&zig_args, "LLD", self.use_lld); + + if (self.target.ofmt) |ofmt| { + try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)})); + } + + if (self.entry_symbol_name) |entry| { + try zig_args.append("--entry"); + try zig_args.append(entry); + } + + { + var it = self.force_undefined_symbols.keyIterator(); + while (it.next()) |symbol_name| { + try zig_args.append("--force_undefined"); + try zig_args.append(symbol_name.*); + } + } + + if (self.stack_size) |stack_size| { + try zig_args.append("--stack"); + try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size})); + } + + if (self.root_src) |root_src| try zig_args.append(root_src.getPath(b)); + + // We will add link objects from transitive dependencies, but we want to keep + // all link objects in the same order provided. + // This array is used to keep self.link_objects immutable. + var transitive_deps: TransitiveDeps = .{ + .link_objects = ArrayList(LinkObject).init(b.allocator), + .seen_system_libs = StringHashMap(void).init(b.allocator), + .seen_steps = std.AutoHashMap(*const Step, void).init(b.allocator), + .is_linking_libcpp = self.is_linking_libcpp, + .is_linking_libc = self.is_linking_libc, + .frameworks = &self.frameworks, + }; + + try transitive_deps.seen_steps.put(&self.step, {}); + try transitive_deps.add(self.link_objects.items); + + var prev_has_extra_flags = false; + + for (transitive_deps.link_objects.items) |link_object| { + switch (link_object) { + .static_path => |static_path| try zig_args.append(static_path.getPath(b)), + + .other_step => |other| switch (other.kind) { + .exe => @panic("Cannot link with an executable build artifact"), + .@"test" => @panic("Cannot link with a test"), + .obj => { + try zig_args.append(other.getOutputSource().getPath(b)); + }, + .lib => l: { + if (self.isStaticLibrary() and other.isStaticLibrary()) { + // Avoid putting a static library inside a static library. + break :l; + } + + const full_path_lib = other.getOutputLibSource().getPath(b); + try zig_args.append(full_path_lib); + + if (other.linkage == Linkage.dynamic and !self.target.isWindows()) { + if (fs.path.dirname(full_path_lib)) |dirname| { + try zig_args.append("-rpath"); + try zig_args.append(dirname); + } + } + }, + }, + + .system_lib => |system_lib| { + const prefix: []const u8 = prefix: { + if (system_lib.needed) break :prefix "-needed-l"; + if (system_lib.weak) break :prefix "-weak-l"; + break :prefix "-l"; + }; + switch (system_lib.use_pkg_config) { + .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })), + .yes, .force => { + if (self.runPkgConfig(system_lib.name)) |args| { + try zig_args.appendSlice(args); + } else |err| switch (err) { + error.PkgConfigInvalidOutput, + error.PkgConfigCrashed, + error.PkgConfigFailed, + error.PkgConfigNotInstalled, + error.PackageNotFound, + => switch (system_lib.use_pkg_config) { + .yes => { + // pkg-config failed, so fall back to linking the library + // by name directly. + try zig_args.append(b.fmt("{s}{s}", .{ + prefix, + system_lib.name, + })); + }, + .force => { + panic("pkg-config failed for library {s}", .{system_lib.name}); + }, + .no => unreachable, + }, + + else => |e| return e, + } + }, + } + }, + + .assembly_file => |asm_file| { + if (prev_has_extra_flags) { + try zig_args.append("-extra-cflags"); + try zig_args.append("--"); + prev_has_extra_flags = false; + } + try zig_args.append(asm_file.getPath(b)); + }, + + .c_source_file => |c_source_file| { + if (c_source_file.args.len == 0) { + if (prev_has_extra_flags) { + try zig_args.append("-cflags"); + try zig_args.append("--"); + prev_has_extra_flags = false; + } + } else { + try zig_args.append("-cflags"); + for (c_source_file.args) |arg| { + try zig_args.append(arg); + } + try zig_args.append("--"); + } + try zig_args.append(c_source_file.source.getPath(b)); + }, + + .c_source_files => |c_source_files| { + if (c_source_files.flags.len == 0) { + if (prev_has_extra_flags) { + try zig_args.append("-cflags"); + try zig_args.append("--"); + prev_has_extra_flags = false; + } + } else { + try zig_args.append("-cflags"); + for (c_source_files.flags) |flag| { + try zig_args.append(flag); + } + try zig_args.append("--"); + } + for (c_source_files.files) |file| { + try zig_args.append(b.pathFromRoot(file)); + } + }, + } + } + + if (transitive_deps.is_linking_libcpp) { + try zig_args.append("-lc++"); + } + + if (transitive_deps.is_linking_libc) { + try zig_args.append("-lc"); + } + + if (self.image_base) |image_base| { + try zig_args.append("--image-base"); + try zig_args.append(b.fmt("0x{x}", .{image_base})); + } + + if (self.filter) |filter| { + try zig_args.append("--test-filter"); + try zig_args.append(filter); + } + + if (self.test_evented_io) { + try zig_args.append("--test-evented-io"); + } + + if (self.test_runner) |test_runner| { + try zig_args.append("--test-runner"); + try zig_args.append(b.pathFromRoot(test_runner)); + } + + for (b.debug_log_scopes) |log_scope| { + try zig_args.append("--debug-log"); + try zig_args.append(log_scope); + } + + if (b.debug_compile_errors) { + try zig_args.append("--debug-compile-errors"); + } + + if (b.verbose_cimport) try zig_args.append("--verbose-cimport"); + if (b.verbose_air) try zig_args.append("--verbose-air"); + if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path})); + if (b.verbose_llvm_bc) |path| try zig_args.append(b.fmt("--verbose-llvm-bc={s}", .{path})); + if (b.verbose_link or self.verbose_link) try zig_args.append("--verbose-link"); + if (b.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc"); + if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features"); + + if (self.emit_analysis.getArg(b, "emit-analysis")) |arg| try zig_args.append(arg); + if (self.emit_asm.getArg(b, "emit-asm")) |arg| try zig_args.append(arg); + if (self.emit_bin.getArg(b, "emit-bin")) |arg| try zig_args.append(arg); + if (self.emit_docs.getArg(b, "emit-docs")) |arg| try zig_args.append(arg); + if (self.emit_implib.getArg(b, "emit-implib")) |arg| try zig_args.append(arg); + if (self.emit_llvm_bc.getArg(b, "emit-llvm-bc")) |arg| try zig_args.append(arg); + if (self.emit_llvm_ir.getArg(b, "emit-llvm-ir")) |arg| try zig_args.append(arg); + + if (self.emit_h) try zig_args.append("-femit-h"); + + try addFlag(&zig_args, "strip", self.strip); + try addFlag(&zig_args, "unwind-tables", self.unwind_tables); + + if (self.dwarf_format) |dwarf_format| { + try zig_args.append(switch (dwarf_format) { + .@"32" => "-gdwarf32", + .@"64" => "-gdwarf64", + }); + } + + switch (self.compress_debug_sections) { + .none => {}, + .zlib => try zig_args.append("--compress-debug-sections=zlib"), + } + + if (self.link_eh_frame_hdr) { + try zig_args.append("--eh-frame-hdr"); + } + if (self.link_emit_relocs) { + try zig_args.append("--emit-relocs"); + } + if (self.link_function_sections) { + try zig_args.append("-ffunction-sections"); + } + if (self.link_gc_sections) |x| { + try zig_args.append(if (x) "--gc-sections" else "--no-gc-sections"); + } + if (!self.linker_dynamicbase) { + try zig_args.append("--no-dynamicbase"); + } + if (self.linker_allow_shlib_undefined) |x| { + try zig_args.append(if (x) "-fallow-shlib-undefined" else "-fno-allow-shlib-undefined"); + } + if (self.link_z_notext) { + try zig_args.append("-z"); + try zig_args.append("notext"); + } + if (!self.link_z_relro) { + try zig_args.append("-z"); + try zig_args.append("norelro"); + } + if (self.link_z_lazy) { + try zig_args.append("-z"); + try zig_args.append("lazy"); + } + if (self.link_z_common_page_size) |size| { + try zig_args.append("-z"); + try zig_args.append(b.fmt("common-page-size={d}", .{size})); + } + if (self.link_z_max_page_size) |size| { + try zig_args.append("-z"); + try zig_args.append(b.fmt("max-page-size={d}", .{size})); + } + + if (self.libc_file) |libc_file| { + try zig_args.append("--libc"); + try zig_args.append(libc_file.getPath(b)); + } else if (b.libc_file) |libc_file| { + try zig_args.append("--libc"); + try zig_args.append(libc_file); + } + + switch (self.optimize) { + .Debug => {}, // Skip since it's the default. + else => try zig_args.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), + } + + try zig_args.append("--cache-dir"); + try zig_args.append(b.cache_root.path orelse "."); + + try zig_args.append("--global-cache-dir"); + try zig_args.append(b.global_cache_root.path orelse "."); + + try zig_args.append("--name"); + try zig_args.append(self.name); + + if (self.linkage) |some| switch (some) { + .dynamic => try zig_args.append("-dynamic"), + .static => try zig_args.append("-static"), + }; + if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic) { + if (self.version) |version| { + try zig_args.append("--version"); + try zig_args.append(b.fmt("{}", .{version})); + } + + if (self.target.isDarwin()) { + const install_name = self.install_name orelse b.fmt("@rpath/{s}{s}{s}", .{ + self.target.libPrefix(), + self.name, + self.target.dynamicLibSuffix(), + }); + try zig_args.append("-install_name"); + try zig_args.append(install_name); + } + } + + if (self.entitlements) |entitlements| { + try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); + } + if (self.pagezero_size) |pagezero_size| { + const size = try std.fmt.allocPrint(b.allocator, "{x}", .{pagezero_size}); + try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); + } + if (self.search_strategy) |strat| switch (strat) { + .paths_first => try zig_args.append("-search_paths_first"), + .dylibs_first => try zig_args.append("-search_dylibs_first"), + }; + if (self.headerpad_size) |headerpad_size| { + const size = try std.fmt.allocPrint(b.allocator, "{x}", .{headerpad_size}); + try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); + } + if (self.headerpad_max_install_names) { + try zig_args.append("-headerpad_max_install_names"); + } + if (self.dead_strip_dylibs) { + try zig_args.append("-dead_strip_dylibs"); + } + + try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt); + try addFlag(&zig_args, "single-threaded", self.single_threaded); + if (self.disable_stack_probing) { + try zig_args.append("-fno-stack-check"); + } + try addFlag(&zig_args, "stack-protector", self.stack_protector); + if (self.red_zone) |red_zone| { + if (red_zone) { + try zig_args.append("-mred-zone"); + } else { + try zig_args.append("-mno-red-zone"); + } + } + try addFlag(&zig_args, "omit-frame-pointer", self.omit_frame_pointer); + try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns); + + if (self.disable_sanitize_c) { + try zig_args.append("-fno-sanitize-c"); + } + if (self.sanitize_thread) { + try zig_args.append("-fsanitize-thread"); + } + if (self.rdynamic) { + try zig_args.append("-rdynamic"); + } + if (self.import_memory) { + try zig_args.append("--import-memory"); + } + if (self.import_symbols) { + try zig_args.append("--import-symbols"); + } + if (self.import_table) { + try zig_args.append("--import-table"); + } + if (self.export_table) { + try zig_args.append("--export-table"); + } + if (self.initial_memory) |initial_memory| { + try zig_args.append(b.fmt("--initial-memory={d}", .{initial_memory})); + } + if (self.max_memory) |max_memory| { + try zig_args.append(b.fmt("--max-memory={d}", .{max_memory})); + } + if (self.shared_memory) { + try zig_args.append("--shared-memory"); + } + if (self.global_base) |global_base| { + try zig_args.append(b.fmt("--global-base={d}", .{global_base})); + } + + if (self.code_model != .default) { + try zig_args.append("-mcmodel"); + try zig_args.append(@tagName(self.code_model)); + } + if (self.wasi_exec_model) |model| { + try zig_args.append(b.fmt("-mexec-model={s}", .{@tagName(model)})); + } + for (self.export_symbol_names) |symbol_name| { + try zig_args.append(b.fmt("--export={s}", .{symbol_name})); + } + + if (!self.target.isNative()) { + try zig_args.appendSlice(&.{ + "-target", try self.target.zigTriple(b.allocator), + "-mcpu", try std.Build.serializeCpu(b.allocator, self.target.getCpu()), + }); + + if (self.target.dynamic_linker.get()) |dynamic_linker| { + try zig_args.append("--dynamic-linker"); + try zig_args.append(dynamic_linker); + } + } + + if (self.linker_script) |linker_script| { + try zig_args.append("--script"); + try zig_args.append(linker_script.getPath(b)); + } + + if (self.version_script) |version_script| { + try zig_args.append("--version-script"); + try zig_args.append(b.pathFromRoot(version_script)); + } + + if (self.kind == .@"test") { + if (self.exec_cmd_args) |exec_cmd_args| { + for (exec_cmd_args) |cmd_arg| { + if (cmd_arg) |arg| { + try zig_args.append("--test-cmd"); + try zig_args.append(arg); + } else { + try zig_args.append("--test-cmd-bin"); + } + } + } + } + + try self.appendModuleArgs(&zig_args); + + for (self.include_dirs.items) |include_dir| { + switch (include_dir) { + .raw_path => |include_path| { + try zig_args.append("-I"); + try zig_args.append(b.pathFromRoot(include_path)); + }, + .raw_path_system => |include_path| { + if (b.sysroot != null) { + try zig_args.append("-iwithsysroot"); + } else { + try zig_args.append("-isystem"); + } + + const resolved_include_path = b.pathFromRoot(include_path); + + const common_include_path = if (builtin.os.tag == .windows and b.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: { + // We need to check for disk designator and strip it out from dir path so + // that zig/clang can concat resolved_include_path with sysroot. + const disk_designator = fs.path.diskDesignatorWindows(resolved_include_path); + + if (mem.indexOf(u8, resolved_include_path, disk_designator)) |where| { + break :blk resolved_include_path[where + disk_designator.len ..]; + } + + break :blk resolved_include_path; + } else resolved_include_path; + + try zig_args.append(common_include_path); + }, + .other_step => |other| { + if (other.emit_h) { + const h_path = other.getOutputHSource().getPath(b); + try zig_args.append("-isystem"); + try zig_args.append(fs.path.dirname(h_path).?); + } + if (other.installed_headers.items.len > 0) { + try zig_args.append("-I"); + try zig_args.append(b.pathJoin(&.{ + other.step.owner.install_prefix, "include", + })); + } + }, + .config_header_step => |config_header| { + const full_file_path = config_header.output_file.path.?; + const header_dir_path = full_file_path[0 .. full_file_path.len - config_header.include_path.len]; + try zig_args.appendSlice(&.{ "-I", header_dir_path }); + }, + } + } + + for (self.c_macros.items) |c_macro| { + try zig_args.append("-D"); + try zig_args.append(c_macro); + } + + try zig_args.ensureUnusedCapacity(2 * self.lib_paths.items.len); + for (self.lib_paths.items) |lib_path| { + zig_args.appendAssumeCapacity("-L"); + zig_args.appendAssumeCapacity(lib_path.getPath2(b, step)); + } + + try zig_args.ensureUnusedCapacity(2 * self.rpaths.items.len); + for (self.rpaths.items) |rpath| { + zig_args.appendAssumeCapacity("-rpath"); + + if (self.target_info.target.isDarwin()) switch (rpath) { + .path => |path| { + // On Darwin, we should not try to expand special runtime paths such as + // * @executable_path + // * @loader_path + if (mem.startsWith(u8, path, "@executable_path") or + mem.startsWith(u8, path, "@loader_path")) + { + zig_args.appendAssumeCapacity(path); + continue; + } + }, + .generated => {}, + }; + + zig_args.appendAssumeCapacity(rpath.getPath2(b, step)); + } + + for (self.framework_dirs.items) |directory_source| { + if (b.sysroot != null) { + try zig_args.append("-iframeworkwithsysroot"); + } else { + try zig_args.append("-iframework"); + } + try zig_args.append(directory_source.getPath2(b, step)); + try zig_args.append("-F"); + try zig_args.append(directory_source.getPath2(b, step)); + } + + { + var it = self.frameworks.iterator(); + while (it.next()) |entry| { + const name = entry.key_ptr.*; + const info = entry.value_ptr.*; + if (info.needed) { + try zig_args.append("-needed_framework"); + } else if (info.weak) { + try zig_args.append("-weak_framework"); + } else { + try zig_args.append("-framework"); + } + try zig_args.append(name); + } + } + + if (b.sysroot) |sysroot| { + try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot }); + } + + for (b.search_prefixes.items) |search_prefix| { + var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| { + return step.fail("unable to open prefix directory '{s}': {s}", .{ + search_prefix, @errorName(err), + }); + }; + defer prefix_dir.close(); + + // Avoid passing -L and -I flags for nonexistent directories. + // This prevents a warning, that should probably be upgraded to an error in Zig's + // CLI parsing code, when the linker sees an -L directory that does not exist. + + if (prefix_dir.accessZ("lib", .{})) |_| { + try zig_args.appendSlice(&.{ + "-L", try fs.path.join(b.allocator, &.{ search_prefix, "lib" }), + }); + } else |err| switch (err) { + error.FileNotFound => {}, + else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{ + search_prefix, @errorName(e), + }), + } + + if (prefix_dir.accessZ("include", .{})) |_| { + try zig_args.appendSlice(&.{ + "-I", try fs.path.join(b.allocator, &.{ search_prefix, "include" }), + }); + } else |err| switch (err) { + error.FileNotFound => {}, + else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{ + search_prefix, @errorName(e), + }), + } + } + + try addFlag(&zig_args, "valgrind", self.valgrind_support); + try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath); + try addFlag(&zig_args, "build-id", self.build_id); + + if (self.zig_lib_dir) |dir| { + try zig_args.append("--zig-lib-dir"); + try zig_args.append(b.pathFromRoot(dir)); + } else if (b.zig_lib_dir) |dir| { + try zig_args.append("--zig-lib-dir"); + try zig_args.append(dir); + } + + if (self.main_pkg_path) |dir| { + try zig_args.append("--main-pkg-path"); + try zig_args.append(b.pathFromRoot(dir)); + } + + try addFlag(&zig_args, "PIC", self.force_pic); + try addFlag(&zig_args, "PIE", self.pie); + try addFlag(&zig_args, "lto", self.want_lto); + + if (self.subsystem) |subsystem| { + try zig_args.append("--subsystem"); + try zig_args.append(switch (subsystem) { + .Console => "console", + .Windows => "windows", + .Posix => "posix", + .Native => "native", + .EfiApplication => "efi_application", + .EfiBootServiceDriver => "efi_boot_service_driver", + .EfiRom => "efi_rom", + .EfiRuntimeDriver => "efi_runtime_driver", + }); + } + + try zig_args.append("--listen=-"); + + // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux + // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and + // pass that to zig, e.g. via 'zig build-lib @args.rsp' + // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html + var args_length: usize = 0; + for (zig_args.items) |arg| { + args_length += arg.len + 1; // +1 to account for null terminator + } + if (args_length >= 30 * 1024) { + try b.cache_root.handle.makePath("args"); + + const args_to_escape = zig_args.items[2..]; + var escaped_args = try ArrayList([]const u8).initCapacity(b.allocator, args_to_escape.len); + arg_blk: for (args_to_escape) |arg| { + for (arg, 0..) |c, arg_idx| { + if (c == '\\' or c == '"') { + // Slow path for arguments that need to be escaped. We'll need to allocate and copy + var escaped = try ArrayList(u8).initCapacity(b.allocator, arg.len + 1); + const writer = escaped.writer(); + try writer.writeAll(arg[0..arg_idx]); + for (arg[arg_idx..]) |to_escape| { + if (to_escape == '\\' or to_escape == '"') try writer.writeByte('\\'); + try writer.writeByte(to_escape); + } + escaped_args.appendAssumeCapacity(escaped.items); + continue :arg_blk; + } + } + escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument + } + + // Write the args to zig-cache/args/ to avoid conflicts with + // other zig build commands running in parallel. + const partially_quoted = try std.mem.join(b.allocator, "\" \"", escaped_args.items); + const args = try std.mem.concat(b.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" }); + + var args_hash: [Sha256.digest_length]u8 = undefined; + Sha256.hash(args, &args_hash, .{}); + var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined; + _ = try std.fmt.bufPrint( + &args_hex_hash, + "{s}", + .{std.fmt.fmtSliceHexLower(&args_hash)}, + ); + + const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash; + try b.cache_root.handle.writeFile(args_file, args); + + const resolved_args_file = try mem.concat(b.allocator, u8, &.{ + "@", + try b.cache_root.join(b.allocator, &.{args_file}), + }); + + zig_args.shrinkRetainingCapacity(2); + try zig_args.append(resolved_args_file); + } + + const output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) { + error.NeedCompileErrorCheck => { + assert(self.expect_errors.len != 0); + try checkCompileErrors(self); + return; + }, + else => |e| return e, + }; + const output_dir = fs.path.dirname(output_bin_path).?; + + // Update generated files + { + self.output_dirname_source.path = output_dir; + + self.output_path_source.path = b.pathJoin( + &.{ output_dir, self.out_filename }, + ); + + if (self.kind == .lib) { + self.output_lib_path_source.path = b.pathJoin( + &.{ output_dir, self.out_lib_filename }, + ); + } + + if (self.emit_h) { + self.output_h_path_source.path = b.pathJoin( + &.{ output_dir, self.out_h_filename }, + ); + } + + if (self.target.isWindows() or self.target.isUefi()) { + self.output_pdb_path_source.path = b.pathJoin( + &.{ output_dir, self.out_pdb_filename }, + ); + } + } + + if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and + self.version != null and self.target.wantSharedLibSymLinks()) + { + try doAtomicSymLinks( + step, + self.getOutputSource().getPath(b), + self.major_only_filename.?, + self.name_only_filename.?, + ); + } +} + +fn isLibCLibrary(name: []const u8) bool { + const libc_libraries = [_][]const u8{ "c", "m", "dl", "rt", "pthread" }; + for (libc_libraries) |libc_lib_name| { + if (mem.eql(u8, name, libc_lib_name)) + return true; + } + return false; +} + +fn isLibCppLibrary(name: []const u8) bool { + const libcpp_libraries = [_][]const u8{ "c++", "stdc++" }; + for (libcpp_libraries) |libcpp_lib_name| { + if (mem.eql(u8, name, libcpp_lib_name)) + return true; + } + return false; +} + +/// Returned slice must be freed by the caller. +fn findVcpkgRoot(allocator: Allocator) !?[]const u8 { + const appdata_path = try fs.getAppDataDir(allocator, "vcpkg"); + defer allocator.free(appdata_path); + + const path_file = try fs.path.join(allocator, &[_][]const u8{ appdata_path, "vcpkg.path.txt" }); + defer allocator.free(path_file); + + const file = fs.cwd().openFile(path_file, .{}) catch return null; + defer file.close(); + + const size = @intCast(usize, try file.getEndPos()); + const vcpkg_path = try allocator.alloc(u8, size); + const size_read = try file.read(vcpkg_path); + std.debug.assert(size == size_read); + + return vcpkg_path; +} + +pub fn doAtomicSymLinks( + step: *Step, + output_path: []const u8, + filename_major_only: []const u8, + filename_name_only: []const u8, +) !void { + const arena = step.owner.allocator; + const out_dir = fs.path.dirname(output_path) orelse "."; + const out_basename = fs.path.basename(output_path); + // sym link for libfoo.so.1 to libfoo.so.1.2.3 + const major_only_path = try fs.path.join(arena, &.{ out_dir, filename_major_only }); + fs.atomicSymLink(arena, out_basename, major_only_path) catch |err| { + return step.fail("unable to symlink {s} -> {s}: {s}", .{ + major_only_path, out_basename, @errorName(err), + }); + }; + // sym link for libfoo.so to libfoo.so.1 + const name_only_path = try fs.path.join(arena, &.{ out_dir, filename_name_only }); + fs.atomicSymLink(arena, filename_major_only, name_only_path) catch |err| { + return step.fail("Unable to symlink {s} -> {s}: {s}", .{ + name_only_path, filename_major_only, @errorName(err), + }); + }; +} + +fn execPkgConfigList(self: *std.Build, out_code: *u8) (PkgConfigError || ExecError)![]const PkgConfigPkg { + const stdout = try self.execAllowFail(&[_][]const u8{ "pkg-config", "--list-all" }, out_code, .Ignore); + var list = ArrayList(PkgConfigPkg).init(self.allocator); + errdefer list.deinit(); + var line_it = mem.tokenize(u8, stdout, "\r\n"); + while (line_it.next()) |line| { + if (mem.trim(u8, line, " \t").len == 0) continue; + var tok_it = mem.tokenize(u8, line, " \t"); + try list.append(PkgConfigPkg{ + .name = tok_it.next() orelse return error.PkgConfigInvalidOutput, + .desc = tok_it.rest(), + }); + } + return list.toOwnedSlice(); +} + +fn getPkgConfigList(self: *std.Build) ![]const PkgConfigPkg { + if (self.pkg_config_pkg_list) |res| { + return res; + } + var code: u8 = undefined; + if (execPkgConfigList(self, &code)) |list| { + self.pkg_config_pkg_list = list; + return list; + } else |err| { + const result = switch (err) { + error.ProcessTerminated => error.PkgConfigCrashed, + error.ExecNotSupported => error.PkgConfigFailed, + error.ExitCodeFailure => error.PkgConfigFailed, + error.FileNotFound => error.PkgConfigNotInstalled, + error.InvalidName => error.PkgConfigNotInstalled, + error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, + else => return err, + }; + self.pkg_config_pkg_list = result; + return result; + } +} + +fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void { + const cond = opt orelse return; + try args.ensureUnusedCapacity(1); + if (cond) { + args.appendAssumeCapacity("-f" ++ name); + } else { + args.appendAssumeCapacity("-fno-" ++ name); + } +} + +const TransitiveDeps = struct { + link_objects: ArrayList(LinkObject), + seen_system_libs: StringHashMap(void), + seen_steps: std.AutoHashMap(*const Step, void), + is_linking_libcpp: bool, + is_linking_libc: bool, + frameworks: *StringHashMap(FrameworkLinkInfo), + + fn add(td: *TransitiveDeps, link_objects: []const LinkObject) !void { + try td.link_objects.ensureUnusedCapacity(link_objects.len); + + for (link_objects) |link_object| { + try td.link_objects.append(link_object); + switch (link_object) { + .other_step => |other| try addInner(td, other, other.isDynamicLibrary()), + else => {}, + } + } + } + + fn addInner(td: *TransitiveDeps, other: *CompileStep, dyn: bool) !void { + // Inherit dependency on libc and libc++ + td.is_linking_libcpp = td.is_linking_libcpp or other.is_linking_libcpp; + td.is_linking_libc = td.is_linking_libc or other.is_linking_libc; + + // Inherit dependencies on darwin frameworks + if (!dyn) { + var it = other.frameworks.iterator(); + while (it.next()) |framework| { + try td.frameworks.put(framework.key_ptr.*, framework.value_ptr.*); + } + } + + // Inherit dependencies on system libraries and static libraries. + for (other.link_objects.items) |other_link_object| { + switch (other_link_object) { + .system_lib => |system_lib| { + if ((try td.seen_system_libs.fetchPut(system_lib.name, {})) != null) + continue; + + if (dyn) + continue; + + try td.link_objects.append(other_link_object); + }, + .other_step => |inner_other| { + if ((try td.seen_steps.fetchPut(&inner_other.step, {})) != null) + continue; + + if (!dyn) + try td.link_objects.append(other_link_object); + + try addInner(td, inner_other, dyn or inner_other.isDynamicLibrary()); + }, + else => continue, + } + } + } +}; + +fn checkCompileErrors(self: *CompileStep) !void { + // Clear this field so that it does not get printed by the build runner. + const actual_eb = self.step.result_error_bundle; + self.step.result_error_bundle = std.zig.ErrorBundle.empty; + + const arena = self.step.owner.allocator; + + var actual_stderr_list = std.ArrayList(u8).init(arena); + try actual_eb.renderToWriter(.{ + .ttyconf = .no_color, + .include_reference_trace = false, + .include_source_line = false, + }, actual_stderr_list.writer()); + const actual_stderr = try actual_stderr_list.toOwnedSlice(); + + // Render the expected lines into a string that we can compare verbatim. + var expected_generated = std.ArrayList(u8).init(arena); + + var actual_line_it = mem.split(u8, actual_stderr, "\n"); + for (self.expect_errors) |expect_line| { + const actual_line = actual_line_it.next() orelse { + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + continue; + }; + if (mem.endsWith(u8, actual_line, expect_line)) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + if (mem.startsWith(u8, expect_line, ":?:?: ")) { + if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) { + try expected_generated.appendSlice(actual_line); + try expected_generated.append('\n'); + continue; + } + } + try expected_generated.appendSlice(expect_line); + try expected_generated.append('\n'); + } + + if (mem.eql(u8, expected_generated.items, actual_stderr)) return; + + // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile + return self.step.fail( + \\ + \\========= expected: ===================== + \\{s} + \\========= but found: ==================== + \\{s} + \\========================================= + , .{ expected_generated.items, actual_stderr }); +} diff --git a/lib/std/Build/Step/ConfigHeader.zig b/lib/std/Build/Step/ConfigHeader.zig new file mode 100644 index 0000000000000000000000000000000000000000..6bfe28ae62576463649d9dd11dafe4d2ba0b52ee --- /dev/null +++ b/lib/std/Build/Step/ConfigHeader.zig @@ -0,0 +1,437 @@ +const std = @import("std"); +const ConfigHeaderStep = @This(); +const Step = std.Build.Step; + +pub const Style = union(enum) { + /// The configure format supported by autotools. It uses `#undef foo` to + /// mark lines that can be substituted with different values. + autoconf: std.Build.FileSource, + /// The configure format supported by CMake. It uses `@@FOO@@` and + /// `#cmakedefine` for template substitution. + cmake: std.Build.FileSource, + /// 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 getFileSource(style: Style) ?std.Build.FileSource { + switch (style) { + .autoconf, .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, +}; + +step: Step, +values: std.StringArrayHashMap(Value), +output_file: std.Build.GeneratedFile, + +style: Style, +max_bytes: usize, +include_path: []const u8, + +pub const base_id: Step.Id = .config_header; + +pub const Options = struct { + style: Style = .blank, + max_bytes: usize = 2 * 1024 * 1024, + include_path: ?[]const u8 = null, + first_ret_addr: ?usize = null, +}; + +pub fn create(owner: *std.Build, options: Options) *ConfigHeaderStep { + const self = owner.allocator.create(ConfigHeaderStep) catch @panic("OOM"); + + var include_path: []const u8 = "config.h"; + + if (options.style.getFileSource()) |s| switch (s) { + .path => |p| { + const basename = std.fs.path.basename(p); + if (std.mem.endsWith(u8, basename, ".h.in")) { + include_path = basename[0 .. basename.len - 3]; + } + }, + else => {}, + }; + + if (options.include_path) |p| { + include_path = p; + } + + const name = if (options.style.getFileSource()) |s| + owner.fmt("configure {s} header {s} to {s}", .{ + @tagName(options.style), s.getDisplayName(), include_path, + }) + else + owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path }); + + self.* = .{ + .step = Step.init(.{ + .id = base_id, + .name = name, + .owner = owner, + .makeFn = make, + .first_ret_addr = options.first_ret_addr orelse @returnAddress(), + }), + .style = options.style, + .values = std.StringArrayHashMap(Value).init(owner.allocator), + + .max_bytes = options.max_bytes, + .include_path = include_path, + .output_file = .{ .step = &self.step }, + }; + + return self; +} + +pub fn addValues(self: *ConfigHeaderStep, values: anytype) void { + return addValuesInner(self, values) catch @panic("OOM"); +} + +pub fn getFileSource(self: *ConfigHeaderStep) std.Build.FileSource { + return .{ .generated = &self.output_file }; +} + +fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void { + inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| { + try putValue(self, field.name, field.type, @field(values, field.name)); + } +} + +fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v: T) !void { + switch (@typeInfo(T)) { + .Null => { + try self.values.put(field_name, .undef); + }, + .Void => { + try self.values.put(field_name, .defined); + }, + .Bool => { + try self.values.put(field_name, .{ .boolean = v }); + }, + .Int => { + try self.values.put(field_name, .{ .int = v }); + }, + .ComptimeInt => { + try self.values.put(field_name, .{ .int = v }); + }, + .EnumLiteral => { + try self.values.put(field_name, .{ .ident = @tagName(v) }); + }, + .Optional => { + if (v) |x| { + return putValue(self, field_name, @TypeOf(x), x); + } else { + try self.values.put(field_name, .undef); + } + }, + .Pointer => |ptr| { + switch (@typeInfo(ptr.child)) { + .Array => |array| { + if (ptr.size == .One and array.child == u8) { + try self.values.put(field_name, .{ .string = v }); + return; + } + }, + .Int => { + if (ptr.size == .Slice and ptr.child == u8) { + try self.values.put(field_name, .{ .string = v }); + return; + } + }, + else => {}, + } + + @compileError("unsupported ConfigHeaderStep value type: " ++ @typeName(T)); + }, + else => @compileError("unsupported ConfigHeaderStep value type: " ++ @typeName(T)), + } +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const b = step.owner; + const self = @fieldParentPtr(ConfigHeaderStep, "step", step); + const gpa = b.allocator; + const arena = b.allocator; + + var man = b.cache.obtain(); + defer man.deinit(); + + // Random bytes to make ConfigHeaderStep unique. Refresh this with new + // random bytes when ConfigHeaderStep implementation is modified in a + // non-backwards-compatible way. + man.hash.add(@as(u32, 0xdef08d23)); + + var output = std.ArrayList(u8).init(gpa); + defer output.deinit(); + + const header_text = "This file was generated by ConfigHeaderStep using the Zig Build System."; + const c_generated_line = "/* " ++ header_text ++ " */\n"; + const asm_generated_line = "; " ++ header_text ++ "\n"; + + switch (self.style) { + .autoconf => |file_source| { + try output.appendSlice(c_generated_line); + const src_path = file_source.getPath(b); + const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); + try render_autoconf(step, contents, &output, self.values, src_path); + }, + .cmake => |file_source| { + try output.appendSlice(c_generated_line); + const src_path = file_source.getPath(b); + const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); + try render_cmake(step, contents, &output, self.values, src_path); + }, + .blank => { + try output.appendSlice(c_generated_line); + try render_blank(&output, self.values, self.include_path); + }, + .nasm => { + try output.appendSlice(asm_generated_line); + try render_nasm(&output, self.values); + }, + } + + man.hash.addBytes(output.items); + + if (try step.cacheHit(&man)) { + const digest = man.final(); + self.output_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, self.include_path, + }); + return; + } + + const digest = man.final(); + + // If output_path has directory parts, deal with them. Example: + // output_dir is zig-cache/o/HASH + // output_path is libavutil/avconfig.h + // We want to open directory zig-cache/o/HASH/libavutil/ + // but keep output_dir as zig-cache/o/HASH for -I include + const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, self.include_path }); + const sub_path_dirname = std.fs.path.dirname(sub_path).?; + + b.cache_root.handle.makePath(sub_path_dirname) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, sub_path_dirname, @errorName(err), + }); + }; + + b.cache_root.handle.writeFile(sub_path, output.items) catch |err| { + return step.fail("unable to write file '{}{s}': {s}", .{ + b.cache_root, sub_path, @errorName(err), + }); + }; + + self.output_file.path = try b.cache_root.join(arena, &.{sub_path}); + try man.writeManifest(); +} + +fn render_autoconf( + step: *Step, + contents: []const u8, + output: *std.ArrayList(u8), + values: std.StringArrayHashMap(Value), + src_path: []const u8, +) !void { + var values_copy = try values.clone(); + defer values_copy.deinit(); + + var any_errors = false; + var line_index: u32 = 0; + var line_it = std.mem.split(u8, contents, "\n"); + while (line_it.next()) |line| : (line_index += 1) { + if (!std.mem.startsWith(u8, line, "#")) { + try output.appendSlice(line); + try output.appendSlice("\n"); + continue; + } + var it = std.mem.tokenize(u8, line[1..], " \t\r"); + const undef = it.next().?; + if (!std.mem.eql(u8, undef, "undef")) { + try output.appendSlice(line); + try output.appendSlice("\n"); + continue; + } + const name = it.rest(); + const kv = values_copy.fetchSwapRemove(name) orelse { + try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ + src_path, line_index + 1, name, + }); + any_errors = true; + continue; + }; + try renderValueC(output, name, kv.value); + } + + for (values_copy.keys()) |name| { + try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); + any_errors = true; + } + + if (any_errors) { + return error.MakeFailed; + } +} + +fn render_cmake( + step: *Step, + contents: []const u8, + output: *std.ArrayList(u8), + values: std.StringArrayHashMap(Value), + src_path: []const u8, +) !void { + var values_copy = try values.clone(); + defer values_copy.deinit(); + + var any_errors = false; + var line_index: u32 = 0; + var line_it = std.mem.split(u8, contents, "\n"); + while (line_it.next()) |line| : (line_index += 1) { + if (!std.mem.startsWith(u8, line, "#")) { + try output.appendSlice(line); + try output.appendSlice("\n"); + continue; + } + var it = std.mem.tokenize(u8, line[1..], " \t\r"); + const cmakedefine = it.next().?; + if (!std.mem.eql(u8, cmakedefine, "cmakedefine")) { + try output.appendSlice(line); + try output.appendSlice("\n"); + continue; + } + const name = it.next() orelse { + try step.addError("{s}:{d}: error: missing define name", .{ + src_path, line_index + 1, + }); + any_errors = true; + continue; + }; + const kv = values_copy.fetchSwapRemove(name) orelse { + try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ + src_path, line_index + 1, name, + }); + any_errors = true; + continue; + }; + try renderValueC(output, name, kv.value); + } + + for (values_copy.keys()) |name| { + try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); + any_errors = true; + } + + if (any_errors) { + return error.HeaderConfigFailed; + } +} + +fn render_blank( + output: *std.ArrayList(u8), + defines: std.StringArrayHashMap(Value), + include_path: []const u8, +) !void { + const include_guard_name = try output.allocator.dupe(u8, include_path); + for (include_guard_name) |*byte| { + switch (byte.*) { + 'a'...'z' => byte.* = byte.* - 'a' + 'A', + 'A'...'Z', '0'...'9' => continue, + else => byte.* = '_', + } + } + + try output.appendSlice("#ifndef "); + try output.appendSlice(include_guard_name); + try output.appendSlice("\n#define "); + try output.appendSlice(include_guard_name); + try output.appendSlice("\n"); + + const values = defines.values(); + for (defines.keys(), 0..) |name, i| { + try renderValueC(output, name, values[i]); + } + + try output.appendSlice("#endif /* "); + try output.appendSlice(include_guard_name); + try output.appendSlice(" */\n"); +} + +fn render_nasm(output: *std.ArrayList(u8), defines: std.StringArrayHashMap(Value)) !void { + const values = defines.values(); + for (defines.keys(), 0..) |name, i| { + try renderValueNasm(output, name, values[i]); + } +} + +fn renderValueC(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { + switch (value) { + .undef => { + try output.appendSlice("/* #undef "); + try output.appendSlice(name); + try output.appendSlice(" */\n"); + }, + .defined => { + try output.appendSlice("#define "); + try output.appendSlice(name); + try output.appendSlice("\n"); + }, + .boolean => |b| { + try output.appendSlice("#define "); + try output.appendSlice(name); + try output.appendSlice(" "); + try output.appendSlice(if (b) "true\n" else "false\n"); + }, + .int => |i| { + try output.writer().print("#define {s} {d}\n", .{ name, i }); + }, + .ident => |ident| { + try output.writer().print("#define {s} {s}\n", .{ name, ident }); + }, + .string => |string| { + // TODO: use C-specific escaping instead of zig string literals + try output.writer().print("#define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); + }, + } +} + +fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { + switch (value) { + .undef => { + try output.appendSlice("; %undef "); + try output.appendSlice(name); + try output.appendSlice("\n"); + }, + .defined => { + try output.appendSlice("%define "); + try output.appendSlice(name); + try output.appendSlice("\n"); + }, + .boolean => |b| { + try output.appendSlice("%define "); + try output.appendSlice(name); + try output.appendSlice(if (b) " 1\n" else " 0\n"); + }, + .int => |i| { + try output.writer().print("%define {s} {d}\n", .{ name, i }); + }, + .ident => |ident| { + try output.writer().print("%define {s} {s}\n", .{ name, ident }); + }, + .string => |string| { + // TODO: use nasm-specific escaping instead of zig string literals + try output.writer().print("%define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); + }, + } +} diff --git a/lib/std/Build/Step/Fmt.zig b/lib/std/Build/Step/Fmt.zig new file mode 100644 index 0000000000000000000000000000000000000000..23d5d9e3ff0b47a14cd2875822775d3245c9dacf --- /dev/null +++ b/lib/std/Build/Step/Fmt.zig @@ -0,0 +1,72 @@ +//! This step has two modes: +//! * Modify mode: directly modify source files, formatting them in place. +//! * Check mode: fail the step if a non-conforming file is found. +const std = @import("std"); +const Step = std.Build.Step; +const FmtStep = @This(); + +step: Step, +paths: []const []const u8, +exclude_paths: []const []const u8, +check: bool, + +pub const base_id = .fmt; + +pub const Options = struct { + paths: []const []const u8 = &.{}, + exclude_paths: []const []const u8 = &.{}, + /// If true, fails the build step when any non-conforming files are encountered. + check: bool = false, +}; + +pub fn create(owner: *std.Build, options: Options) *FmtStep { + const self = owner.allocator.create(FmtStep) catch @panic("OOM"); + const name = if (options.check) "zig fmt --check" else "zig fmt"; + self.* = .{ + .step = Step.init(.{ + .id = base_id, + .name = name, + .owner = owner, + .makeFn = make, + }), + .paths = options.paths, + .exclude_paths = options.exclude_paths, + .check = options.check, + }; + return self; +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + // zig fmt is fast enough that no progress is needed. + _ = prog_node; + + // TODO: if check=false, this means we are modifying source files in place, which + // is an operation that could race against other operations also modifying source files + // in place. In this case, this step should obtain a write lock while making those + // modifications. + + const b = step.owner; + const arena = b.allocator; + const self = @fieldParentPtr(FmtStep, "step", step); + + var argv: std.ArrayListUnmanaged([]const u8) = .{}; + try argv.ensureUnusedCapacity(arena, 2 + 1 + self.paths.len + 2 * self.exclude_paths.len); + + argv.appendAssumeCapacity(b.zig_exe); + argv.appendAssumeCapacity("fmt"); + + if (self.check) { + argv.appendAssumeCapacity("--check"); + } + + for (self.paths) |p| { + argv.appendAssumeCapacity(b.pathFromRoot(p)); + } + + for (self.exclude_paths) |p| { + argv.appendAssumeCapacity("--exclude"); + argv.appendAssumeCapacity(b.pathFromRoot(p)); + } + + return step.evalChildProcess(argv.items); +} diff --git a/lib/std/Build/Step/InstallArtifact.zig b/lib/std/Build/Step/InstallArtifact.zig new file mode 100644 index 0000000000000000000000000000000000000000..fa357a9ae944c905b89852befa8a5143fa1ef980 --- /dev/null +++ b/lib/std/Build/Step/InstallArtifact.zig @@ -0,0 +1,130 @@ +const std = @import("std"); +const Step = std.Build.Step; +const CompileStep = std.Build.CompileStep; +const InstallDir = std.Build.InstallDir; +const InstallArtifactStep = @This(); +const fs = std.fs; + +pub const base_id = .install_artifact; + +step: Step, +artifact: *CompileStep, +dest_dir: InstallDir, +pdb_dir: ?InstallDir, +h_dir: ?InstallDir, +/// If non-null, adds additional path components relative to dest_dir, and +/// overrides the basename of the CompileStep. +dest_sub_path: ?[]const u8, + +pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { + const self = owner.allocator.create(InstallArtifactStep) catch @panic("OOM"); + self.* = InstallArtifactStep{ + .step = Step.init(.{ + .id = base_id, + .name = owner.fmt("install {s}", .{artifact.name}), + .owner = owner, + .makeFn = make, + }), + .artifact = artifact, + .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { + .obj => @panic("Cannot install a .obj build artifact."), + .exe, .@"test" => InstallDir{ .bin = {} }, + .lib => InstallDir{ .lib = {} }, + }, + .pdb_dir = if (artifact.producesPdbFile()) blk: { + if (artifact.kind == .exe or artifact.kind == .@"test") { + break :blk InstallDir{ .bin = {} }; + } else { + break :blk InstallDir{ .lib = {} }; + } + } else null, + .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null, + .dest_sub_path = null, + }; + self.step.dependOn(&artifact.step); + + owner.pushInstalledFile(self.dest_dir, artifact.out_filename); + if (self.artifact.isDynamicLibrary()) { + if (artifact.major_only_filename) |name| { + owner.pushInstalledFile(.lib, name); + } + if (artifact.name_only_filename) |name| { + owner.pushInstalledFile(.lib, name); + } + if (self.artifact.target.isWindows()) { + owner.pushInstalledFile(.lib, artifact.out_lib_filename); + } + } + if (self.pdb_dir) |pdb_dir| { + owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename); + } + if (self.h_dir) |h_dir| { + owner.pushInstalledFile(h_dir, artifact.out_h_filename); + } + return self; +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const self = @fieldParentPtr(InstallArtifactStep, "step", step); + const src_builder = self.artifact.step.owner; + const dest_builder = step.owner; + + const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename; + const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path); + const cwd = fs.cwd(); + + var all_cached = true; + + { + const full_src_path = self.artifact.getOutputSource().getPath(src_builder); + const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_dest_path, @errorName(err), + }); + }; + all_cached = all_cached and p == .fresh; + } + + if (self.artifact.isDynamicLibrary() and + self.artifact.version != null and + self.artifact.target.wantSharedLibSymLinks()) + { + try CompileStep.doAtomicSymLinks(step, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); + } + if (self.artifact.isDynamicLibrary() and + self.artifact.target.isWindows() and + self.artifact.emit_implib != .no_emit) + { + const full_src_path = self.artifact.getOutputLibSource().getPath(src_builder); + const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename); + const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_implib_path, @errorName(err), + }); + }; + all_cached = all_cached and p == .fresh; + } + if (self.pdb_dir) |pdb_dir| { + const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder); + const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename); + const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_pdb_path, @errorName(err), + }); + }; + all_cached = all_cached and p == .fresh; + } + if (self.h_dir) |h_dir| { + const full_src_path = self.artifact.getOutputHSource().getPath(src_builder); + const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename); + const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_h_path, @errorName(err), + }); + }; + all_cached = all_cached and p == .fresh; + } + self.artifact.installed_path = full_dest_path; + step.result_cached = all_cached; +} diff --git a/lib/std/Build/Step/InstallDir.zig b/lib/std/Build/Step/InstallDir.zig new file mode 100644 index 0000000000000000000000000000000000000000..28280dcb7f4bf74c86ae774b2f6ee7d43925ea1f --- /dev/null +++ b/lib/std/Build/Step/InstallDir.zig @@ -0,0 +1,110 @@ +const std = @import("std"); +const mem = std.mem; +const fs = std.fs; +const Step = std.Build.Step; +const InstallDir = std.Build.InstallDir; +const InstallDirStep = @This(); + +step: Step, +options: Options, +/// This is used by the build system when a file being installed comes from one +/// package but is being installed by another. +dest_builder: *std.Build, + +pub const base_id = .install_dir; + +pub const Options = struct { + source_dir: []const u8, + install_dir: InstallDir, + install_subdir: []const u8, + /// File paths which end in any of these suffixes will be excluded + /// from being installed. + exclude_extensions: []const []const u8 = &.{}, + /// File paths which end in any of these suffixes will result in + /// empty files being installed. This is mainly intended for large + /// test.zig files in order to prevent needless installation bloat. + /// However if the files were not present at all, then + /// `@import("test.zig")` would be a compile error. + blank_extensions: []const []const u8 = &.{}, + + fn dupe(self: Options, b: *std.Build) Options { + return .{ + .source_dir = b.dupe(self.source_dir), + .install_dir = self.install_dir.dupe(b), + .install_subdir = b.dupe(self.install_subdir), + .exclude_extensions = b.dupeStrings(self.exclude_extensions), + .blank_extensions = b.dupeStrings(self.blank_extensions), + }; + } +}; + +pub fn init(owner: *std.Build, options: Options) InstallDirStep { + owner.pushInstalledFile(options.install_dir, options.install_subdir); + return .{ + .step = Step.init(.{ + .id = .install_dir, + .name = owner.fmt("install {s}/", .{options.source_dir}), + .owner = owner, + .makeFn = make, + }), + .options = options.dupe(owner), + .dest_builder = owner, + }; +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const self = @fieldParentPtr(InstallDirStep, "step", step); + const dest_builder = self.dest_builder; + const arena = dest_builder.allocator; + const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir); + const src_builder = self.step.owner; + var src_dir = src_builder.build_root.handle.openIterableDir(self.options.source_dir, .{}) catch |err| { + return step.fail("unable to open source directory '{}{s}': {s}", .{ + src_builder.build_root, self.options.source_dir, @errorName(err), + }); + }; + defer src_dir.close(); + var it = try src_dir.walk(arena); + var all_cached = true; + next_entry: while (try it.next()) |entry| { + for (self.options.exclude_extensions) |ext| { + if (mem.endsWith(u8, entry.path, ext)) { + continue :next_entry; + } + } + + // relative to src build root + const src_sub_path = try fs.path.join(arena, &.{ self.options.source_dir, entry.path }); + const dest_path = try fs.path.join(arena, &.{ dest_prefix, entry.path }); + const cwd = fs.cwd(); + + switch (entry.kind) { + .Directory => try cwd.makePath(dest_path), + .File => { + for (self.options.blank_extensions) |ext| { + if (mem.endsWith(u8, entry.path, ext)) { + try dest_builder.truncateFile(dest_path); + continue :next_entry; + } + } + + const prev_status = fs.Dir.updateFile( + src_builder.build_root.handle, + src_sub_path, + cwd, + dest_path, + .{}, + ) catch |err| { + return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{ + src_builder.build_root, src_sub_path, dest_path, @errorName(err), + }); + }; + all_cached = all_cached and prev_status == .fresh; + }, + else => continue, + } + } + + step.result_cached = all_cached; +} diff --git a/lib/std/Build/Step/InstallFile.zig b/lib/std/Build/Step/InstallFile.zig new file mode 100644 index 0000000000000000000000000000000000000000..b6b66fd1e06de48b89bb779d1913531c93d82535 --- /dev/null +++ b/lib/std/Build/Step/InstallFile.zig @@ -0,0 +1,57 @@ +const std = @import("std"); +const Step = std.Build.Step; +const FileSource = std.Build.FileSource; +const InstallDir = std.Build.InstallDir; +const InstallFileStep = @This(); +const assert = std.debug.assert; + +pub const base_id = .install_file; + +step: Step, +source: FileSource, +dir: InstallDir, +dest_rel_path: []const u8, +/// This is used by the build system when a file being installed comes from one +/// package but is being installed by another. +dest_builder: *std.Build, + +pub fn create( + owner: *std.Build, + source: FileSource, + dir: InstallDir, + dest_rel_path: []const u8, +) *InstallFileStep { + assert(dest_rel_path.len != 0); + owner.pushInstalledFile(dir, dest_rel_path); + const self = owner.allocator.create(InstallFileStep) catch @panic("OOM"); + self.* = .{ + .step = Step.init(.{ + .id = base_id, + .name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), + .owner = owner, + .makeFn = make, + }), + .source = source.dupe(owner), + .dir = dir.dupe(owner), + .dest_rel_path = owner.dupePath(dest_rel_path), + .dest_builder = owner, + }; + source.addStepDependencies(&self.step); + return self; +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const src_builder = step.owner; + const self = @fieldParentPtr(InstallFileStep, "step", step); + const dest_builder = self.dest_builder; + const full_src_path = self.source.getPath2(src_builder, step); + const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path); + const cwd = std.fs.cwd(); + const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { + return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ + full_src_path, full_dest_path, @errorName(err), + }); + }; + step.result_cached = prev == .fresh; +} diff --git a/lib/std/Build/Step/ObjCopy.zig b/lib/std/Build/Step/ObjCopy.zig new file mode 100644 index 0000000000000000000000000000000000000000..608c56591f935ef519e334266647e1e763286e70 --- /dev/null +++ b/lib/std/Build/Step/ObjCopy.zig @@ -0,0 +1,122 @@ +const std = @import("std"); +const ObjCopyStep = @This(); + +const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; +const ArrayListUnmanaged = std.ArrayListUnmanaged; +const File = std.fs.File; +const InstallDir = std.Build.InstallDir; +const CompileStep = std.Build.CompileStep; +const Step = std.Build.Step; +const elf = std.elf; +const fs = std.fs; +const io = std.io; +const sort = std.sort; + +pub const base_id: Step.Id = .objcopy; + +pub const RawFormat = enum { + bin, + hex, +}; + +step: Step, +file_source: std.Build.FileSource, +basename: []const u8, +output_file: std.Build.GeneratedFile, + +format: ?RawFormat, +only_section: ?[]const u8, +pad_to: ?u64, + +pub const Options = struct { + basename: ?[]const u8 = null, + format: ?RawFormat = null, + only_section: ?[]const u8 = null, + pad_to: ?u64 = null, +}; + +pub fn create( + owner: *std.Build, + file_source: std.Build.FileSource, + options: Options, +) *ObjCopyStep { + const self = owner.allocator.create(ObjCopyStep) catch @panic("OOM"); + self.* = ObjCopyStep{ + .step = Step.init(.{ + .id = base_id, + .name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}), + .owner = owner, + .makeFn = make, + }), + .file_source = file_source, + .basename = options.basename orelse file_source.getDisplayName(), + .output_file = std.Build.GeneratedFile{ .step = &self.step }, + + .format = options.format, + .only_section = options.only_section, + .pad_to = options.pad_to, + }; + file_source.addStepDependencies(&self.step); + return self; +} + +pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource { + return .{ .generated = &self.output_file }; +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + const b = step.owner; + const self = @fieldParentPtr(ObjCopyStep, "step", step); + + var man = b.cache.obtain(); + defer man.deinit(); + + // Random bytes to make ObjCopyStep unique. Refresh this with new random + // bytes when ObjCopyStep implementation is modified incompatibly. + man.hash.add(@as(u32, 0xe18b7baf)); + + const full_src_path = self.file_source.getPath(b); + _ = try man.addFile(full_src_path, null); + man.hash.addOptionalBytes(self.only_section); + man.hash.addOptional(self.pad_to); + man.hash.addOptional(self.format); + + if (try step.cacheHit(&man)) { + // Cache hit, skip subprocess execution. + const digest = man.final(); + self.output_file.path = try b.cache_root.join(b.allocator, &.{ + "o", &digest, self.basename, + }); + return; + } + + const digest = man.final(); + const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename }); + const cache_path = "o" ++ fs.path.sep_str ++ digest; + b.cache_root.handle.makePath(cache_path) catch |err| { + return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); + }; + + var argv = std.ArrayList([]const u8).init(b.allocator); + try argv.appendSlice(&.{ b.zig_exe, "objcopy" }); + + if (self.only_section) |only_section| { + try argv.appendSlice(&.{ "-j", only_section }); + } + if (self.pad_to) |pad_to| { + try argv.appendSlice(&.{ "--pad-to", b.fmt("{d}", .{pad_to}) }); + } + if (self.format) |format| switch (format) { + .bin => try argv.appendSlice(&.{ "-O", "binary" }), + .hex => try argv.appendSlice(&.{ "-O", "hex" }), + }; + + try argv.appendSlice(&.{ full_src_path, full_dest_path }); + + try argv.append("--listen=-"); + _ = try step.evalZigProcess(argv.items, prog_node); + + self.output_file.path = full_dest_path; + try man.writeManifest(); +} diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig new file mode 100644 index 0000000000000000000000000000000000000000..101c284cf0b8c9b5250b7bda4c0a74bbbfa73114 --- /dev/null +++ b/lib/std/Build/Step/Options.zig @@ -0,0 +1,421 @@ +const std = @import("std"); +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, + +contents: std.ArrayList(u8), +artifact_args: std.ArrayList(OptionArtifactArg), +file_source_args: std.ArrayList(OptionFileSourceArg), + +pub fn create(owner: *std.Build) *OptionsStep { + const self = owner.allocator.create(OptionsStep) catch @panic("OOM"); + self.* = .{ + .step = Step.init(.{ + .id = base_id, + .name = "options", + .owner = owner, + .makeFn = make, + }), + .generated_file = undefined, + .contents = std.ArrayList(u8).init(owner.allocator), + .artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator), + .file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.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.step.owner), + }) 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.step.owner.dupe(name), .artifact = artifact }) catch @panic("OOM"); + self.step.dependOn(&artifact.step); +} + +pub fn createModule(self: *OptionsStep) *std.Build.Module { + return self.step.owner.createModule(.{ + .source_file = self.getSource(), + .dependencies = &.{}, + }); +} + +pub fn getSource(self: *OptionsStep) FileSource { + return .{ .generated = &self.generated_file }; +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + // This step completes so quickly that no progress is necessary. + _ = prog_node; + + const b = step.owner; + const self = @fieldParentPtr(OptionsStep, "step", step); + + for (self.artifact_args.items) |item| { + self.addOption( + []const u8, + item.name, + b.pathFromRoot(item.artifact.getOutputSource().getPath(b)), + ); + } + + for (self.file_source_args.items) |item| { + self.addOption( + []const u8, + item.name, + item.source.getPath(b), + ); + } + + const basename = "options.zig"; + + // Hash contents to file name. + var hash = b.cache.hash; + // Random bytes to make unique. Refresh this with new random bytes when + // implementation is modified in a non-backwards-compatible way. + hash.add(@as(u32, 0x38845ef8)); + hash.addBytes(self.contents.items); + const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename; + + self.generated_file.path = try b.cache_root.join(b.allocator, &.{sub_path}); + + // Optimize for the hot path. Stat the file, and if it already exists, + // cache hit. + if (b.cache_root.handle.access(sub_path, .{})) |_| { + // This is the hot path, success. + step.result_cached = true; + return; + } else |outer_err| switch (outer_err) { + error.FileNotFound => { + const sub_dirname = fs.path.dirname(sub_path).?; + b.cache_root.handle.makePath(sub_dirname) catch |e| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, sub_dirname, @errorName(e), + }); + }; + + const rand_int = std.crypto.random.int(u64); + const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ + std.Build.hex64(rand_int) ++ fs.path.sep_str ++ + basename; + const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?; + + b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| { + return step.fail("unable to make temporary directory '{}{s}': {s}", .{ + b.cache_root, tmp_sub_path_dirname, @errorName(err), + }); + }; + + b.cache_root.handle.writeFile(tmp_sub_path, self.contents.items) catch |err| { + return step.fail("unable to write options to '{}{s}': {s}", .{ + b.cache_root, tmp_sub_path, @errorName(err), + }); + }; + + b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) { + error.PathAlreadyExists => { + // Other process beat us to it. Clean up the temp file. + b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| { + try step.addError("warning: unable to delete temp file '{}{s}': {s}", .{ + b.cache_root, tmp_sub_path, @errorName(e), + }); + }; + step.result_cached = true; + return; + }, + else => { + return step.fail("unable to rename options from '{}{s}' to '{}{s}': {s}", .{ + b.cache_root, tmp_sub_path, + b.cache_root, sub_path, + @errorName(err), + }); + }, + }; + }, + else => |e| return step.fail("unable to access options file '{}{s}': {s}", .{ + b.cache_root, sub_path, @errorName(e), + }), + } +} + +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 cache: std.Build.Cache = .{ + .gpa = arena.allocator(), + .manifest_dir = std.fs.cwd(), + }; + + var builder = try std.Build.create( + arena.allocator(), + "test", + .{ .path = "test", .handle = std.fs.cwd() }, + .{ .path = "test", .handle = std.fs.cwd() }, + .{ .path = "test", .handle = std.fs.cwd() }, + host, + &cache, + ); + 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.Ast.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(0), .zig); +} diff --git a/lib/std/Build/Step/RemoveDir.zig b/lib/std/Build/Step/RemoveDir.zig new file mode 100644 index 0000000000000000000000000000000000000000..59025a7e91297ba5156536df4d499778567d3ee6 --- /dev/null +++ b/lib/std/Build/Step/RemoveDir.zig @@ -0,0 +1,42 @@ +const std = @import("std"); +const fs = std.fs; +const Step = std.Build.Step; +const RemoveDirStep = @This(); + +pub const base_id = .remove_dir; + +step: Step, +dir_path: []const u8, + +pub fn init(owner: *std.Build, dir_path: []const u8) RemoveDirStep { + return RemoveDirStep{ + .step = Step.init(.{ + .id = .remove_dir, + .name = owner.fmt("RemoveDir {s}", .{dir_path}), + .owner = owner, + .makeFn = make, + }), + .dir_path = owner.dupePath(dir_path), + }; +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + // TODO update progress node while walking file system. + // Should the standard library support this use case?? + _ = prog_node; + + const b = step.owner; + const self = @fieldParentPtr(RemoveDirStep, "step", step); + + b.build_root.handle.deleteTree(self.dir_path) catch |err| { + if (b.build_root.path) |base| { + return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{ + base, self.dir_path, @errorName(err), + }); + } else { + return step.fail("unable to recursively delete path '{s}': {s}", .{ + self.dir_path, @errorName(err), + }); + } + }; +} diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig new file mode 100644 index 0000000000000000000000000000000000000000..4e973cfd982f87ea3638c84d33ab3a647482f4e1 --- /dev/null +++ b/lib/std/Build/Step/Run.zig @@ -0,0 +1,1254 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const Step = std.Build.Step; +const CompileStep = std.Build.CompileStep; +const WriteFileStep = std.Build.WriteFileStep; +const fs = std.fs; +const mem = std.mem; +const process = std.process; +const ArrayList = std.ArrayList; +const EnvMap = process.EnvMap; +const Allocator = mem.Allocator; +const ExecError = std.Build.ExecError; +const assert = std.debug.assert; + +const RunStep = @This(); + +pub const base_id: Step.Id = .run; + +step: Step, + +/// See also addArg and addArgs to modifying this directly +argv: ArrayList(Arg), + +/// Set this to modify the current working directory +/// TODO change this to a Build.Cache.Directory to better integrate with +/// future child process cwd API. +cwd: ?[]const u8, + +/// Override this field to modify the environment, or use setEnvironmentVariable +env_map: ?*EnvMap, + +/// Configures whether the RunStep is considered to have side-effects, and also +/// whether the RunStep will inherit stdio streams, forwarding them to the +/// parent process, in which case will require a global lock to prevent other +/// steps from interfering with stdio while the subprocess associated with this +/// RunStep is running. +/// If the RunStep is determined to not have side-effects, then execution will +/// be skipped if all output files are up-to-date and input files are +/// unchanged. +stdio: StdIo = .infer_from_args, +/// This field must be `null` if stdio is `inherit`. +stdin: ?[]const u8 = null, + +/// Additional file paths relative to build.zig that, when modified, indicate +/// that the RunStep should be re-executed. +/// If the RunStep is determined to have side-effects, this field is ignored +/// and the RunStep is always executed when it appears in the build graph. +extra_file_dependencies: []const []const u8 = &.{}, + +/// After adding an output argument, this step will by default rename itself +/// for a better display name in the build summary. +/// This can be disabled by setting this to false. +rename_step_with_output_arg: bool = true, + +/// If this is true, a RunStep which is configured to check the output of the +/// executed binary will not fail the build if the binary cannot be executed +/// due to being for a foreign binary to the host system which is running the +/// build graph. +/// Command-line arguments such as -fqemu and -fwasmtime may affect whether a +/// binary is detected as foreign, as well as system configuration such as +/// Rosetta (macOS) and binfmt_misc (Linux). +/// If this RunStep is considered to have side-effects, then this flag does +/// nothing. +skip_foreign_checks: bool = false, + +/// If stderr or stdout exceeds this amount, the child process is killed and +/// the step fails. +max_stdio_size: usize = 10 * 1024 * 1024, + +captured_stdout: ?*Output = null, +captured_stderr: ?*Output = null, + +has_side_effects: bool = false, + +pub const StdIo = union(enum) { + /// Whether the RunStep has side-effects will be determined by whether or not one + /// of the args is an output file (added with `addOutputFileArg`). + /// If the RunStep is determined to have side-effects, this is the same as `inherit`. + /// The step will fail if the subprocess crashes or returns a non-zero exit code. + infer_from_args, + /// Causes the RunStep to be considered to have side-effects, and therefore + /// always execute when it appears in the build graph. + /// It also means that this step will obtain a global lock to prevent other + /// steps from running in the meantime. + /// The step will fail if the subprocess crashes or returns a non-zero exit code. + inherit, + /// Causes the RunStep to be considered to *not* have side-effects. The + /// process will be re-executed if any of the input dependencies are + /// modified. The exit code and standard I/O streams will be checked for + /// certain conditions, and the step will succeed or fail based on these + /// conditions. + /// Note that an explicit check for exit code 0 needs to be added to this + /// list if such a check is desirable. + check: std.ArrayList(Check), + /// This RunStep is running a zig unit test binary and will communicate + /// extra metadata over the IPC protocol. + zig_test, + + pub const Check = union(enum) { + expect_stderr_exact: []const u8, + expect_stderr_match: []const u8, + expect_stdout_exact: []const u8, + expect_stdout_match: []const u8, + expect_term: std.process.Child.Term, + }; +}; + +pub const Arg = union(enum) { + artifact: *CompileStep, + file_source: std.Build.FileSource, + directory_source: std.Build.FileSource, + bytes: []u8, + output: *Output, +}; + +pub const Output = struct { + generated_file: std.Build.GeneratedFile, + prefix: []const u8, + basename: []const u8, +}; + +pub fn create(owner: *std.Build, name: []const u8) *RunStep { + const self = owner.allocator.create(RunStep) catch @panic("OOM"); + self.* = .{ + .step = Step.init(.{ + .id = base_id, + .name = name, + .owner = owner, + .makeFn = make, + }), + .argv = ArrayList(Arg).init(owner.allocator), + .cwd = null, + .env_map = null, + }; + return self; +} + +pub fn setName(self: *RunStep, name: []const u8) void { + self.step.name = name; + self.rename_step_with_output_arg = false; +} + +pub fn enableTestRunnerMode(rs: *RunStep) void { + rs.stdio = .zig_test; + rs.addArgs(&.{"--listen=-"}); +} + +pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { + self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM"); + self.step.dependOn(&artifact.step); +} + +/// This provides file path as a command line argument to the command being +/// run, and returns a FileSource which can be used as inputs to other APIs +/// throughout the build system. +pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource { + return addPrefixedOutputFileArg(rs, "", basename); +} + +pub fn addPrefixedOutputFileArg( + rs: *RunStep, + prefix: []const u8, + basename: []const u8, +) std.Build.FileSource { + const b = rs.step.owner; + + const output = b.allocator.create(Output) catch @panic("OOM"); + output.* = .{ + .prefix = prefix, + .basename = basename, + .generated_file = .{ .step = &rs.step }, + }; + rs.argv.append(.{ .output = output }) catch @panic("OOM"); + + if (rs.rename_step_with_output_arg) { + rs.setName(b.fmt("{s} ({s})", .{ rs.step.name, basename })); + } + + return .{ .generated = &output.generated_file }; +} + +pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { + self.argv.append(.{ + .file_source = file_source.dupe(self.step.owner), + }) catch @panic("OOM"); + file_source.addStepDependencies(&self.step); +} + +pub fn addDirectorySourceArg(self: *RunStep, directory_source: std.Build.FileSource) void { + self.argv.append(.{ + .directory_source = directory_source.dupe(self.step.owner), + }) catch @panic("OOM"); + directory_source.addStepDependencies(&self.step); +} + +pub fn addArg(self: *RunStep, arg: []const u8) void { + self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM"); +} + +pub fn addArgs(self: *RunStep, args: []const []const u8) void { + for (args) |arg| { + self.addArg(arg); + } +} + +pub fn clearEnvironment(self: *RunStep) void { + const b = self.step.owner; + const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM"); + new_env_map.* = EnvMap.init(b.allocator); + self.env_map = new_env_map; +} + +pub fn addPathDir(self: *RunStep, search_path: []const u8) void { + const b = self.step.owner; + const env_map = getEnvMapInternal(self); + + const key = "PATH"; + var prev_path = env_map.get(key); + + if (prev_path) |pp| { + const new_path = b.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); + env_map.put(key, new_path) catch @panic("OOM"); + } else { + env_map.put(key, b.dupePath(search_path)) catch @panic("OOM"); + } +} + +pub fn getEnvMap(self: *RunStep) *EnvMap { + return getEnvMapInternal(self); +} + +fn getEnvMapInternal(self: *RunStep) *EnvMap { + const arena = self.step.owner.allocator; + return self.env_map orelse { + const env_map = arena.create(EnvMap) catch @panic("OOM"); + env_map.* = process.getEnvMap(arena) catch @panic("unhandled error"); + self.env_map = env_map; + return env_map; + }; +} + +pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void { + const b = self.step.owner; + const env_map = self.getEnvMap(); + env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); +} + +pub fn removeEnvironmentVariable(self: *RunStep, key: []const u8) void { + self.getEnvMap().remove(key); +} + +/// Adds a check for exact stderr match. Does not add any other checks. +pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { + const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) }; + self.addCheck(new_check); +} + +/// Adds a check for exact stdout match as well as a check for exit code 0, if +/// there is not already an expected termination check. +pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { + const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) }; + self.addCheck(new_check); + if (!self.hasTermCheck()) { + self.expectExitCode(0); + } +} + +pub fn expectExitCode(self: *RunStep, code: u8) void { + const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } }; + self.addCheck(new_check); +} + +pub fn hasTermCheck(self: RunStep) bool { + for (self.stdio.check.items) |check| switch (check) { + .expect_term => return true, + else => continue, + }; + return false; +} + +pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void { + switch (self.stdio) { + .infer_from_args => { + self.stdio = .{ .check = std.ArrayList(StdIo.Check).init(self.step.owner.allocator) }; + self.stdio.check.append(new_check) catch @panic("OOM"); + }, + .check => |*checks| checks.append(new_check) catch @panic("OOM"), + else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"), + } +} + +pub fn captureStdErr(self: *RunStep) std.Build.FileSource { + assert(self.stdio != .inherit); + + if (self.captured_stderr) |output| return .{ .generated = &output.generated_file }; + + const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); + output.* = .{ + .prefix = "", + .basename = "stderr", + .generated_file = .{ .step = &self.step }, + }; + self.captured_stderr = output; + return .{ .generated = &output.generated_file }; +} + +pub fn captureStdOut(self: *RunStep) std.Build.FileSource { + assert(self.stdio != .inherit); + + if (self.captured_stdout) |output| return .{ .generated = &output.generated_file }; + + const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); + output.* = .{ + .prefix = "", + .basename = "stdout", + .generated_file = .{ .step = &self.step }, + }; + self.captured_stdout = output; + return .{ .generated = &output.generated_file }; +} + +/// Returns whether the RunStep has side effects *other than* updating the output arguments. +fn hasSideEffects(self: RunStep) bool { + if (self.has_side_effects) return true; + return switch (self.stdio) { + .infer_from_args => !self.hasAnyOutputArgs(), + .inherit => true, + .check => false, + .zig_test => false, + }; +} + +fn hasAnyOutputArgs(self: RunStep) bool { + if (self.captured_stdout != null) return true; + if (self.captured_stderr != null) return true; + for (self.argv.items) |arg| switch (arg) { + .output => return true, + else => continue, + }; + return false; +} + +fn checksContainStdout(checks: []const StdIo.Check) bool { + for (checks) |check| switch (check) { + .expect_stderr_exact, + .expect_stderr_match, + .expect_term, + => continue, + + .expect_stdout_exact, + .expect_stdout_match, + => return true, + }; + return false; +} + +fn checksContainStderr(checks: []const StdIo.Check) bool { + for (checks) |check| switch (check) { + .expect_stdout_exact, + .expect_stdout_match, + .expect_term, + => continue, + + .expect_stderr_exact, + .expect_stderr_match, + => return true, + }; + return false; +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + const b = step.owner; + const arena = b.allocator; + const self = @fieldParentPtr(RunStep, "step", step); + const has_side_effects = self.hasSideEffects(); + + var argv_list = ArrayList([]const u8).init(arena); + var output_placeholders = ArrayList(struct { + index: usize, + output: *Output, + }).init(arena); + + var man = b.cache.obtain(); + defer man.deinit(); + + for (self.argv.items) |arg| { + switch (arg) { + .bytes => |bytes| { + try argv_list.append(bytes); + man.hash.addBytes(bytes); + }, + .file_source => |file| { + const file_path = file.getPath(b); + try argv_list.append(file_path); + _ = try man.addFile(file_path, null); + }, + .directory_source => |file| { + const file_path = file.getPath(b); + try argv_list.append(file_path); + man.hash.addBytes(file_path); + }, + .artifact => |artifact| { + if (artifact.target.isWindows()) { + // On Windows we don't have rpaths so we have to add .dll search paths to PATH + self.addPathForDynLibs(artifact); + } + const file_path = artifact.installed_path orelse + artifact.getOutputSource().getPath(b); + + try argv_list.append(file_path); + + _ = try man.addFile(file_path, null); + }, + .output => |output| { + man.hash.addBytes(output.prefix); + man.hash.addBytes(output.basename); + // Add a placeholder into the argument list because we need the + // manifest hash to be updated with all arguments before the + // object directory is computed. + try argv_list.append(""); + try output_placeholders.append(.{ + .index = argv_list.items.len - 1, + .output = output, + }); + }, + } + } + + if (self.captured_stdout) |output| { + man.hash.addBytes(output.basename); + } + + if (self.captured_stderr) |output| { + man.hash.addBytes(output.basename); + } + + hashStdIo(&man.hash, self.stdio); + + if (has_side_effects) { + try runCommand(self, argv_list.items, has_side_effects, null, prog_node); + return; + } + + for (self.extra_file_dependencies) |file_path| { + _ = try man.addFile(b.pathFromRoot(file_path), null); + } + + if (try step.cacheHit(&man)) { + // cache hit, skip running command + const digest = man.final(); + for (output_placeholders.items) |placeholder| { + placeholder.output.generated_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, placeholder.output.basename, + }); + } + + if (self.captured_stdout) |output| { + output.generated_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, output.basename, + }); + } + + if (self.captured_stderr) |output| { + output.generated_file.path = try b.cache_root.join(arena, &.{ + "o", &digest, output.basename, + }); + } + + step.result_cached = true; + return; + } + + const digest = man.final(); + + for (output_placeholders.items) |placeholder| { + const output_components = .{ "o", &digest, placeholder.output.basename }; + const output_sub_path = try fs.path.join(arena, &output_components); + const output_sub_dir_path = fs.path.dirname(output_sub_path).?; + b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, output_sub_dir_path, @errorName(err), + }); + }; + const output_path = try b.cache_root.join(arena, &output_components); + placeholder.output.generated_file.path = output_path; + const cli_arg = if (placeholder.output.prefix.len == 0) + output_path + else + b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path }); + argv_list.items[placeholder.index] = cli_arg; + } + + try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node); + + try step.writeManifest(&man); +} + +fn formatTerm( + term: ?std.process.Child.Term, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = fmt; + _ = options; + if (term) |t| switch (t) { + .Exited => |code| try writer.print("exited with code {}", .{code}), + .Signal => |sig| try writer.print("terminated with signal {}", .{sig}), + .Stopped => |sig| try writer.print("stopped with signal {}", .{sig}), + .Unknown => |code| try writer.print("terminated for unknown reason with code {}", .{code}), + } else { + try writer.writeAll("exited with any code"); + } +} +fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) { + return .{ .data = term }; +} + +fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool { + return if (expected) |e| switch (e) { + .Exited => |expected_code| switch (actual) { + .Exited => |actual_code| expected_code == actual_code, + else => false, + }, + .Signal => |expected_sig| switch (actual) { + .Signal => |actual_sig| expected_sig == actual_sig, + else => false, + }, + .Stopped => |expected_sig| switch (actual) { + .Stopped => |actual_sig| expected_sig == actual_sig, + else => false, + }, + .Unknown => |expected_code| switch (actual) { + .Unknown => |actual_code| expected_code == actual_code, + else => false, + }, + } else switch (actual) { + .Exited => true, + else => false, + }; +} + +fn runCommand( + self: *RunStep, + argv: []const []const u8, + has_side_effects: bool, + digest: ?*const [std.Build.Cache.hex_digest_len]u8, + prog_node: *std.Progress.Node, +) !void { + const step = &self.step; + const b = step.owner; + const arena = b.allocator; + + try step.handleChildProcUnsupported(self.cwd, argv); + try Step.handleVerbose2(step.owner, self.cwd, self.env_map, argv); + + const allow_skip = switch (self.stdio) { + .check, .zig_test => self.skip_foreign_checks, + else => false, + }; + + var interp_argv = std.ArrayList([]const u8).init(b.allocator); + defer interp_argv.deinit(); + + const result = spawnChildAndCollect(self, argv, has_side_effects, prog_node) catch |err| term: { + // InvalidExe: cpu arch mismatch + // FileNotFound: can happen with a wrong dynamic linker path + if (err == error.InvalidExe or err == error.FileNotFound) interpret: { + // TODO: learn the target from the binary directly rather than from + // relying on it being a CompileStep. This will make this logic + // work even for the edge case that the binary was produced by a + // third party. + const exe = switch (self.argv.items[0]) { + .artifact => |exe| exe, + else => break :interpret, + }; + switch (exe.kind) { + .exe, .@"test" => {}, + else => break :interpret, + } + + const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc; + switch (b.host.getExternalExecutor(exe.target_info, .{ + .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, + .link_libc = exe.is_linking_libc, + })) { + .native, .rosetta => { + if (allow_skip) return error.MakeSkipped; + break :interpret; + }, + .wine => |bin_name| { + if (b.enable_wine) { + try interp_argv.append(bin_name); + try interp_argv.appendSlice(argv); + } else { + return failForeign(self, "-fwine", argv[0], exe); + } + }, + .qemu => |bin_name| { + if (b.enable_qemu) { + const glibc_dir_arg = if (need_cross_glibc) + b.glibc_runtimes_dir orelse + return failForeign(self, "--glibc-runtimes", argv[0], exe) + else + null; + + try interp_argv.append(bin_name); + + if (glibc_dir_arg) |dir| { + // TODO look into making this a call to `linuxTriple`. This + // needs the directory to be called "i686" rather than + // "x86" which is why we do it manually here. + const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; + const cpu_arch = exe.target.getCpuArch(); + const os_tag = exe.target.getOsTag(); + const abi = exe.target.getAbi(); + const cpu_arch_name: []const u8 = if (cpu_arch == .x86) + "i686" + else + @tagName(cpu_arch); + const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{ + dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), + }); + + try interp_argv.append("-L"); + try interp_argv.append(full_dir); + } + + try interp_argv.appendSlice(argv); + } else { + return failForeign(self, "-fqemu", argv[0], exe); + } + }, + .darling => |bin_name| { + if (b.enable_darling) { + try interp_argv.append(bin_name); + try interp_argv.appendSlice(argv); + } else { + return failForeign(self, "-fdarling", argv[0], exe); + } + }, + .wasmtime => |bin_name| { + if (b.enable_wasmtime) { + try interp_argv.append(bin_name); + try interp_argv.append("--dir=."); + try interp_argv.append(argv[0]); + try interp_argv.append("--"); + try interp_argv.appendSlice(argv[1..]); + } else { + return failForeign(self, "-fwasmtime", argv[0], exe); + } + }, + .bad_dl => |foreign_dl| { + if (allow_skip) return error.MakeSkipped; + + const host_dl = b.host.dynamic_linker.get() orelse "(none)"; + + return step.fail( + \\the host system is unable to execute binaries from the target + \\ because the host dynamic linker is '{s}', + \\ while the target dynamic linker is '{s}'. + \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step + , .{ host_dl, foreign_dl }); + }, + .bad_os_or_cpu => { + if (allow_skip) return error.MakeSkipped; + + const host_name = try b.host.target.zigTriple(b.allocator); + const foreign_name = try exe.target.zigTriple(b.allocator); + + return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{ + host_name, foreign_name, + }); + }, + } + + if (exe.target.isWindows()) { + // On Windows we don't have rpaths so we have to add .dll search paths to PATH + self.addPathForDynLibs(exe); + } + + try Step.handleVerbose2(step.owner, self.cwd, self.env_map, interp_argv.items); + + break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| { + return step.fail("unable to spawn interpreter {s}: {s}", .{ + interp_argv.items[0], @errorName(e), + }); + }; + } + + return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); + }; + + step.result_duration_ns = result.elapsed_ns; + step.result_peak_rss = result.peak_rss; + step.test_results = result.stdio.test_results; + + // Capture stdout and stderr to GeneratedFile objects. + const Stream = struct { + captured: ?*Output, + is_null: bool, + bytes: []const u8, + }; + for ([_]Stream{ + .{ + .captured = self.captured_stdout, + .is_null = result.stdio.stdout_null, + .bytes = result.stdio.stdout, + }, + .{ + .captured = self.captured_stderr, + .is_null = result.stdio.stderr_null, + .bytes = result.stdio.stderr, + }, + }) |stream| { + if (stream.captured) |output| { + assert(!stream.is_null); + + const output_components = .{ "o", digest.?, output.basename }; + const output_path = try b.cache_root.join(arena, &output_components); + output.generated_file.path = output_path; + + const sub_path = try fs.path.join(arena, &output_components); + const sub_path_dirname = fs.path.dirname(sub_path).?; + b.cache_root.handle.makePath(sub_path_dirname) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, sub_path_dirname, @errorName(err), + }); + }; + b.cache_root.handle.writeFile(sub_path, stream.bytes) catch |err| { + return step.fail("unable to write file '{}{s}': {s}", .{ + b.cache_root, sub_path, @errorName(err), + }); + }; + } + } + + const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; + + switch (self.stdio) { + .check => |checks| for (checks.items) |check| switch (check) { + .expect_stderr_exact => |expected_bytes| { + assert(!result.stdio.stderr_null); + if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) { + return step.fail( + \\ + \\========= expected this stderr: ========= + \\{s} + \\========= but found: ==================== + \\{s} + \\========= from the following command: === + \\{s} + , .{ + expected_bytes, + result.stdio.stderr, + try Step.allocPrintCmd(arena, self.cwd, final_argv), + }); + } + }, + .expect_stderr_match => |match| { + assert(!result.stdio.stderr_null); + if (mem.indexOf(u8, result.stdio.stderr, match) == null) { + return step.fail( + \\ + \\========= expected to find in stderr: ========= + \\{s} + \\========= but stderr does not contain it: ===== + \\{s} + \\========= from the following command: ========= + \\{s} + , .{ + match, + result.stdio.stderr, + try Step.allocPrintCmd(arena, self.cwd, final_argv), + }); + } + }, + .expect_stdout_exact => |expected_bytes| { + assert(!result.stdio.stdout_null); + if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) { + return step.fail( + \\ + \\========= expected this stdout: ========= + \\{s} + \\========= but found: ==================== + \\{s} + \\========= from the following command: === + \\{s} + , .{ + expected_bytes, + result.stdio.stdout, + try Step.allocPrintCmd(arena, self.cwd, final_argv), + }); + } + }, + .expect_stdout_match => |match| { + assert(!result.stdio.stdout_null); + if (mem.indexOf(u8, result.stdio.stdout, match) == null) { + return step.fail( + \\ + \\========= expected to find in stdout: ========= + \\{s} + \\========= but stdout does not contain it: ===== + \\{s} + \\========= from the following command: ========= + \\{s} + , .{ + match, + result.stdio.stdout, + try Step.allocPrintCmd(arena, self.cwd, final_argv), + }); + } + }, + .expect_term => |expected_term| { + if (!termMatches(expected_term, result.term)) { + return step.fail("the following command {} (expected {}):\n{s}", .{ + fmtTerm(result.term), + fmtTerm(expected_term), + try Step.allocPrintCmd(arena, self.cwd, final_argv), + }); + } + }, + }, + .zig_test => { + const prefix: []const u8 = p: { + if (result.stdio.test_metadata) |tm| { + if (tm.next_index <= tm.names.len) { + const name = tm.testName(tm.next_index - 1); + break :p b.fmt("while executing test '{s}', ", .{name}); + } + } + break :p ""; + }; + const expected_term: std.process.Child.Term = .{ .Exited = 0 }; + if (!termMatches(expected_term, result.term)) { + return step.fail("{s}the following command {} (expected {}):\n{s}", .{ + prefix, + fmtTerm(result.term), + fmtTerm(expected_term), + try Step.allocPrintCmd(arena, self.cwd, final_argv), + }); + } + if (!result.stdio.test_results.isSuccess()) { + return step.fail( + "{s}the following test command failed:\n{s}", + .{ prefix, try Step.allocPrintCmd(arena, self.cwd, final_argv) }, + ); + } + }, + else => { + try step.handleChildProcessTerm(result.term, self.cwd, final_argv); + }, + } +} + +const ChildProcResult = struct { + term: std.process.Child.Term, + elapsed_ns: u64, + peak_rss: usize, + + stdio: StdIoResult, +}; + +fn spawnChildAndCollect( + self: *RunStep, + argv: []const []const u8, + has_side_effects: bool, + prog_node: *std.Progress.Node, +) !ChildProcResult { + const b = self.step.owner; + const arena = b.allocator; + + var child = std.process.Child.init(argv, arena); + if (self.cwd) |cwd| { + child.cwd = b.pathFromRoot(cwd); + } else { + child.cwd = b.build_root.path; + child.cwd_dir = b.build_root.handle; + } + child.env_map = self.env_map orelse b.env_map; + child.request_resource_usage_statistics = true; + + child.stdin_behavior = switch (self.stdio) { + .infer_from_args => if (has_side_effects) .Inherit else .Ignore, + .inherit => .Inherit, + .check => .Ignore, + .zig_test => .Pipe, + }; + child.stdout_behavior = switch (self.stdio) { + .infer_from_args => if (has_side_effects) .Inherit else .Ignore, + .inherit => .Inherit, + .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore, + .zig_test => .Pipe, + }; + child.stderr_behavior = switch (self.stdio) { + .infer_from_args => if (has_side_effects) .Inherit else .Pipe, + .inherit => .Inherit, + .check => .Pipe, + .zig_test => .Pipe, + }; + if (self.captured_stdout != null) child.stdout_behavior = .Pipe; + if (self.captured_stderr != null) child.stderr_behavior = .Pipe; + if (self.stdin != null) { + assert(child.stdin_behavior != .Inherit); + child.stdin_behavior = .Pipe; + } + + try child.spawn(); + var timer = try std.time.Timer.start(); + + const result = if (self.stdio == .zig_test) + evalZigTest(self, &child, prog_node) + else + evalGeneric(self, &child); + + const term = try child.wait(); + const elapsed_ns = timer.read(); + + return .{ + .stdio = try result, + .term = term, + .elapsed_ns = elapsed_ns, + .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, + }; +} + +const StdIoResult = struct { + // These use boolean flags instead of optionals as a workaround for + // https://github.com/ziglang/zig/issues/14783 + stdout: []const u8, + stderr: []const u8, + stdout_null: bool, + stderr_null: bool, + test_results: Step.TestResults, + test_metadata: ?TestMetadata, +}; + +fn evalZigTest( + self: *RunStep, + child: *std.process.Child, + prog_node: *std.Progress.Node, +) !StdIoResult { + const gpa = self.step.owner.allocator; + const arena = self.step.owner.allocator; + + var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ + .stdout = child.stdout.?, + .stderr = child.stderr.?, + }); + defer poller.deinit(); + + try sendMessage(child.stdin.?, .query_test_metadata); + + const Header = std.zig.Server.Message.Header; + + const stdout = poller.fifo(.stdout); + const stderr = poller.fifo(.stderr); + + var fail_count: u32 = 0; + var skip_count: u32 = 0; + var leak_count: u32 = 0; + var test_count: u32 = 0; + + var metadata: ?TestMetadata = null; + + var sub_prog_node: ?std.Progress.Node = null; + defer if (sub_prog_node) |*n| n.end(); + + poll: while (true) { + while (stdout.readableLength() < @sizeOf(Header)) { + if (!(try poller.poll())) break :poll; + } + const header = stdout.reader().readStruct(Header) catch unreachable; + while (stdout.readableLength() < header.bytes_len) { + if (!(try poller.poll())) break :poll; + } + const body = stdout.readableSliceOfLen(header.bytes_len); + + switch (header.tag) { + .zig_version => { + if (!std.mem.eql(u8, builtin.zig_version_string, body)) { + return self.step.fail( + "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", + .{ builtin.zig_version_string, body }, + ); + } + }, + .test_metadata => { + const TmHdr = std.zig.Server.Message.TestMetadata; + const tm_hdr = @ptrCast(*align(1) const TmHdr, body); + test_count = tm_hdr.tests_len; + + const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)]; + const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)]; + const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)]; + const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len]; + + const names = std.mem.bytesAsSlice(u32, names_bytes); + const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes); + const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes); + const names_aligned = try arena.alloc(u32, names.len); + for (names_aligned, names) |*dest, src| dest.* = src; + + const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len); + for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src; + + const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len); + for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src; + + prog_node.setEstimatedTotalItems(names.len); + metadata = .{ + .string_bytes = try arena.dupe(u8, string_bytes), + .names = names_aligned, + .async_frame_lens = async_frame_lens_aligned, + .expected_panic_msgs = expected_panic_msgs_aligned, + .next_index = 0, + .prog_node = prog_node, + }; + + try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); + }, + .test_results => { + const md = metadata.?; + + const TrHdr = std.zig.Server.Message.TestResults; + const tr_hdr = @ptrCast(*align(1) const TrHdr, body); + fail_count += @boolToInt(tr_hdr.flags.fail); + skip_count += @boolToInt(tr_hdr.flags.skip); + leak_count += @boolToInt(tr_hdr.flags.leak); + + if (tr_hdr.flags.fail or tr_hdr.flags.leak) { + const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0); + const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n"); + const label = if (tr_hdr.flags.fail) "failed" else "leaked"; + if (msg.len > 0) { + try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg }); + } else { + try self.step.addError("'{s}' {s}", .{ name, label }); + } + stderr.discard(msg.len); + } + + try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); + }, + else => {}, // ignore other messages + } + + stdout.discard(body.len); + } + + if (stderr.readableLength() > 0) { + const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n"); + if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg); + } + + // Send EOF to stdin. + child.stdin.?.close(); + child.stdin = null; + + return .{ + .stdout = &.{}, + .stderr = &.{}, + .stdout_null = true, + .stderr_null = true, + .test_results = .{ + .test_count = test_count, + .fail_count = fail_count, + .skip_count = skip_count, + .leak_count = leak_count, + }, + .test_metadata = metadata, + }; +} + +const TestMetadata = struct { + names: []const u32, + async_frame_lens: []const u32, + expected_panic_msgs: []const u32, + string_bytes: []const u8, + next_index: u32, + prog_node: *std.Progress.Node, + + fn testName(tm: TestMetadata, index: u32) []const u8 { + return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); + } +}; + +fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { + while (metadata.next_index < metadata.names.len) { + const i = metadata.next_index; + metadata.next_index += 1; + + if (metadata.async_frame_lens[i] != 0) continue; + if (metadata.expected_panic_msgs[i] != 0) continue; + + const name = metadata.testName(i); + if (sub_prog_node.*) |*n| n.end(); + sub_prog_node.* = metadata.prog_node.start(name, 0); + + try sendRunTestMessage(in, i); + return; + } else { + try sendMessage(in, .exit); + } +} + +fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = tag, + .bytes_len = 0, + }; + try file.writeAll(std.mem.asBytes(&header)); +} + +fn sendRunTestMessage(file: std.fs.File, index: u32) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = .run_test, + .bytes_len = 4, + }; + const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index); + try file.writeAll(full_msg); +} + +fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult { + const arena = self.step.owner.allocator; + + if (self.stdin) |stdin| { + child.stdin.?.writeAll(stdin) catch |err| { + return self.step.fail("unable to write stdin: {s}", .{@errorName(err)}); + }; + child.stdin.?.close(); + child.stdin = null; + } + + // These are not optionals, as a workaround for + // https://github.com/ziglang/zig/issues/14783 + var stdout_bytes: []const u8 = undefined; + var stderr_bytes: []const u8 = undefined; + var stdout_null = true; + var stderr_null = true; + + if (child.stdout) |stdout| { + if (child.stderr) |stderr| { + var poller = std.io.poll(arena, enum { stdout, stderr }, .{ + .stdout = stdout, + .stderr = stderr, + }); + defer poller.deinit(); + + while (try poller.poll()) { + if (poller.fifo(.stdout).count > self.max_stdio_size) + return error.StdoutStreamTooLong; + if (poller.fifo(.stderr).count > self.max_stdio_size) + return error.StderrStreamTooLong; + } + + stdout_bytes = try poller.fifo(.stdout).toOwnedSlice(); + stderr_bytes = try poller.fifo(.stderr).toOwnedSlice(); + stdout_null = false; + stderr_null = false; + } else { + stdout_bytes = try stdout.reader().readAllAlloc(arena, self.max_stdio_size); + stdout_null = false; + } + } else if (child.stderr) |stderr| { + stderr_bytes = try stderr.reader().readAllAlloc(arena, self.max_stdio_size); + stderr_null = false; + } + + if (!stderr_null and stderr_bytes.len > 0) { + // Treat stderr as an error message. + const stderr_is_diagnostic = self.captured_stderr == null and switch (self.stdio) { + .check => |checks| !checksContainStderr(checks.items), + else => true, + }; + if (stderr_is_diagnostic) { + try self.step.result_error_msgs.append(arena, stderr_bytes); + } + } + + return .{ + .stdout = stdout_bytes, + .stderr = stderr_bytes, + .stdout_null = stdout_null, + .stderr_null = stderr_null, + .test_results = .{}, + .test_metadata = null, + }; +} + +fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void { + const b = self.step.owner; + for (artifact.link_objects.items) |link_object| { + switch (link_object) { + .other_step => |other| { + if (other.target.isWindows() and other.isDynamicLibrary()) { + addPathDir(self, fs.path.dirname(other.getOutputSource().getPath(b)).?); + addPathForDynLibs(self, other); + } + }, + else => {}, + } + } +} + +fn failForeign( + self: *RunStep, + suggested_flag: []const u8, + argv0: []const u8, + exe: *CompileStep, +) error{ MakeFailed, MakeSkipped, OutOfMemory } { + switch (self.stdio) { + .check, .zig_test => { + if (self.skip_foreign_checks) + return error.MakeSkipped; + + const b = self.step.owner; + const host_name = try b.host.target.zigTriple(b.allocator); + const foreign_name = try exe.target.zigTriple(b.allocator); + + return self.step.fail( + \\unable to spawn foreign binary '{s}' ({s}) on host system ({s}) + \\ consider using {s} or enabling skip_foreign_checks in the Run step + , .{ argv0, foreign_name, host_name, suggested_flag }); + }, + else => { + return self.step.fail("unable to spawn foreign binary '{s}'", .{argv0}); + }, + } +} + +fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { + switch (stdio) { + .infer_from_args, .inherit, .zig_test => {}, + .check => |checks| for (checks.items) |check| { + hh.add(@as(std.meta.Tag(StdIo.Check), check)); + switch (check) { + .expect_stderr_exact, + .expect_stderr_match, + .expect_stdout_exact, + .expect_stdout_match, + => |s| hh.addBytes(s), + + .expect_term => |term| { + hh.add(@as(std.meta.Tag(std.process.Child.Term), term)); + switch (term) { + .Exited => |x| hh.add(x), + .Signal, .Stopped, .Unknown => |x| hh.add(x), + } + }, + } + }, + } +} diff --git a/lib/std/Build/Step/TranslateC.zig b/lib/std/Build/Step/TranslateC.zig new file mode 100644 index 0000000000000000000000000000000000000000..86727ea2f0454214659ce2778bde557c3436bf5e --- /dev/null +++ b/lib/std/Build/Step/TranslateC.zig @@ -0,0 +1,136 @@ +const std = @import("std"); +const Step = std.Build.Step; +const CompileStep = std.Build.CompileStep; +const CheckFileStep = std.Build.CheckFileStep; +const fs = std.fs; +const mem = std.mem; +const CrossTarget = std.zig.CrossTarget; + +const TranslateCStep = @This(); + +pub const base_id = .translate_c; + +step: Step, +source: std.Build.FileSource, +include_dirs: std.ArrayList([]const u8), +c_macros: std.ArrayList([]const u8), +out_basename: []const u8, +target: CrossTarget, +optimize: std.builtin.OptimizeMode, +output_file: std.Build.GeneratedFile, + +pub const Options = struct { + source_file: std.Build.FileSource, + target: CrossTarget, + optimize: std.builtin.OptimizeMode, +}; + +pub fn create(owner: *std.Build, options: Options) *TranslateCStep { + const self = owner.allocator.create(TranslateCStep) catch @panic("OOM"); + const source = options.source_file.dupe(owner); + self.* = TranslateCStep{ + .step = Step.init(.{ + .id = .translate_c, + .name = "translate-c", + .owner = owner, + .makeFn = make, + }), + .source = source, + .include_dirs = std.ArrayList([]const u8).init(owner.allocator), + .c_macros = std.ArrayList([]const u8).init(owner.allocator), + .out_basename = undefined, + .target = options.target, + .optimize = options.optimize, + .output_file = std.Build.GeneratedFile{ .step = &self.step }, + }; + source.addStepDependencies(&self.step); + return self; +} + +pub const AddExecutableOptions = struct { + name: ?[]const u8 = null, + version: ?std.builtin.Version = null, + target: ?CrossTarget = null, + optimize: ?std.builtin.Mode = null, + linkage: ?CompileStep.Linkage = null, +}; + +/// Creates a step to build an executable from the translated source. +pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep { + return self.step.owner.addExecutable(.{ + .root_source_file = .{ .generated = &self.output_file }, + .name = options.name orelse "translated_c", + .version = options.version, + .target = options.target orelse self.target, + .optimize = options.optimize orelse self.optimize, + .linkage = options.linkage, + }); +} + +pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void { + self.include_dirs.append(self.step.owner.dupePath(include_dir)) catch @panic("OOM"); +} + +pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep { + return CheckFileStep.create( + self.step.owner, + .{ .generated = &self.output_file }, + .{ .expected_matches = expected_matches }, + ); +} + +/// If the value is omitted, it is set to 1. +/// `name` and `value` need not live longer than the function call. +pub fn defineCMacro(self: *TranslateCStep, name: []const u8, value: ?[]const u8) void { + const macro = std.Build.constructCMacro(self.step.owner.allocator, name, value); + self.c_macros.append(macro) catch @panic("OOM"); +} + +/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. +pub fn defineCMacroRaw(self: *TranslateCStep, name_and_value: []const u8) void { + self.c_macros.append(self.step.owner.dupe(name_and_value)) catch @panic("OOM"); +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + const b = step.owner; + const self = @fieldParentPtr(TranslateCStep, "step", step); + + var argv_list = std.ArrayList([]const u8).init(b.allocator); + try argv_list.append(b.zig_exe); + try argv_list.append("translate-c"); + try argv_list.append("-lc"); + + try argv_list.append("--listen=-"); + + if (!self.target.isNative()) { + try argv_list.append("-target"); + try argv_list.append(try self.target.zigTriple(b.allocator)); + } + + switch (self.optimize) { + .Debug => {}, // Skip since it's the default. + else => try argv_list.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), + } + + for (self.include_dirs.items) |include_dir| { + try argv_list.append("-I"); + try argv_list.append(include_dir); + } + + for (self.c_macros.items) |c_macro| { + try argv_list.append("-D"); + try argv_list.append(c_macro); + } + + try argv_list.append(self.source.getPath(b)); + + const output_path = try step.evalZigProcess(argv_list.items, prog_node); + + self.out_basename = fs.path.basename(output_path); + const output_dir = fs.path.dirname(output_path).?; + + self.output_file.path = try fs.path.join( + b.allocator, + &[_][]const u8{ output_dir, self.out_basename }, + ); +} diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig new file mode 100644 index 0000000000000000000000000000000000000000..68f7c37c6c94cbde3dddfca58a435b91c3087506 --- /dev/null +++ b/lib/std/Build/Step/WriteFile.zig @@ -0,0 +1,291 @@ +//! WriteFileStep is primarily used to create a directory in an appropriate +//! location inside the local cache which has a set of files that have either +//! been generated during the build, or are copied from the source package. +//! +//! However, this step has an additional capability of writing data to paths +//! relative to the package root, effectively mutating the package's source +//! files. Be careful with the latter functionality; it should not be used +//! during the normal build process, but as a utility run by a developer with +//! intention to update source files, which will then be committed to version +//! control. +const std = @import("std"); +const Step = std.Build.Step; +const fs = std.fs; +const ArrayList = std.ArrayList; +const WriteFileStep = @This(); + +step: Step, +/// The elements here are pointers because we need stable pointers for the +/// GeneratedFile field. +files: std.ArrayListUnmanaged(*File), +output_source_files: std.ArrayListUnmanaged(OutputSourceFile), +generated_directory: std.Build.GeneratedFile, + +pub const base_id = .write_file; + +pub const File = struct { + generated_file: std.Build.GeneratedFile, + sub_path: []const u8, + contents: Contents, +}; + +pub const OutputSourceFile = struct { + contents: Contents, + sub_path: []const u8, +}; + +pub const Contents = union(enum) { + bytes: []const u8, + copy: std.Build.FileSource, +}; + +pub fn create(owner: *std.Build) *WriteFileStep { + const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM"); + wf.* = .{ + .step = Step.init(.{ + .id = .write_file, + .name = "WriteFile", + .owner = owner, + .makeFn = make, + }), + .files = .{}, + .output_source_files = .{}, + .generated_directory = .{ .step = &wf.step }, + }; + return wf; +} + +pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { + const b = wf.step.owner; + const gpa = b.allocator; + const file = gpa.create(File) catch @panic("OOM"); + file.* = .{ + .generated_file = .{ .step = &wf.step }, + .sub_path = b.dupePath(sub_path), + .contents = .{ .bytes = b.dupe(bytes) }, + }; + wf.files.append(gpa, file) catch @panic("OOM"); + + wf.maybeUpdateName(); +} + +/// Place the file into the generated directory within the local cache, +/// along with all the rest of the files added to this step. The parameter +/// here is the destination path relative to the local cache directory +/// associated with this WriteFileStep. It may be a basename, or it may +/// include sub-directories, in which case this step will ensure the +/// required sub-path exists. +/// This is the option expected to be used most commonly with `addCopyFile`. +pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { + const b = wf.step.owner; + const gpa = b.allocator; + const file = gpa.create(File) catch @panic("OOM"); + file.* = .{ + .generated_file = .{ .step = &wf.step }, + .sub_path = b.dupePath(sub_path), + .contents = .{ .copy = source }, + }; + wf.files.append(gpa, file) catch @panic("OOM"); + + wf.maybeUpdateName(); + source.addStepDependencies(&wf.step); +} + +/// A path relative to the package root. +/// Be careful with this because it updates source files. This should not be +/// used as part of the normal build process, but as a utility occasionally +/// run by a developer with intent to modify source files and then commit +/// those changes to version control. +/// A file added this way is not available with `getFileSource`. +pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { + const b = wf.step.owner; + wf.output_source_files.append(b.allocator, .{ + .contents = .{ .copy = source }, + .sub_path = sub_path, + }) catch @panic("OOM"); + source.addStepDependencies(&wf.step); +} + +/// A path relative to the package root. +/// Be careful with this because it updates source files. This should not be +/// used as part of the normal build process, but as a utility occasionally +/// run by a developer with intent to modify source files and then commit +/// those changes to version control. +/// A file added this way is not available with `getFileSource`. +pub fn addBytesToSource(wf: *WriteFileStep, bytes: []const u8, sub_path: []const u8) void { + const b = wf.step.owner; + wf.output_source_files.append(b.allocator, .{ + .contents = .{ .bytes = bytes }, + .sub_path = sub_path, + }) catch @panic("OOM"); +} + +/// Gets a file source for the given sub_path. If the file does not exist, returns `null`. +pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSource { + for (wf.files.items) |file| { + if (std.mem.eql(u8, file.sub_path, sub_path)) { + return .{ .generated = &file.generated_file }; + } + } + return null; +} + +/// Returns a `FileSource` representing the base directory that contains all the +/// files from this `WriteFileStep`. +pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource { + return .{ .generated = &wf.generated_directory }; +} + +fn maybeUpdateName(wf: *WriteFileStep) void { + if (wf.files.items.len == 1) { + // First time adding a file; update name. + if (std.mem.eql(u8, wf.step.name, "WriteFile")) { + wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path}); + } + } +} + +fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const b = step.owner; + const wf = @fieldParentPtr(WriteFileStep, "step", step); + + // Writing to source files is kind of an extra capability of this + // WriteFileStep - arguably it should be a different step. But anyway here + // it is, it happens unconditionally and does not interact with the other + // files here. + var any_miss = false; + for (wf.output_source_files.items) |output_source_file| { + if (fs.path.dirname(output_source_file.sub_path)) |dirname| { + b.build_root.handle.makePath(dirname) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.build_root, dirname, @errorName(err), + }); + }; + } + switch (output_source_file.contents) { + .bytes => |bytes| { + b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| { + return step.fail("unable to write file '{}{s}': {s}", .{ + b.build_root, output_source_file.sub_path, @errorName(err), + }); + }; + any_miss = true; + }, + .copy => |file_source| { + const source_path = file_source.getPath(b); + const prev_status = fs.Dir.updateFile( + fs.cwd(), + source_path, + b.build_root.handle, + output_source_file.sub_path, + .{}, + ) catch |err| { + return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{ + source_path, b.build_root, output_source_file.sub_path, @errorName(err), + }); + }; + any_miss = any_miss or prev_status == .stale; + }, + } + } + + // The cache is used here not really as a way to speed things up - because writing + // the data to a file would probably be very fast - but as a way to find a canonical + // location to put build artifacts. + + // If, for example, a hard-coded path was used as the location to put WriteFileStep + // files, then two WriteFileSteps executing in parallel might clobber each other. + + var man = b.cache.obtain(); + defer man.deinit(); + + // Random bytes to make WriteFileStep unique. Refresh this with + // new random bytes when WriteFileStep implementation is modified + // in a non-backwards-compatible way. + man.hash.add(@as(u32, 0xd767ee59)); + + for (wf.files.items) |file| { + man.hash.addBytes(file.sub_path); + switch (file.contents) { + .bytes => |bytes| { + man.hash.addBytes(bytes); + }, + .copy => |file_source| { + _ = try man.addFile(file_source.getPath(b), null); + }, + } + } + + if (try step.cacheHit(&man)) { + const digest = man.final(); + for (wf.files.items) |file| { + file.generated_file.path = try b.cache_root.join(b.allocator, &.{ + "o", &digest, file.sub_path, + }); + } + wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); + return; + } + + const digest = man.final(); + const cache_path = "o" ++ fs.path.sep_str ++ digest; + + wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); + + var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { + return step.fail("unable to make path '{}{s}': {s}", .{ + b.cache_root, cache_path, @errorName(err), + }); + }; + defer cache_dir.close(); + + for (wf.files.items) |file| { + if (fs.path.dirname(file.sub_path)) |dirname| { + cache_dir.makePath(dirname) catch |err| { + return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{ + b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err), + }); + }; + } + switch (file.contents) { + .bytes => |bytes| { + cache_dir.writeFile(file.sub_path, bytes) catch |err| { + return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{ + b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err), + }); + }; + }, + .copy => |file_source| { + const source_path = file_source.getPath(b); + const prev_status = fs.Dir.updateFile( + fs.cwd(), + source_path, + cache_dir, + file.sub_path, + .{}, + ) catch |err| { + return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{ + source_path, + b.cache_root, + cache_path, + fs.path.sep, + file.sub_path, + @errorName(err), + }); + }; + // At this point we already will mark the step as a cache miss. + // But this is kind of a partial cache hit since individual + // file copies may be avoided. Oh well, this information is + // discarded. + _ = prev_status; + }, + } + + file.generated_file.path = try b.cache_root.join(b.allocator, &.{ + cache_path, file.sub_path, + }); + } + + try step.writeManifest(&man); +} diff --git a/lib/std/Build/TranslateCStep.zig b/lib/std/Build/TranslateCStep.zig deleted file mode 100644 index f2dc23d950740c08f37217049dc5e7796b85ad93..0000000000000000000000000000000000000000 --- a/lib/std/Build/TranslateCStep.zig +++ /dev/null @@ -1,136 +0,0 @@ -const std = @import("../std.zig"); -const Step = std.Build.Step; -const CompileStep = std.Build.CompileStep; -const CheckFileStep = std.Build.CheckFileStep; -const fs = std.fs; -const mem = std.mem; -const CrossTarget = std.zig.CrossTarget; - -const TranslateCStep = @This(); - -pub const base_id = .translate_c; - -step: Step, -source: std.Build.FileSource, -include_dirs: std.ArrayList([]const u8), -c_macros: std.ArrayList([]const u8), -out_basename: []const u8, -target: CrossTarget, -optimize: std.builtin.OptimizeMode, -output_file: std.Build.GeneratedFile, - -pub const Options = struct { - source_file: std.Build.FileSource, - target: CrossTarget, - optimize: std.builtin.OptimizeMode, -}; - -pub fn create(owner: *std.Build, options: Options) *TranslateCStep { - const self = owner.allocator.create(TranslateCStep) catch @panic("OOM"); - const source = options.source_file.dupe(owner); - self.* = TranslateCStep{ - .step = Step.init(.{ - .id = .translate_c, - .name = "translate-c", - .owner = owner, - .makeFn = make, - }), - .source = source, - .include_dirs = std.ArrayList([]const u8).init(owner.allocator), - .c_macros = std.ArrayList([]const u8).init(owner.allocator), - .out_basename = undefined, - .target = options.target, - .optimize = options.optimize, - .output_file = std.Build.GeneratedFile{ .step = &self.step }, - }; - source.addStepDependencies(&self.step); - return self; -} - -pub const AddExecutableOptions = struct { - name: ?[]const u8 = null, - version: ?std.builtin.Version = null, - target: ?CrossTarget = null, - optimize: ?std.builtin.Mode = null, - linkage: ?CompileStep.Linkage = null, -}; - -/// Creates a step to build an executable from the translated source. -pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep { - return self.step.owner.addExecutable(.{ - .root_source_file = .{ .generated = &self.output_file }, - .name = options.name orelse "translated_c", - .version = options.version, - .target = options.target orelse self.target, - .optimize = options.optimize orelse self.optimize, - .linkage = options.linkage, - }); -} - -pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void { - self.include_dirs.append(self.step.owner.dupePath(include_dir)) catch @panic("OOM"); -} - -pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep { - return CheckFileStep.create( - self.step.owner, - .{ .generated = &self.output_file }, - .{ .expected_matches = expected_matches }, - ); -} - -/// If the value is omitted, it is set to 1. -/// `name` and `value` need not live longer than the function call. -pub fn defineCMacro(self: *TranslateCStep, name: []const u8, value: ?[]const u8) void { - const macro = std.Build.constructCMacro(self.step.owner.allocator, name, value); - self.c_macros.append(macro) catch @panic("OOM"); -} - -/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. -pub fn defineCMacroRaw(self: *TranslateCStep, name_and_value: []const u8) void { - self.c_macros.append(self.step.owner.dupe(name_and_value)) catch @panic("OOM"); -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - const b = step.owner; - const self = @fieldParentPtr(TranslateCStep, "step", step); - - var argv_list = std.ArrayList([]const u8).init(b.allocator); - try argv_list.append(b.zig_exe); - try argv_list.append("translate-c"); - try argv_list.append("-lc"); - - try argv_list.append("--listen=-"); - - if (!self.target.isNative()) { - try argv_list.append("-target"); - try argv_list.append(try self.target.zigTriple(b.allocator)); - } - - switch (self.optimize) { - .Debug => {}, // Skip since it's the default. - else => try argv_list.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), - } - - for (self.include_dirs.items) |include_dir| { - try argv_list.append("-I"); - try argv_list.append(include_dir); - } - - for (self.c_macros.items) |c_macro| { - try argv_list.append("-D"); - try argv_list.append(c_macro); - } - - try argv_list.append(self.source.getPath(b)); - - const output_path = try step.evalZigProcess(argv_list.items, prog_node); - - self.out_basename = fs.path.basename(output_path); - const output_dir = fs.path.dirname(output_path).?; - - self.output_file.path = try fs.path.join( - b.allocator, - &[_][]const u8{ output_dir, self.out_basename }, - ); -} diff --git a/lib/std/Build/WriteFileStep.zig b/lib/std/Build/WriteFileStep.zig deleted file mode 100644 index dee79af5bee544f35ff72f32602f4a24b48ac7cb..0000000000000000000000000000000000000000 --- a/lib/std/Build/WriteFileStep.zig +++ /dev/null @@ -1,293 +0,0 @@ -//! WriteFileStep is primarily used to create a directory in an appropriate -//! location inside the local cache which has a set of files that have either -//! been generated during the build, or are copied from the source package. -//! -//! However, this step has an additional capability of writing data to paths -//! relative to the package root, effectively mutating the package's source -//! files. Be careful with the latter functionality; it should not be used -//! during the normal build process, but as a utility run by a developer with -//! intention to update source files, which will then be committed to version -//! control. - -step: Step, -/// The elements here are pointers because we need stable pointers for the -/// GeneratedFile field. -files: std.ArrayListUnmanaged(*File), -output_source_files: std.ArrayListUnmanaged(OutputSourceFile), -generated_directory: std.Build.GeneratedFile, - -pub const base_id = .write_file; - -pub const File = struct { - generated_file: std.Build.GeneratedFile, - sub_path: []const u8, - contents: Contents, -}; - -pub const OutputSourceFile = struct { - contents: Contents, - sub_path: []const u8, -}; - -pub const Contents = union(enum) { - bytes: []const u8, - copy: std.Build.FileSource, -}; - -pub fn create(owner: *std.Build) *WriteFileStep { - const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM"); - wf.* = .{ - .step = Step.init(.{ - .id = .write_file, - .name = "WriteFile", - .owner = owner, - .makeFn = make, - }), - .files = .{}, - .output_source_files = .{}, - .generated_directory = .{ .step = &wf.step }, - }; - return wf; -} - -pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { - const b = wf.step.owner; - const gpa = b.allocator; - const file = gpa.create(File) catch @panic("OOM"); - file.* = .{ - .generated_file = .{ .step = &wf.step }, - .sub_path = b.dupePath(sub_path), - .contents = .{ .bytes = b.dupe(bytes) }, - }; - wf.files.append(gpa, file) catch @panic("OOM"); - - wf.maybeUpdateName(); -} - -/// Place the file into the generated directory within the local cache, -/// along with all the rest of the files added to this step. The parameter -/// here is the destination path relative to the local cache directory -/// associated with this WriteFileStep. It may be a basename, or it may -/// include sub-directories, in which case this step will ensure the -/// required sub-path exists. -/// This is the option expected to be used most commonly with `addCopyFile`. -pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { - const b = wf.step.owner; - const gpa = b.allocator; - const file = gpa.create(File) catch @panic("OOM"); - file.* = .{ - .generated_file = .{ .step = &wf.step }, - .sub_path = b.dupePath(sub_path), - .contents = .{ .copy = source }, - }; - wf.files.append(gpa, file) catch @panic("OOM"); - - wf.maybeUpdateName(); - source.addStepDependencies(&wf.step); -} - -/// A path relative to the package root. -/// Be careful with this because it updates source files. This should not be -/// used as part of the normal build process, but as a utility occasionally -/// run by a developer with intent to modify source files and then commit -/// those changes to version control. -/// A file added this way is not available with `getFileSource`. -pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { - const b = wf.step.owner; - wf.output_source_files.append(b.allocator, .{ - .contents = .{ .copy = source }, - .sub_path = sub_path, - }) catch @panic("OOM"); - source.addStepDependencies(&wf.step); -} - -/// A path relative to the package root. -/// Be careful with this because it updates source files. This should not be -/// used as part of the normal build process, but as a utility occasionally -/// run by a developer with intent to modify source files and then commit -/// those changes to version control. -/// A file added this way is not available with `getFileSource`. -pub fn addBytesToSource(wf: *WriteFileStep, bytes: []const u8, sub_path: []const u8) void { - const b = wf.step.owner; - wf.output_source_files.append(b.allocator, .{ - .contents = .{ .bytes = bytes }, - .sub_path = sub_path, - }) catch @panic("OOM"); -} - -/// Gets a file source for the given sub_path. If the file does not exist, returns `null`. -pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSource { - for (wf.files.items) |file| { - if (std.mem.eql(u8, file.sub_path, sub_path)) { - return .{ .generated = &file.generated_file }; - } - } - return null; -} - -/// Returns a `FileSource` representing the base directory that contains all the -/// files from this `WriteFileStep`. -pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource { - return .{ .generated = &wf.generated_directory }; -} - -fn maybeUpdateName(wf: *WriteFileStep) void { - if (wf.files.items.len == 1) { - // First time adding a file; update name. - if (std.mem.eql(u8, wf.step.name, "WriteFile")) { - wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path}); - } - } -} - -fn make(step: *Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const b = step.owner; - const wf = @fieldParentPtr(WriteFileStep, "step", step); - - // Writing to source files is kind of an extra capability of this - // WriteFileStep - arguably it should be a different step. But anyway here - // it is, it happens unconditionally and does not interact with the other - // files here. - var any_miss = false; - for (wf.output_source_files.items) |output_source_file| { - if (fs.path.dirname(output_source_file.sub_path)) |dirname| { - b.build_root.handle.makePath(dirname) catch |err| { - return step.fail("unable to make path '{}{s}': {s}", .{ - b.build_root, dirname, @errorName(err), - }); - }; - } - switch (output_source_file.contents) { - .bytes => |bytes| { - b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| { - return step.fail("unable to write file '{}{s}': {s}", .{ - b.build_root, output_source_file.sub_path, @errorName(err), - }); - }; - any_miss = true; - }, - .copy => |file_source| { - const source_path = file_source.getPath(b); - const prev_status = fs.Dir.updateFile( - fs.cwd(), - source_path, - b.build_root.handle, - output_source_file.sub_path, - .{}, - ) catch |err| { - return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{ - source_path, b.build_root, output_source_file.sub_path, @errorName(err), - }); - }; - any_miss = any_miss or prev_status == .stale; - }, - } - } - - // The cache is used here not really as a way to speed things up - because writing - // the data to a file would probably be very fast - but as a way to find a canonical - // location to put build artifacts. - - // If, for example, a hard-coded path was used as the location to put WriteFileStep - // files, then two WriteFileSteps executing in parallel might clobber each other. - - var man = b.cache.obtain(); - defer man.deinit(); - - // Random bytes to make WriteFileStep unique. Refresh this with - // new random bytes when WriteFileStep implementation is modified - // in a non-backwards-compatible way. - man.hash.add(@as(u32, 0xd767ee59)); - - for (wf.files.items) |file| { - man.hash.addBytes(file.sub_path); - switch (file.contents) { - .bytes => |bytes| { - man.hash.addBytes(bytes); - }, - .copy => |file_source| { - _ = try man.addFile(file_source.getPath(b), null); - }, - } - } - - if (try step.cacheHit(&man)) { - const digest = man.final(); - for (wf.files.items) |file| { - file.generated_file.path = try b.cache_root.join(b.allocator, &.{ - "o", &digest, file.sub_path, - }); - } - wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); - return; - } - - const digest = man.final(); - const cache_path = "o" ++ fs.path.sep_str ++ digest; - - wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); - - var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { - return step.fail("unable to make path '{}{s}': {s}", .{ - b.cache_root, cache_path, @errorName(err), - }); - }; - defer cache_dir.close(); - - for (wf.files.items) |file| { - if (fs.path.dirname(file.sub_path)) |dirname| { - cache_dir.makePath(dirname) catch |err| { - return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{ - b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err), - }); - }; - } - switch (file.contents) { - .bytes => |bytes| { - cache_dir.writeFile(file.sub_path, bytes) catch |err| { - return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{ - b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err), - }); - }; - }, - .copy => |file_source| { - const source_path = file_source.getPath(b); - const prev_status = fs.Dir.updateFile( - fs.cwd(), - source_path, - cache_dir, - file.sub_path, - .{}, - ) catch |err| { - return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{ - source_path, - b.cache_root, - cache_path, - fs.path.sep, - file.sub_path, - @errorName(err), - }); - }; - // At this point we already will mark the step as a cache miss. - // But this is kind of a partial cache hit since individual - // file copies may be avoided. Oh well, this information is - // discarded. - _ = prev_status; - }, - } - - file.generated_file.path = try b.cache_root.join(b.allocator, &.{ - cache_path, file.sub_path, - }); - } - - try step.writeManifest(&man); -} - -const std = @import("../std.zig"); -const Step = std.Build.Step; -const fs = std.fs; -const ArrayList = std.ArrayList; - -const WriteFileStep = @This();