Merge pull request #11910 from ziglang/linker-tests

This commit is contained in:
Jakub Konka 2022-06-24 00:02:12 +02:00 committed by GitHub
commit 291c08f7b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 857 additions and 180 deletions

View File

@ -489,6 +489,7 @@ pub fn build(b: *Builder) !void {
toolchain_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes));
toolchain_step.dependOn(tests.addStandaloneTests(b, test_filter, modes, skip_non_native, enable_macos_sdk, target));
toolchain_step.dependOn(tests.addLinkTests(b, test_filter, modes, enable_macos_sdk));
toolchain_step.dependOn(tests.addStackTraceTests(b, test_filter, modes));
toolchain_step.dependOn(tests.addCliTests(b, test_filter, modes));
toolchain_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, modes));

View File

@ -76,6 +76,7 @@ release/bin/zig build test-run-translated-c -Denable-macos-sdk
release/bin/zig build docs -Denable-macos-sdk
release/bin/zig build test-fmt -Denable-macos-sdk
release/bin/zig build test-cases -Denable-macos-sdk -Dsingle-threaded
release/bin/zig build test-link -Denable-macos-sdk
if [ "${BUILD_REASON}" != "PullRequest" ]; then
mv ../LICENSE release/

View File

@ -24,6 +24,7 @@ pub const TranslateCStep = @import("build/TranslateCStep.zig");
pub const WriteFileStep = @import("build/WriteFileStep.zig");
pub const RunStep = @import("build/RunStep.zig");
pub const CheckFileStep = @import("build/CheckFileStep.zig");
pub const CheckObjectStep = @import("build/CheckObjectStep.zig");
pub const InstallRawStep = @import("build/InstallRawStep.zig");
pub const OptionsStep = @import("build/OptionsStep.zig");
@ -1582,6 +1583,9 @@ pub const LibExeObjStep = struct {
/// (Darwin) Path to entitlements file
entitlements: ?[]const u8 = null,
/// (Darwin) Size of the pagezero segment.
pagezero_size: ?u64 = null,
/// Position Independent Code
force_pic: ?bool = null,
@ -1861,6 +1865,10 @@ pub const LibExeObjStep = struct {
return run_step;
}
pub fn checkObject(self: *LibExeObjStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep {
return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format);
}
pub fn setLinkerScriptPath(self: *LibExeObjStep, source: FileSource) void {
self.linker_script = source.dupe(self.builder);
source.addStepDependencies(&self.step);
@ -2638,6 +2646,10 @@ pub const LibExeObjStep = struct {
if (self.entitlements) |entitlements| {
try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements });
}
if (self.pagezero_size) |pagezero_size| {
const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{pagezero_size});
try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size });
}
if (self.bundle_compiler_rt) |x| {
if (x) {
@ -3443,6 +3455,7 @@ pub const Step = struct {
write_file,
run,
check_file,
check_object,
install_raw,
options,
custom,

View File

@ -0,0 +1,392 @@
const std = @import("../std.zig");
const assert = std.debug.assert;
const build = std.build;
const fs = std.fs;
const macho = std.macho;
const mem = std.mem;
const testing = std.testing;
const CheckObjectStep = @This();
const Allocator = mem.Allocator;
const Builder = build.Builder;
const Step = build.Step;
pub const base_id = .check_obj;
step: Step,
builder: *Builder,
source: build.FileSource,
max_bytes: usize = 20 * 1024 * 1024,
checks: std.ArrayList(Check),
dump_symtab: bool = false,
obj_format: std.Target.ObjectFormat,
pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep {
const gpa = builder.allocator;
const self = gpa.create(CheckObjectStep) catch unreachable;
self.* = .{
.builder = builder,
.step = Step.init(.check_file, "CheckObject", gpa, make),
.source = source.dupe(builder),
.checks = std.ArrayList(Check).init(gpa),
.obj_format = obj_format,
};
self.source.addStepDependencies(&self.step);
return self;
}
const Action = union(enum) {
match: MatchAction,
compute_eq: ComputeEqAction,
};
/// MatchAction 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.
const MatchAction = struct {
needle: []const u8,
/// Will return true if the `needle` 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: MatchAction, haystack: []const u8, global_vars: anytype) !bool {
var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " ");
var needle_it = mem.tokenize(u8, mem.trim(u8, act.needle, " "), " ");
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);
try global_vars.putNoClobber(name, value);
} else {
if (!mem.eql(u8, hay_tok, needle_tok)) return false;
}
}
return true;
}
};
/// ComputeEqAction 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 ComputeEqAction = struct {
expected: []const u8,
var_stack: std.ArrayList([]const u8),
op_stack: std.ArrayList(Op),
const Op = enum {
add,
};
};
const Check = struct {
builder: *Builder,
actions: std.ArrayList(Action),
fn create(b: *Builder) Check {
return .{
.builder = b,
.actions = std.ArrayList(Action).init(b.allocator),
};
}
fn match(self: *Check, needle: []const u8) void {
self.actions.append(.{
.match = .{ .needle = self.builder.dupe(needle) },
}) catch unreachable;
}
fn computeEq(self: *Check, act: ComputeEqAction) void {
self.actions.append(.{
.compute_eq = act,
}) catch unreachable;
}
};
/// 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.builder);
new_check.match(phrase);
self.checks.append(new_check) catch unreachable;
}
/// 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(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 checkComputeEq(self: *CheckObjectStep, program: []const u8, expected: []const u8) void {
const gpa = self.builder.allocator;
var ca = ComputeEqAction{
.expected = expected,
.var_stack = std.ArrayList([]const u8).init(gpa),
.op_stack = std.ArrayList(ComputeEqAction.Op).init(gpa),
};
var it = mem.tokenize(u8, program, " ");
while (it.next()) |next| {
if (mem.eql(u8, next, "+")) {
ca.op_stack.append(.add) catch unreachable;
} else {
ca.var_stack.append(self.builder.dupe(next)) catch unreachable;
}
}
var new_check = Check.create(self.builder);
new_check.computeEq(ca);
self.checks.append(new_check) catch unreachable;
}
fn make(step: *Step) !void {
const self = @fieldParentPtr(CheckObjectStep, "step", step);
const gpa = self.builder.allocator;
const src_path = self.source.getPath(self.builder);
const contents = try fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
const output = switch (self.obj_format) {
.macho => try MachODumper.parseAndDump(contents, .{
.gpa = gpa,
.dump_symtab = self.dump_symtab,
}),
.elf => @panic("TODO elf parser"),
.coff => @panic("TODO coff parser"),
.wasm => @panic("TODO wasm parser"),
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) {
.match => |match_act| {
while (it.next()) |line| {
if (try match_act.match(line, &vars)) break;
} else {
std.debug.print(
\\
\\========= Expected to find: ==========================
\\{s}
\\========= But parsed file does not contain it: =======
\\{s}
\\
, .{ match_act.needle, output });
return error.TestFailed;
}
},
.compute_eq => |c_eq| {
var values = std.ArrayList(u64).init(gpa);
try values.ensureTotalCapacity(c_eq.var_stack.items.len);
for (c_eq.var_stack.items) |vv| {
const val = vars.get(vv) orelse {
std.debug.print(
\\
\\========= Variable was not extracted: ===========
\\{s}
\\========= From parsed file: =====================
\\{s}
\\
, .{ vv, output });
return error.TestFailed;
};
values.appendAssumeCapacity(val);
}
var op_i: usize = 1;
var reduced: u64 = values.items[0];
for (c_eq.op_stack.items) |op| {
const other = values.items[op_i];
switch (op) {
.add => {
reduced += other;
},
}
}
const expected = vars.get(c_eq.expected) orelse {
std.debug.print(
\\
\\========= Variable was not extracted: ===========
\\{s}
\\========= From parsed file: =====================
\\{s}
\\
, .{ c_eq.expected, output });
return error.TestFailed;
};
try testing.expectEqual(reduced, expected);
},
}
}
}
}
const Opts = struct {
gpa: ?Allocator = null,
dump_symtab: bool = false,
};
const MachODumper = struct {
const symtab_label = "symtab";
fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 {
const gpa = opts.gpa orelse unreachable; // MachO dumper requires an 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_cmd: ?macho.symtab_command = null;
var i: u16 = 0;
while (i < hdr.ncmds) : (i += 1) {
var cmd = try macho.LoadCommand.read(gpa, reader);
if (opts.dump_symtab and cmd.cmd() == .SYMTAB) {
symtab_cmd = cmd.symtab;
}
try dumpLoadCommand(cmd, i, writer);
try writer.writeByte('\n');
}
if (symtab_cmd) |cmd| {
try writer.writeAll(symtab_label ++ "\n");
const strtab = bytes[cmd.stroff..][0..cmd.strsize];
const raw_symtab = bytes[cmd.symoff..][0 .. cmd.nsyms * @sizeOf(macho.nlist_64)];
const symtab = mem.bytesAsSlice(macho.nlist_64, raw_symtab);
for (symtab) |sym| {
if (sym.stab()) continue;
const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0);
try writer.print("{s} {x}\n", .{ sym_name, sym.n_value });
}
}
return output.toOwnedSlice();
}
fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, 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 => {
// TODO dump section headers
const seg = lc.segment.inner;
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,
});
},
.ID_DYLIB,
.LOAD_DYLIB,
=> {
const dylib = lc.dylib.inner.dylib;
try writer.writeByte('\n');
try writer.print(
\\name {s}
\\timestamp {d}
\\current version {x}
\\compatibility version {x}
, .{
mem.sliceTo(lc.dylib.data, 0),
dylib.timestamp,
dylib.current_version,
dylib.compatibility_version,
});
},
.MAIN => {
try writer.writeByte('\n');
try writer.print(
\\entryoff {x}
\\stacksize {x}
, .{ lc.main.entryoff, lc.main.stacksize });
},
.RPATH => {
try writer.writeByte('\n');
try writer.print(
\\path {s}
, .{
mem.sliceTo(lc.rpath.data, 0),
});
},
else => {},
}
}
};

View File

@ -149,6 +149,7 @@ fn make(step: *Step) !void {
const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root;
var argv_list = ArrayList([]const u8).init(self.builder.allocator);
for (self.argv.items) |arg| {
switch (arg) {
.bytes => |bytes| try argv_list.append(bytes),

View File

@ -934,6 +934,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{pagezero_size}));
}
if (self.base.options.entry) |entry| {
try argv.append("-e");
try argv.append(entry);
}
try argv.appendSlice(positionals.items);
try argv.append("-o");
@ -3371,13 +3376,12 @@ fn addCodeSignatureLC(self: *MachO) !void {
fn setEntryPoint(self: *MachO) !void {
if (self.base.options.output_mode != .Exe) return;
// TODO we should respect the -entry flag passed in by the user to set a custom
// entrypoint. For now, assume default of `_main`.
const seg = self.load_commands.items[self.text_segment_cmd_index.?].segment;
const n_strx = self.strtab_dir.getKeyAdapted(@as([]const u8, "_main"), StringIndexAdapter{
const entry_name = self.base.options.entry orelse "_main";
const n_strx = self.strtab_dir.getKeyAdapted(entry_name, StringIndexAdapter{
.bytes = &self.strtab,
}) orelse {
log.err("'_main' export not found", .{});
log.err("entrypoint '{s}' not found", .{entry_name});
return error.MissingMainEntrypoint;
};
const resolv = self.symbol_resolver.get(n_strx) orelse unreachable;
@ -5711,28 +5715,46 @@ fn writeDyldInfoData(self: *MachO) !void {
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].dyld_info_only;
const rebase_off = mem.alignForwardGeneric(u64, seg.inner.fileoff, @alignOf(u64));
const rebase_size = try bind.rebaseInfoSize(rebase_pointers.items);
dyld_info.rebase_off = @intCast(u32, rebase_off);
dyld_info.rebase_size = @intCast(u32, rebase_size);
log.debug("writing rebase info from 0x{x} to 0x{x}", .{
dyld_info.rebase_off,
dyld_info.rebase_off + dyld_info.rebase_size,
});
const bind_off = mem.alignForwardGeneric(u64, dyld_info.rebase_off + dyld_info.rebase_size, @alignOf(u64));
const bind_size = try bind.bindInfoSize(bind_pointers.items);
dyld_info.bind_off = @intCast(u32, bind_off);
dyld_info.bind_size = @intCast(u32, bind_size);
log.debug("writing bind info from 0x{x} to 0x{x}", .{
dyld_info.bind_off,
dyld_info.bind_off + dyld_info.bind_size,
});
const lazy_bind_off = mem.alignForwardGeneric(u64, dyld_info.bind_off + dyld_info.bind_size, @alignOf(u64));
const lazy_bind_size = try bind.lazyBindInfoSize(lazy_bind_pointers.items);
dyld_info.lazy_bind_off = @intCast(u32, lazy_bind_off);
dyld_info.lazy_bind_size = @intCast(u32, lazy_bind_size);
log.debug("writing lazy bind info from 0x{x} to 0x{x}", .{
dyld_info.lazy_bind_off,
dyld_info.lazy_bind_off + dyld_info.lazy_bind_size,
});
const export_off = mem.alignForwardGeneric(u64, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size, @alignOf(u64));
const export_size = trie.size;
dyld_info.export_off = @intCast(u32, export_off);
dyld_info.export_size = @intCast(u32, export_size);
log.debug("writing export trie from 0x{x} to 0x{x}", .{
dyld_info.export_off,
dyld_info.export_off + dyld_info.export_size,
});
dyld_info.rebase_off = @intCast(u32, seg.inner.fileoff);
dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, rebase_size, @alignOf(u64)));
seg.inner.filesize += dyld_info.rebase_size;
seg.inner.filesize = dyld_info.export_off + dyld_info.export_size - seg.inner.fileoff;
dyld_info.bind_off = dyld_info.rebase_off + dyld_info.rebase_size;
dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, bind_size, @alignOf(u64)));
seg.inner.filesize += dyld_info.bind_size;
dyld_info.lazy_bind_off = dyld_info.bind_off + dyld_info.bind_size;
dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, lazy_bind_size, @alignOf(u64)));
seg.inner.filesize += dyld_info.lazy_bind_size;
dyld_info.export_off = dyld_info.lazy_bind_off + dyld_info.lazy_bind_size;
dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, export_size, @alignOf(u64)));
seg.inner.filesize += dyld_info.export_size;
const needed_size = dyld_info.rebase_size + dyld_info.bind_size + dyld_info.lazy_bind_size + dyld_info.export_size;
const needed_size = dyld_info.export_off + dyld_info.export_size - dyld_info.rebase_off;
var buffer = try self.base.allocator.alloc(u8, needed_size);
defer self.base.allocator.free(buffer);
mem.set(u8, buffer, 0);
@ -5740,14 +5762,15 @@ fn writeDyldInfoData(self: *MachO) !void {
var stream = std.io.fixedBufferStream(buffer);
const writer = stream.writer();
const base_off = dyld_info.rebase_off;
try bind.writeRebaseInfo(rebase_pointers.items, writer);
try stream.seekBy(@intCast(i64, dyld_info.rebase_size) - @intCast(i64, rebase_size));
try stream.seekTo(dyld_info.bind_off - base_off);
try bind.writeBindInfo(bind_pointers.items, writer);
try stream.seekBy(@intCast(i64, dyld_info.bind_size) - @intCast(i64, bind_size));
try stream.seekTo(dyld_info.lazy_bind_off - base_off);
try bind.writeLazyBindInfo(lazy_bind_pointers.items, writer);
try stream.seekBy(@intCast(i64, dyld_info.lazy_bind_size) - @intCast(i64, lazy_bind_size));
try stream.seekTo(dyld_info.export_off - base_off);
_ = try trie.write(writer);
@ -5758,7 +5781,7 @@ fn writeDyldInfoData(self: *MachO) !void {
try self.base.file.?.pwriteAll(buffer, dyld_info.rebase_off);
try self.populateLazyBindOffsetsInStubHelper(
buffer[dyld_info.rebase_size + dyld_info.bind_size ..][0..dyld_info.lazy_bind_size],
buffer[dyld_info.lazy_bind_off - base_off ..][0..dyld_info.lazy_bind_size],
);
self.load_commands_dirty = true;
}
@ -5928,32 +5951,31 @@ fn writeFunctionStarts(self: *MachO) !void {
} else break;
}
const max_size = @intCast(usize, offsets.items.len * @sizeOf(u64));
var buffer = try self.base.allocator.alloc(u8, max_size);
defer self.base.allocator.free(buffer);
mem.set(u8, buffer, 0);
var buffer = std.ArrayList(u8).init(self.base.allocator);
defer buffer.deinit();
var stream = std.io.fixedBufferStream(buffer);
const writer = stream.writer();
const max_size = @intCast(usize, offsets.items.len * @sizeOf(u64));
try buffer.ensureTotalCapacity(max_size);
for (offsets.items) |offset| {
try std.leb.writeULEB128(writer, offset);
try std.leb.writeULEB128(buffer.writer(), offset);
}
const needed_size = @intCast(u32, mem.alignForwardGeneric(u64, stream.pos, @sizeOf(u64)));
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
const fn_cmd = &self.load_commands.items[self.function_starts_cmd_index.?].linkedit_data;
fn_cmd.dataoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
fn_cmd.datasize = needed_size;
seg.inner.filesize += needed_size;
const dataoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64));
const datasize = buffer.items.len;
fn_cmd.dataoff = @intCast(u32, dataoff);
fn_cmd.datasize = @intCast(u32, datasize);
seg.inner.filesize = fn_cmd.dataoff + fn_cmd.datasize - seg.inner.fileoff;
log.debug("writing function starts info from 0x{x} to 0x{x}", .{
fn_cmd.dataoff,
fn_cmd.dataoff + fn_cmd.datasize,
});
try self.base.file.?.pwriteAll(buffer[0..needed_size], fn_cmd.dataoff);
try self.base.file.?.pwriteAll(buffer.items, fn_cmd.dataoff);
self.load_commands_dirty = true;
}
@ -6001,11 +6023,12 @@ fn writeDices(self: *MachO) !void {
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].linkedit_data;
const needed_size = @intCast(u32, buf.items.len);
dice_cmd.dataoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
dice_cmd.datasize = needed_size;
seg.inner.filesize += needed_size;
const dataoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64));
const datasize = buf.items.len;
dice_cmd.dataoff = @intCast(u32, dataoff);
dice_cmd.datasize = @intCast(u32, datasize);
seg.inner.filesize = dice_cmd.dataoff + dice_cmd.datasize - seg.inner.fileoff;
log.debug("writing data-in-code from 0x{x} to 0x{x}", .{
dice_cmd.dataoff,
@ -6022,7 +6045,8 @@ fn writeSymbolTable(self: *MachO) !void {
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab;
symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
const symoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(macho.nlist_64));
symtab.symoff = @intCast(u32, symoff);
var locals = std.ArrayList(macho.nlist_64).init(self.base.allocator);
defer locals.deinit();
@ -6122,7 +6146,7 @@ fn writeSymbolTable(self: *MachO) !void {
try self.base.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off);
symtab.nsyms = @intCast(u32, nlocals + nexports + nundefs);
seg.inner.filesize += locals_size + exports_size + undefs_size;
seg.inner.filesize = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64) - seg.inner.fileoff;
// Update dynamic symbol table.
const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].dysymtab;
@ -6142,22 +6166,21 @@ fn writeSymbolTable(self: *MachO) !void {
const nstubs = @intCast(u32, self.stubs_table.keys().len);
const ngot_entries = @intCast(u32, self.got_entries_table.keys().len);
dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
const indirectsymoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64));
dysymtab.indirectsymoff = @intCast(u32, indirectsymoff);
dysymtab.nindirectsyms = nstubs * 2 + ngot_entries;
const needed_size = dysymtab.nindirectsyms * @sizeOf(u32);
seg.inner.filesize += needed_size;
seg.inner.filesize = dysymtab.indirectsymoff + dysymtab.nindirectsyms * @sizeOf(u32) - seg.inner.fileoff;
log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{
dysymtab.indirectsymoff,
dysymtab.indirectsymoff + needed_size,
dysymtab.indirectsymoff + dysymtab.nindirectsyms * @sizeOf(u32),
});
var buf = try self.base.allocator.alloc(u8, needed_size);
defer self.base.allocator.free(buf);
var stream = std.io.fixedBufferStream(buf);
var writer = stream.writer();
var buf = std.ArrayList(u8).init(self.base.allocator);
defer buf.deinit();
try buf.ensureTotalCapacity(dysymtab.nindirectsyms * @sizeOf(u32));
const writer = buf.writer();
stubs.reserved1 = 0;
for (self.stubs_table.keys()) |key| {
@ -6191,7 +6214,9 @@ fn writeSymbolTable(self: *MachO) !void {
}
}
try self.base.file.?.pwriteAll(buf, dysymtab.indirectsymoff);
assert(buf.items.len == dysymtab.nindirectsyms * @sizeOf(u32));
try self.base.file.?.pwriteAll(buf.items, dysymtab.indirectsymoff);
self.load_commands_dirty = true;
}
@ -6201,18 +6226,16 @@ fn writeStringTable(self: *MachO) !void {
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab;
symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize);
symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64)));
seg.inner.filesize += symtab.strsize;
const stroff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64));
const strsize = self.strtab.items.len;
symtab.stroff = @intCast(u32, stroff);
symtab.strsize = @intCast(u32, strsize);
seg.inner.filesize = symtab.stroff + symtab.strsize - seg.inner.fileoff;
log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize });
try self.base.file.?.pwriteAll(self.strtab.items, symtab.stroff);
if (symtab.strsize > self.strtab.items.len) {
// This is potentially the last section, so we need to pad it out.
try self.base.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1);
}
self.load_commands_dirty = true;
}
@ -6236,25 +6259,22 @@ fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
const tracy = trace(@src());
defer tracy.end();
const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data;
const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment;
const cs_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data;
// Code signature data has to be 16-bytes aligned for Apple tools to recognize the file
// https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
const fileoff = mem.alignForwardGeneric(u64, linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize, 16);
const padding = fileoff - (linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize);
const needed_size = code_sig.estimateSize(fileoff);
code_sig_cmd.dataoff = @intCast(u32, fileoff);
code_sig_cmd.datasize = needed_size;
const dataoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, 16);
const datasize = code_sig.estimateSize(dataoff);
cs_cmd.dataoff = @intCast(u32, dataoff);
cs_cmd.datasize = @intCast(u32, code_sig.estimateSize(dataoff));
// Advance size of __LINKEDIT segment
linkedit_segment.inner.filesize += needed_size + padding;
if (linkedit_segment.inner.vmsize < linkedit_segment.inner.filesize) {
linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, linkedit_segment.inner.filesize, self.page_size);
}
log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size });
seg.inner.filesize = cs_cmd.dataoff + cs_cmd.datasize - seg.inner.fileoff;
seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size);
log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ dataoff, dataoff + datasize });
// Pad out the space. We need to do this to calculate valid hashes for everything in the file
// except for code signature data.
try self.base.file.?.pwriteAll(&[_]u8{0}, fileoff + needed_size - 1);
try self.base.file.?.pwriteAll(&[_]u8{0}, dataoff + datasize - 1);
self.load_commands_dirty = true;
}

64
test/link.zig Normal file
View File

@ -0,0 +1,64 @@
const std = @import("std");
const builtin = @import("builtin");
const tests = @import("tests.zig");
pub fn addCases(cases: *tests.StandaloneContext) void {
cases.addBuildFile("test/link/bss/build.zig", .{
.build_modes = false, // we only guarantee zerofill for undefined in Debug
});
cases.addBuildFile("test/link/common_symbols/build.zig", .{
.build_modes = true,
});
cases.addBuildFile("test/link/common_symbols_alignment/build.zig", .{
.build_modes = true,
});
cases.addBuildFile("test/link/interdependent_static_c_libs/build.zig", .{
.build_modes = true,
});
cases.addBuildFile("test/link/static_lib_as_system_lib/build.zig", .{
.build_modes = true,
});
cases.addBuildFile("test/link/tls/build.zig", .{
.build_modes = true,
});
if (builtin.os.tag == .macos) {
cases.addBuildFile("test/link/macho/entry/build.zig", .{
.build_modes = true,
});
cases.addBuildFile("test/link/macho/pagezero/build.zig", .{
.build_modes = false,
});
cases.addBuildFile("test/link/macho/dylib/build.zig", .{
.build_modes = true,
});
cases.addBuildFile("test/link/macho/frameworks/build.zig", .{
.build_modes = true,
.requires_macos_sdk = true,
});
// Try to build and run an Objective-C executable.
cases.addBuildFile("test/link/macho/objc/build.zig", .{
.build_modes = true,
.requires_macos_sdk = true,
});
// Try to build and run an Objective-C++ executable.
cases.addBuildFile("test/link/macho/objcpp/build.zig", .{
.build_modes = true,
.requires_macos_sdk = true,
});
cases.addBuildFile("test/link/macho/stack_size/build.zig", .{
.build_modes = true,
});
}
}

14
test/link/bss/build.zig Normal file
View File

@ -0,0 +1,14 @@
const Builder = @import("std").build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const test_step = b.step("test", "Test");
const exe = b.addExecutable("bss", "main.zig");
b.default_step.dependOn(&exe.step);
exe.setBuildMode(mode);
const run = exe.run();
run.expectStdOutEqual("0, 1, 0\n");
test_step.dependOn(&run.step);
}

13
test/link/bss/main.zig Normal file
View File

@ -0,0 +1,13 @@
const std = @import("std");
// Stress test zerofill layout
var buffer: [0x1000000]u64 = undefined;
pub fn main() anyerror!void {
buffer[0x10] = 1;
try std.io.getStdOut().writer().print("{d}, {d}, {d}\n", .{
buffer[0],
buffer[0x10],
buffer[0x1000000 - 1],
});
}

View File

@ -0,0 +1,7 @@
#include <stdio.h>
char world[] = "world";
char* hello() {
return "Hello";
}

View File

@ -0,0 +1,49 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const test_step = b.step("test", "Test");
test_step.dependOn(b.getInstallStep());
const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0));
dylib.setBuildMode(mode);
dylib.addCSourceFile("a.c", &.{});
dylib.linkLibC();
dylib.install();
const check_dylib = dylib.checkObject(.macho);
check_dylib.checkStart("cmd ID_DYLIB");
check_dylib.checkNext("name @rpath/liba.dylib");
check_dylib.checkNext("timestamp 2");
check_dylib.checkNext("current version 10000");
check_dylib.checkNext("compatibility version 10000");
test_step.dependOn(&check_dylib.step);
const exe = b.addExecutable("main", null);
exe.setBuildMode(mode);
exe.addCSourceFile("main.c", &.{});
exe.linkSystemLibrary("a");
exe.linkLibC();
exe.addLibraryPath(b.pathFromRoot("zig-out/lib/"));
exe.addRPath(b.pathFromRoot("zig-out/lib"));
const check_exe = exe.checkObject(.macho);
check_exe.checkStart("cmd LOAD_DYLIB");
check_exe.checkNext("name @rpath/liba.dylib");
check_exe.checkNext("timestamp 2");
check_exe.checkNext("current version 10000");
check_exe.checkNext("compatibility version 10000");
check_exe.checkStart("cmd RPATH");
check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable);
test_step.dependOn(&check_exe.step);
const run = exe.run();
run.cwd = b.pathFromRoot(".");
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
}

View File

@ -0,0 +1,9 @@
#include <stdio.h>
char* hello();
extern char world[];
int main() {
printf("%s %s", hello(), world);
return 0;
}

View File

@ -0,0 +1,34 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const test_step = b.step("test", "Test");
test_step.dependOn(b.getInstallStep());
const exe = b.addExecutable("main", null);
exe.setBuildMode(mode);
exe.addCSourceFile("main.c", &.{});
exe.linkLibC();
exe.entry_symbol_name = "_non_main";
const check_exe = exe.checkObject(.macho);
check_exe.checkStart("segname __TEXT");
check_exe.checkNext("vmaddr {vmaddr}");
check_exe.checkStart("cmd MAIN");
check_exe.checkNext("entryoff {entryoff}");
check_exe.checkInSymtab();
check_exe.checkNext("_non_main {n_value}");
check_exe.checkComputeEq("vmaddr entryoff +", "n_value");
test_step.dependOn(&check_exe.step);
const run = exe.run();
run.expectStdOutEqual("42");
test_step.dependOn(&run.step);
}

View File

@ -0,0 +1,6 @@
#include <stdio.h>
int non_main() {
printf("%d", 42);
return 0;
}

View File

@ -0,0 +1,32 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const test_step = b.step("test", "Test the program");
const exe = b.addExecutable("test", null);
b.default_step.dependOn(&exe.step);
exe.addCSourceFile("main.c", &[0][]const u8{});
exe.setBuildMode(mode);
exe.linkLibC();
exe.linkFramework("Cocoa");
const check = exe.checkObject(.macho);
check.checkStart("cmd LOAD_DYLIB");
check.checkNext("name {*}Cocoa");
switch (mode) {
.Debug, .ReleaseSafe => {
check.checkStart("cmd LOAD_DYLIB");
check.checkNext("name {*}libobjc{*}.dylib");
},
else => {},
}
test_step.dependOn(&check.step);
const run_cmd = exe.run();
test_step.dependOn(&run_cmd.step);
}

View File

@ -1,19 +1,8 @@
const std = @import("std");
const Builder = std.build.Builder;
const CrossTarget = std.zig.CrossTarget;
fn isRunnableTarget(t: CrossTarget) bool {
// TODO I think we might be able to run this on Linux via Darling.
// Add a check for that here, and return true if Darling is available.
if (t.isNative() and t.getOsTag() == .macos)
return true
else
return false;
}
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const target = b.standardTargetOptions(.{});
const test_step = b.step("test", "Test the program");
@ -23,14 +12,11 @@ pub fn build(b: *Builder) void {
exe.addCSourceFile("Foo.m", &[0][]const u8{});
exe.addCSourceFile("test.m", &[0][]const u8{});
exe.setBuildMode(mode);
exe.setTarget(target);
exe.linkLibC();
// TODO when we figure out how to ship framework stubs for cross-compilation,
// populate paths to the sysroot here.
exe.linkFramework("Foundation");
if (isRunnableTarget(target)) {
const run_cmd = exe.run();
test_step.dependOn(&run_cmd.step);
}
const run_cmd = exe.run();
test_step.dependOn(&run_cmd.step);
}

View File

@ -1,19 +1,8 @@
const std = @import("std");
const Builder = std.build.Builder;
const CrossTarget = std.zig.CrossTarget;
fn isRunnableTarget(t: CrossTarget) bool {
// TODO I think we might be able to run this on Linux via Darling.
// Add a check for that here, and return true if Darling is available.
if (t.isNative() and t.getOsTag() == .macos)
return true
else
return false;
}
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const target = b.standardTargetOptions(.{});
const test_step = b.step("test", "Test the program");
@ -23,14 +12,13 @@ pub fn build(b: *Builder) void {
exe.addCSourceFile("Foo.mm", &[0][]const u8{});
exe.addCSourceFile("test.mm", &[0][]const u8{});
exe.setBuildMode(mode);
exe.setTarget(target);
exe.linkLibCpp();
// TODO when we figure out how to ship framework stubs for cross-compilation,
// populate paths to the sysroot here.
exe.linkFramework("Foundation");
if (isRunnableTarget(target)) {
const run_cmd = exe.run();
test_step.dependOn(&run_cmd.step);
}
const run_cmd = exe.run();
run_cmd.expectStdOutEqual("Hello from C++ and Zig");
test_step.dependOn(&run_cmd.step);
}

View File

@ -0,0 +1,43 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const test_step = b.step("test", "Test");
test_step.dependOn(b.getInstallStep());
{
const exe = b.addExecutable("pagezero", null);
exe.setBuildMode(mode);
exe.addCSourceFile("main.c", &.{});
exe.linkLibC();
exe.pagezero_size = 0x4000;
const check = exe.checkObject(.macho);
check.checkStart("LC 0");
check.checkNext("segname __PAGEZERO");
check.checkNext("vmaddr 0");
check.checkNext("vmsize 4000");
check.checkStart("segname __TEXT");
check.checkNext("vmaddr 4000");
test_step.dependOn(&check.step);
}
{
const exe = b.addExecutable("no_pagezero", null);
exe.setBuildMode(mode);
exe.addCSourceFile("main.c", &.{});
exe.linkLibC();
exe.pagezero_size = 0;
const check = exe.checkObject(.macho);
check.checkStart("LC 0");
check.checkNext("segname __TEXT");
check.checkNext("vmaddr 0");
test_step.dependOn(&check.step);
}
}

View File

@ -0,0 +1,3 @@
int main(int argc, char* argv[]) {
return 0;
}

View File

@ -0,0 +1,24 @@
const std = @import("std");
const Builder = std.build.Builder;
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const test_step = b.step("test", "Test");
test_step.dependOn(b.getInstallStep());
const exe = b.addExecutable("main", null);
exe.setBuildMode(mode);
exe.addCSourceFile("main.c", &.{});
exe.linkLibC();
exe.stack_size = 0x100000000;
const check_exe = exe.checkObject(.macho);
check_exe.checkStart("cmd MAIN");
check_exe.checkNext("stacksize 100000000");
test_step.dependOn(&check_exe.step);
const run = exe.run();
test_step.dependOn(&run.step);
}

View File

@ -0,0 +1,3 @@
int main(int argc, char* argv[]) {
return 0;
}

5
test/link/tls/a.c Normal file
View File

@ -0,0 +1,5 @@
_Thread_local int a;
int getA() {
return a;
}

View File

@ -6,10 +6,12 @@ pub fn build(b: *Builder) void {
const lib = b.addSharedLibrary("a", null, b.version(1, 0, 0));
lib.setBuildMode(mode);
lib.addCSourceFile("a.c", &.{});
lib.linkLibC();
const test_exe = b.addTest("main.zig");
test_exe.setBuildMode(mode);
test_exe.linkLibrary(lib);
test_exe.linkLibC();
const test_step = b.step("test", "Test it");
test_step.dependOn(&test_exe.step);

15
test/link/tls/main.zig Normal file
View File

@ -0,0 +1,15 @@
const std = @import("std");
extern threadlocal var a: i32;
extern fn getA() i32;
fn getA2() i32 {
return a;
}
test {
a = 2;
try std.testing.expect(getA() == 2);
try std.testing.expect(2 == getA2());
try std.testing.expect(getA() == getA2());
}

View File

@ -13,31 +13,12 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
cases.addBuildFile("test/standalone/main_pkg_path/build.zig", .{});
cases.addBuildFile("test/standalone/shared_library/build.zig", .{});
cases.addBuildFile("test/standalone/mix_o_files/build.zig", .{});
if (builtin.os.tag == .macos) {
// Zig's macOS linker does not yet support LTO for LLVM IR files:
// https://github.com/ziglang/zig/issues/8680
cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{
.build_modes = false,
.cross_targets = true,
});
} else {
cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{
.build_modes = true,
.cross_targets = true,
});
}
cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{
.build_modes = true,
.cross_targets = true,
});
cases.addBuildFile("test/standalone/global_linkage/build.zig", .{});
cases.addBuildFile("test/standalone/static_c_lib/build.zig", .{});
cases.addBuildFile("test/standalone/link_interdependent_static_c_libs/build.zig", .{});
cases.addBuildFile("test/standalone/link_static_lib_as_system_lib/build.zig", .{});
cases.addBuildFile("test/standalone/link_common_symbols/build.zig", .{});
cases.addBuildFile("test/standalone/link_frameworks/build.zig", .{
.requires_macos_sdk = true,
});
cases.addBuildFile("test/standalone/link_common_symbols_alignment/build.zig", .{});
if (builtin.os.tag == .macos) {
cases.addBuildFile("test/standalone/link_import_tls_dylib/build.zig", .{});
}
cases.addBuildFile("test/standalone/issue_339/build.zig", .{});
cases.addBuildFile("test/standalone/issue_8550/build.zig", .{});
cases.addBuildFile("test/standalone/issue_794/build.zig", .{});
@ -69,16 +50,6 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
if (builtin.os.tag == .linux) {
cases.addBuildFile("test/standalone/pie/build.zig", .{});
}
// Try to build and run an Objective-C executable.
cases.addBuildFile("test/standalone/objc/build.zig", .{
.build_modes = true,
.requires_macos_sdk = true,
});
// Try to build and run an Objective-C++ executable.
cases.addBuildFile("test/standalone/objcpp/build.zig", .{
.build_modes = true,
.requires_macos_sdk = true,
});
// Ensure the development tools are buildable.
cases.add("tools/gen_spirv_spec.zig");

View File

@ -1,34 +0,0 @@
const std = @import("std");
const Builder = std.build.Builder;
const CrossTarget = std.zig.CrossTarget;
fn isRunnableTarget(t: CrossTarget) bool {
// TODO I think we might be able to run this on Linux via Darling.
// Add a check for that here, and return true if Darling is available.
if (t.isNative() and t.getOsTag() == .macos)
return true
else
return false;
}
pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const target = b.standardTargetOptions(.{});
const test_step = b.step("test", "Test the program");
const exe = b.addExecutable("test", null);
b.default_step.dependOn(&exe.step);
exe.addCSourceFile("main.c", &[0][]const u8{});
exe.setBuildMode(mode);
exe.setTarget(target);
exe.linkLibC();
// TODO when we figure out how to ship framework stubs for cross-compilation,
// populate paths to the sysroot here.
exe.linkFramework("Cocoa");
if (isRunnableTarget(target)) {
const run_cmd = exe.run();
test_step.dependOn(&run_cmd.step);
}
}

View File

@ -1 +0,0 @@
_Thread_local int a;

View File

@ -1,7 +0,0 @@
const std = @import("std");
extern threadlocal var a: i32;
test {
try std.testing.expect(a == 0);
}

View File

@ -21,6 +21,7 @@ const assemble_and_link = @import("assemble_and_link.zig");
const translate_c = @import("translate_c.zig");
const run_translated_c = @import("run_translated_c.zig");
const gen_h = @import("gen_h.zig");
const link = @import("link.zig");
// Implementations
pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext;
@ -479,6 +480,27 @@ pub fn addStandaloneTests(
return cases.step;
}
pub fn addLinkTests(
b: *build.Builder,
test_filter: ?[]const u8,
modes: []const Mode,
enable_macos_sdk: bool,
) *build.Step {
const cases = b.allocator.create(StandaloneContext) catch unreachable;
cases.* = StandaloneContext{
.b = b,
.step = b.step("test-link", "Run the linker tests"),
.test_index = 0,
.test_filter = test_filter,
.modes = modes,
.skip_non_native = true,
.enable_macos_sdk = enable_macos_sdk,
.target = .{},
};
link.addCases(cases);
return cases.step;
}
pub fn addCliTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step {
_ = test_filter;
_ = modes;
@ -973,7 +995,8 @@ pub const StandaloneContext = struct {
}
if (features.cross_targets and !self.target.isNative()) {
const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{self.target.zigTriple(b.allocator) catch unreachable}) catch unreachable;
const target_triple = self.target.zigTriple(b.allocator) catch unreachable;
const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable;
zig_args.append(target_arg) catch unreachable;
}