self-hosted: first passing test

* introduce std.atomic.Int
 * add src-self-hosted/test.zig which is tested by the main test suite
   - it fully utilizes the multithreaded async/await event loop so the
     tests should Go Fast
 * `stage2/bin/zig build-obj test.zig` is able to spit out an error if 2 exported
   functions collide
 * ability for `zig test` to accept `--object` and `--assembly`
   arguments
 * std.build: TestStep supports addLibPath and addObjectFile
master
Andrew Kelley 2018-07-10 20:18:43 -04:00
parent 8fba0a6ae8
commit 574e31f0a0
11 changed files with 432 additions and 107 deletions

View File

@ -431,6 +431,7 @@ set(ZIG_CPP_SOURCES
set(ZIG_STD_FILES
"array_list.zig"
"atomic/index.zig"
"atomic/int.zig"
"atomic/queue_mpmc.zig"
"atomic/queue_mpsc.zig"
"atomic/stack.zig"

152
build.zig
View File

@ -35,70 +35,27 @@ pub fn build(b: *Builder) !void {
"BUILD_INFO",
});
var index: usize = 0;
const cmake_binary_dir = nextValue(&index, build_info);
const cxx_compiler = nextValue(&index, build_info);
const llvm_config_exe = nextValue(&index, build_info);
const lld_include_dir = nextValue(&index, build_info);
const lld_libraries = nextValue(&index, build_info);
const std_files = nextValue(&index, build_info);
const c_header_files = nextValue(&index, build_info);
const dia_guids_lib = nextValue(&index, build_info);
var ctx = Context{
.cmake_binary_dir = nextValue(&index, build_info),
.cxx_compiler = nextValue(&index, build_info),
.llvm_config_exe = nextValue(&index, build_info),
.lld_include_dir = nextValue(&index, build_info),
.lld_libraries = nextValue(&index, build_info),
.std_files = nextValue(&index, build_info),
.c_header_files = nextValue(&index, build_info),
.dia_guids_lib = nextValue(&index, build_info),
.llvm = undefined,
};
ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);
const llvm = findLLVM(b, llvm_config_exe) catch unreachable;
var test_stage2 = b.addTest("src-self-hosted/test.zig");
test_stage2.setBuildMode(builtin.Mode.Debug);
var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
exe.setBuildMode(mode);
// This is for finding /lib/libz.a on alpine linux.
// TODO turn this into -Dextra-lib-path=/lib option
exe.addLibPath("/lib");
exe.addIncludeDir("src");
exe.addIncludeDir(cmake_binary_dir);
addCppLib(b, exe, cmake_binary_dir, "zig_cpp");
if (lld_include_dir.len != 0) {
exe.addIncludeDir(lld_include_dir);
var it = mem.split(lld_libraries, ";");
while (it.next()) |lib| {
exe.addObjectFile(lib);
}
} else {
addCppLib(b, exe, cmake_binary_dir, "embedded_lld_wasm");
addCppLib(b, exe, cmake_binary_dir, "embedded_lld_elf");
addCppLib(b, exe, cmake_binary_dir, "embedded_lld_coff");
addCppLib(b, exe, cmake_binary_dir, "embedded_lld_lib");
}
dependOnLib(exe, llvm);
if (exe.target.getOs() == builtin.Os.linux) {
const libstdcxx_path_padded = try b.exec([][]const u8{
cxx_compiler,
"-print-file-name=libstdc++.a",
});
const libstdcxx_path = mem.split(libstdcxx_path_padded, "\r\n").next().?;
if (mem.eql(u8, libstdcxx_path, "libstdc++.a")) {
warn(
\\Unable to determine path to libstdc++.a
\\On Fedora, install libstdc++-static and try again.
\\
);
return error.RequiredLibraryNotFound;
}
exe.addObjectFile(libstdcxx_path);
exe.linkSystemLibrary("pthread");
} else if (exe.target.isDarwin()) {
exe.linkSystemLibrary("c++");
}
if (dia_guids_lib.len != 0) {
exe.addObjectFile(dia_guids_lib);
}
if (exe.target.getOs() != builtin.Os.windows) {
exe.linkSystemLibrary("xml2");
}
exe.linkSystemLibrary("c");
try configureStage2(b, test_stage2, ctx);
try configureStage2(b, exe, ctx);
b.default_step.dependOn(&exe.step);
@ -110,12 +67,16 @@ pub fn build(b: *Builder) !void {
exe.setVerboseLink(verbose_link_exe);
b.installArtifact(exe);
installStdLib(b, std_files);
installCHeaders(b, c_header_files);
installStdLib(b, ctx.std_files);
installCHeaders(b, ctx.c_header_files);
const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
const with_lldb = b.option(bool, "with-lldb", "Run tests in LLDB to get a backtrace if one fails") orelse false;
const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests");
test_stage2_step.dependOn(&test_stage2.step);
test_step.dependOn(test_stage2_step);
test_step.dependOn(docs_step);
test_step.dependOn(tests.addPkgTests(b, test_filter, "test/behavior.zig", "behavior", "Run the behavior tests", with_lldb));
@ -133,7 +94,7 @@ pub fn build(b: *Builder) !void {
test_step.dependOn(tests.addGenHTests(b, test_filter));
}
fn dependOnLib(lib_exe_obj: *std.build.LibExeObjStep, dep: *const LibraryDep) void {
fn dependOnLib(lib_exe_obj: var, dep: *const LibraryDep) void {
for (dep.libdirs.toSliceConst()) |lib_dir| {
lib_exe_obj.addLibPath(lib_dir);
}
@ -148,7 +109,7 @@ fn dependOnLib(lib_exe_obj: *std.build.LibExeObjStep, dep: *const LibraryDep) vo
}
}
fn addCppLib(b: *Builder, lib_exe_obj: *std.build.LibExeObjStep, cmake_binary_dir: []const u8, lib_name: []const u8) void {
fn addCppLib(b: *Builder, lib_exe_obj: var, cmake_binary_dir: []const u8, lib_name: []const u8) void {
const lib_prefix = if (lib_exe_obj.target.isWindows()) "" else "lib";
lib_exe_obj.addObjectFile(os.path.join(b.allocator, cmake_binary_dir, "zig_cpp", b.fmt("{}{}{}", lib_prefix, lib_name, lib_exe_obj.target.libFileExt())) catch unreachable);
}
@ -254,3 +215,68 @@ fn nextValue(index: *usize, build_info: []const u8) []const u8 {
}
}
}
fn configureStage2(b: *Builder, exe: var, ctx: Context) !void {
// This is for finding /lib/libz.a on alpine linux.
// TODO turn this into -Dextra-lib-path=/lib option
exe.addLibPath("/lib");
exe.addIncludeDir("src");
exe.addIncludeDir(ctx.cmake_binary_dir);
addCppLib(b, exe, ctx.cmake_binary_dir, "zig_cpp");
if (ctx.lld_include_dir.len != 0) {
exe.addIncludeDir(ctx.lld_include_dir);
var it = mem.split(ctx.lld_libraries, ";");
while (it.next()) |lib| {
exe.addObjectFile(lib);
}
} else {
addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_wasm");
addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_elf");
addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_coff");
addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_lib");
}
dependOnLib(exe, ctx.llvm);
if (exe.target.getOs() == builtin.Os.linux) {
const libstdcxx_path_padded = try b.exec([][]const u8{
ctx.cxx_compiler,
"-print-file-name=libstdc++.a",
});
const libstdcxx_path = mem.split(libstdcxx_path_padded, "\r\n").next().?;
if (mem.eql(u8, libstdcxx_path, "libstdc++.a")) {
warn(
\\Unable to determine path to libstdc++.a
\\On Fedora, install libstdc++-static and try again.
\\
);
return error.RequiredLibraryNotFound;
}
exe.addObjectFile(libstdcxx_path);
exe.linkSystemLibrary("pthread");
} else if (exe.target.isDarwin()) {
exe.linkSystemLibrary("c++");
}
if (ctx.dia_guids_lib.len != 0) {
exe.addObjectFile(ctx.dia_guids_lib);
}
if (exe.target.getOs() != builtin.Os.windows) {
exe.linkSystemLibrary("xml2");
}
exe.linkSystemLibrary("c");
}
const Context = struct {
cmake_binary_dir: []const u8,
cxx_compiler: []const u8,
llvm_config_exe: []const u8,
lld_include_dir: []const u8,
lld_libraries: []const u8,
std_files: []const u8,
c_header_files: []const u8,
dia_guids_lib: []const u8,
llvm: LibraryDep,
};

View File

@ -11,11 +11,15 @@ pub const Color = enum {
On,
};
pub const Span = struct {
first: ast.TokenIndex,
last: ast.TokenIndex,
};
pub const Msg = struct {
path: []const u8,
text: []u8,
first_token: TokenIndex,
last_token: TokenIndex,
span: Span,
tree: *ast.Tree,
};
@ -39,8 +43,10 @@ pub fn createFromParseError(
.tree = tree,
.path = path,
.text = text_buf.toOwnedSlice(),
.first_token = loc_token,
.last_token = loc_token,
.span = Span{
.first = loc_token,
.last = loc_token,
},
});
errdefer allocator.destroy(msg);
@ -48,8 +54,8 @@ pub fn createFromParseError(
}
pub fn printToStream(stream: var, msg: *const Msg, color_on: bool) !void {
const first_token = msg.tree.tokens.at(msg.first_token);
const last_token = msg.tree.tokens.at(msg.last_token);
const first_token = msg.tree.tokens.at(msg.span.first);
const last_token = msg.tree.tokens.at(msg.span.last);
const start_loc = msg.tree.tokenLocationPtr(0, first_token);
const end_loc = msg.tree.tokenLocationPtr(first_token.end, last_token);
if (!color_on) {

View File

@ -53,3 +53,8 @@ pub fn resolveZigLibDir(allocator: *mem.Allocator) ![]u8 {
return error.ZigLibDirNotFound;
};
}
/// Caller must free result
pub fn resolveZigCacheDir(allocator: *mem.Allocator) ![]u8 {
return std.mem.dupe(allocator, u8, "zig-cache");
}

View File

@ -481,29 +481,29 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Mo
module.link_out_file = flags.single("out-file");
try module.build();
const process_build_events_handle = try async<loop.allocator> processBuildEvents(module, true);
const process_build_events_handle = try async<loop.allocator> processBuildEvents(module, color);
defer cancel process_build_events_handle;
loop.run();
}
async fn processBuildEvents(module: *Module, watch: bool) void {
while (watch) {
// TODO directly awaiting async should guarantee memory allocation elision
const build_event = await (async module.events.get() catch unreachable);
async fn processBuildEvents(module: *Module, color: errmsg.Color) void {
// TODO directly awaiting async should guarantee memory allocation elision
const build_event = await (async module.events.get() catch unreachable);
switch (build_event) {
Module.Event.Ok => {
std.debug.warn("Build succeeded\n");
return;
},
Module.Event.Error => |err| {
std.debug.warn("build failed: {}\n", @errorName(err));
@panic("TODO error return trace");
},
Module.Event.Fail => |errs| {
@panic("TODO print compile error messages");
},
}
switch (build_event) {
Module.Event.Ok => {
std.debug.warn("Build succeeded\n");
return;
},
Module.Event.Error => |err| {
std.debug.warn("build failed: {}\n", @errorName(err));
@panic("TODO error return trace");
},
Module.Event.Fail => |msgs| {
for (msgs) |msg| {
errmsg.printToFile(&stderr_file, msg, color) catch os.exit(1);
}
},
}
}

View File

@ -89,12 +89,9 @@ pub const Module = struct {
/// the build is complete.
build_group: event.Group(BuildError!void),
const BuildErrorsList = std.SegmentedList(BuildErrorDesc, 1);
compile_errors: event.Locked(CompileErrList),
pub const BuildErrorDesc = struct {
code: BuildError,
text: []const u8,
};
const CompileErrList = std.ArrayList(*errmsg.Msg);
// TODO handle some of these earlier and report them in a way other than error codes
pub const BuildError = error{
@ -131,11 +128,12 @@ pub const Module = struct {
NoStdHandles,
Overflow,
NotSupported,
BufferTooSmall,
};
pub const Event = union(enum) {
Ok,
Fail: []errmsg.Msg,
Fail: []*errmsg.Msg,
Error: BuildError,
};
@ -249,6 +247,7 @@ pub const Module = struct {
.link_out_file = null,
.exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)),
.build_group = event.Group(BuildError!void).init(loop),
.compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)),
});
}
@ -288,7 +287,17 @@ pub const Module = struct {
await (async self.events.put(Event{ .Error = err }) catch unreachable);
return;
};
await (async self.events.put(Event.Ok) catch unreachable);
const compile_errors = blk: {
const held = await (async self.compile_errors.acquire() catch unreachable);
defer held.release();
break :blk held.value.toOwnedSlice();
};
if (compile_errors.len == 0) {
await (async self.events.put(Event.Ok) catch unreachable);
} else {
await (async self.events.put(Event{ .Fail = compile_errors }) catch unreachable);
}
// for now we stop after 1
return;
}
@ -310,10 +319,13 @@ pub const Module = struct {
};
errdefer self.a().free(source_code);
var parsed_file = ParsedFile{
.tree = try std.zig.parse(self.a(), source_code),
const parsed_file = try self.a().create(ParsedFile{
.tree = undefined,
.realpath = root_src_real_path,
};
});
errdefer self.a().destroy(parsed_file);
parsed_file.tree = try std.zig.parse(self.a(), source_code);
errdefer parsed_file.tree.deinit();
const tree = &parsed_file.tree;
@ -337,7 +349,7 @@ pub const Module = struct {
const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else {
@panic("TODO add compile error");
//try self.addCompileError(
// &parsed_file,
// parsed_file,
// fn_proto.fn_token,
// fn_proto.fn_token + 1,
// "missing function name",
@ -357,7 +369,7 @@ pub const Module = struct {
});
errdefer self.a().destroy(fn_decl);
try decl_group.call(addTopLevelDecl, self, tree, &fn_decl.base);
try decl_group.call(addTopLevelDecl, self, parsed_file, &fn_decl.base);
},
ast.Node.Id.TestDecl => @panic("TODO"),
else => unreachable,
@ -367,20 +379,56 @@ pub const Module = struct {
try await (async self.build_group.wait() catch unreachable);
}
async fn addTopLevelDecl(self: *Module, tree: *ast.Tree, decl: *Decl) !void {
const is_export = decl.isExported(tree);
async fn addTopLevelDecl(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void {
const is_export = decl.isExported(&parsed_file.tree);
if (is_export) {
try self.build_group.call(verifyUniqueSymbol, self, decl);
try self.build_group.call(verifyUniqueSymbol, self, parsed_file, decl);
}
}
async fn verifyUniqueSymbol(self: *Module, decl: *Decl) !void {
fn addCompileError(self: *Module, parsed_file: *ParsedFile, span: errmsg.Span, comptime fmt: []const u8, args: ...) !void {
const text = try std.fmt.allocPrint(self.loop.allocator, fmt, args);
errdefer self.loop.allocator.free(text);
try self.build_group.call(addCompileErrorAsync, self, parsed_file, span.first, span.last, text);
}
async fn addCompileErrorAsync(
self: *Module,
parsed_file: *ParsedFile,
first_token: ast.TokenIndex,
last_token: ast.TokenIndex,
text: []u8,
) !void {
const msg = try self.loop.allocator.create(errmsg.Msg{
.path = parsed_file.realpath,
.text = text,
.span = errmsg.Span{
.first = first_token,
.last = last_token,
},
.tree = &parsed_file.tree,
});
errdefer self.loop.allocator.destroy(msg);
const compile_errors = await (async self.compile_errors.acquire() catch unreachable);
defer compile_errors.release();
try compile_errors.value.append(msg);
}
async fn verifyUniqueSymbol(self: *Module, parsed_file: *ParsedFile, decl: *Decl) !void {
const exported_symbol_names = await (async self.exported_symbol_names.acquire() catch unreachable);
defer exported_symbol_names.release();
if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| {
@panic("TODO report compile error");
try self.addCompileError(
parsed_file,
decl.getSpan(),
"exported symbol collision: '{}'",
decl.name,
);
}
}
@ -503,6 +551,22 @@ pub const Decl = struct {
}
}
pub fn getSpan(base: *const Decl) errmsg.Span {
switch (base.id) {
Id.Fn => {
const fn_decl = @fieldParentPtr(Fn, "base", base);
const fn_proto = fn_decl.fn_proto;
const start = fn_proto.fn_token;
const end = fn_proto.name_token orelse start;
return errmsg.Span{
.first = start,
.last = end + 1,
};
},
else => @panic("TODO"),
}
}
pub const Resolution = enum {
Unresolved,
InProgress,

176
src-self-hosted/test.zig Normal file
View File

@ -0,0 +1,176 @@
const std = @import("std");
const mem = std.mem;
const builtin = @import("builtin");
const Target = @import("target.zig").Target;
const Module = @import("module.zig").Module;
const introspect = @import("introspect.zig");
const assertOrPanic = std.debug.assertOrPanic;
const errmsg = @import("errmsg.zig");
test "compile errors" {
var ctx: TestContext = undefined;
try ctx.init();
defer ctx.deinit();
try ctx.testCompileError(
\\export fn entry() void {}
\\export fn entry() void {}
, file1, 2, 8, "exported symbol collision: 'entry'");
try ctx.run();
}
const file1 = "1.zig";
const TestContext = struct {
loop: std.event.Loop,
zig_lib_dir: []u8,
direct_allocator: std.heap.DirectAllocator,
arena: std.heap.ArenaAllocator,
zig_cache_dir: []u8,
file_index: std.atomic.Int(usize),
group: std.event.Group(error!void),
any_err: error!void,
const tmp_dir_name = "stage2_test_tmp";
fn init(self: *TestContext) !void {
self.* = TestContext{
.any_err = {},
.direct_allocator = undefined,
.arena = undefined,
.loop = undefined,
.zig_lib_dir = undefined,
.zig_cache_dir = undefined,
.group = undefined,
.file_index = std.atomic.Int(usize).init(0),
};
self.direct_allocator = std.heap.DirectAllocator.init();
errdefer self.direct_allocator.deinit();
self.arena = std.heap.ArenaAllocator.init(&self.direct_allocator.allocator);
errdefer self.arena.deinit();
// TODO faster allocator for coroutines that is thread-safe/lock-free
try self.loop.initMultiThreaded(&self.direct_allocator.allocator);
errdefer self.loop.deinit();
self.group = std.event.Group(error!void).init(&self.loop);
errdefer self.group.cancelAll();
self.zig_lib_dir = try introspect.resolveZigLibDir(&self.arena.allocator);
errdefer self.arena.allocator.free(self.zig_lib_dir);
self.zig_cache_dir = try introspect.resolveZigCacheDir(&self.arena.allocator);
errdefer self.arena.allocator.free(self.zig_cache_dir);
try std.os.makePath(&self.arena.allocator, tmp_dir_name);
errdefer std.os.deleteTree(&self.arena.allocator, tmp_dir_name) catch {};
}
fn deinit(self: *TestContext) void {
std.os.deleteTree(&self.arena.allocator, tmp_dir_name) catch {};
self.arena.allocator.free(self.zig_cache_dir);
self.arena.allocator.free(self.zig_lib_dir);
self.loop.deinit();
self.arena.deinit();
self.direct_allocator.deinit();
}
fn run(self: *TestContext) !void {
const handle = try self.loop.call(waitForGroup, self);
defer cancel handle;
self.loop.run();
return self.any_err;
}
async fn waitForGroup(self: *TestContext) void {
self.any_err = await (async self.group.wait() catch unreachable);
}
fn testCompileError(
self: *TestContext,
source: []const u8,
path: []const u8,
line: usize,
column: usize,
msg: []const u8,
) !void {
var file_index_buf: [20]u8 = undefined;
const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.next());
const file1_path = try std.os.path.join(&self.arena.allocator, tmp_dir_name, file_index, file1);
if (std.os.path.dirname(file1_path)) |dirname| {
try std.os.makePath(&self.arena.allocator, dirname);
}
// TODO async I/O
try std.io.writeFile(&self.arena.allocator, file1_path, source);
var module = try Module.create(
&self.loop,
"test",
file1_path,
Target.Native,
Module.Kind.Obj,
builtin.Mode.Debug,
self.zig_lib_dir,
self.zig_cache_dir,
);
errdefer module.destroy();
try module.build();
try self.group.call(getModuleEvent, module, source, path, line, column, msg);
}
async fn getModuleEvent(
module: *Module,
source: []const u8,
path: []const u8,
line: usize,
column: usize,
text: []const u8,
) !void {
defer module.destroy();
const build_event = await (async module.events.get() catch unreachable);
switch (build_event) {
Module.Event.Ok => {
@panic("build incorrectly succeeded");
},
Module.Event.Error => |err| {
@panic("build incorrectly failed");
},
Module.Event.Fail => |msgs| {
assertOrPanic(msgs.len != 0);
for (msgs) |msg| {
if (mem.endsWith(u8, msg.path, path) and mem.eql(u8, msg.text, text)) {
const first_token = msg.tree.tokens.at(msg.span.first);
const last_token = msg.tree.tokens.at(msg.span.first);
const start_loc = msg.tree.tokenLocationPtr(0, first_token);
if (start_loc.line + 1 == line and start_loc.column + 1 == column) {
return;
}
}
}
std.debug.warn(
"\n=====source:=======\n{}\n====expected:========\n{}:{}:{}: error: {}\n",
source,
path,
line,
column,
text,
);
std.debug.warn("\n====found:========\n");
var stderr = try std.io.getStdErr();
for (msgs) |msg| {
try errmsg.printToFile(&stderr, msg, errmsg.Color.Auto);
}
std.debug.warn("============\n");
return error.TestFailed;
},
}
}
};

View File

@ -891,15 +891,19 @@ int main(int argc, char **argv) {
add_package(g, cur_pkg, g->root_package);
if (cmd == CmdBuild || cmd == CmdRun) {
codegen_set_emit_file_type(g, emit_file_type);
if (cmd == CmdBuild || cmd == CmdRun || cmd == CmdTest) {
for (size_t i = 0; i < objects.length; i += 1) {
codegen_add_object(g, buf_create_from_str(objects.at(i)));
}
for (size_t i = 0; i < asm_files.length; i += 1) {
codegen_add_assembly(g, buf_create_from_str(asm_files.at(i)));
}
}
if (cmd == CmdBuild || cmd == CmdRun) {
codegen_set_emit_file_type(g, emit_file_type);
codegen_build(g);
codegen_link(g, out_file);
if (timing_info)

View File

@ -1,9 +1,11 @@
pub const Stack = @import("stack.zig").Stack;
pub const QueueMpsc = @import("queue_mpsc.zig").QueueMpsc;
pub const QueueMpmc = @import("queue_mpmc.zig").QueueMpmc;
pub const Int = @import("int.zig").Int;
test "std.atomic" {
_ = @import("stack.zig");
_ = @import("queue_mpsc.zig");
_ = @import("queue_mpmc.zig");
_ = @import("int.zig");
}

19
std/atomic/int.zig Normal file
View File

@ -0,0 +1,19 @@
const builtin = @import("builtin");
const AtomicOrder = builtin.AtomicOrder;
/// Thread-safe, lock-free integer
pub fn Int(comptime T: type) type {
return struct {
value: T,
pub const Self = this;
pub fn init(init_val: T) Self {
return Self{ .value = init_val };
}
pub fn next(self: *Self) T {
return @atomicRmw(T, &self.value, builtin.AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
}
};
}

View File

@ -1596,6 +1596,8 @@ pub const TestStep = struct {
target: Target,
exec_cmd_args: ?[]const ?[]const u8,
include_dirs: ArrayList([]const u8),
lib_paths: ArrayList([]const u8),
object_files: ArrayList([]const u8),
pub fn init(builder: *Builder, root_src: []const u8) TestStep {
const step_name = builder.fmt("test {}", root_src);
@ -1611,9 +1613,15 @@ pub const TestStep = struct {
.target = Target{ .Native = {} },
.exec_cmd_args = null,
.include_dirs = ArrayList([]const u8).init(builder.allocator),
.lib_paths = ArrayList([]const u8).init(builder.allocator),
.object_files = ArrayList([]const u8).init(builder.allocator),
};
}
pub fn addLibPath(self: *TestStep, path: []const u8) void {
self.lib_paths.append(path) catch unreachable;
}
pub fn setVerbose(self: *TestStep, value: bool) void {
self.verbose = value;
}
@ -1638,6 +1646,10 @@ pub const TestStep = struct {
self.filter = text;
}
pub fn addObjectFile(self: *TestStep, path: []const u8) void {
self.object_files.append(path) catch unreachable;
}
pub fn setTarget(self: *TestStep, target_arch: builtin.Arch, target_os: builtin.Os, target_environ: builtin.Environ) void {
self.target = Target{
.Cross = CrossTarget{
@ -1699,6 +1711,11 @@ pub const TestStep = struct {
try zig_args.append(self.name_prefix);
}
for (self.object_files.toSliceConst()) |object_file| {
try zig_args.append("--object");
try zig_args.append(builder.pathFromRoot(object_file));
}
{
var it = self.link_libs.iterator();
while (true) {
@ -1734,6 +1751,11 @@ pub const TestStep = struct {
try zig_args.append(rpath);
}
for (self.lib_paths.toSliceConst()) |lib_path| {
try zig_args.append("--library-path");
try zig_args.append(lib_path);
}
for (builder.lib_paths.toSliceConst()) |lib_path| {
try zig_args.append("--library-path");
try zig_args.append(lib_path);