Commit 927f233ff8
Changed files (8)
lib
std
src
test
standalone
lib/std/Build/Step/Compile.zig
@@ -293,6 +293,7 @@ pub const Kind = enum {
lib,
obj,
@"test",
+ test_obj,
};
pub const HeaderInstallation = union(enum) {
@@ -370,7 +371,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
}
// 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"))
+ const name_adjusted = if ((options.kind == .@"test" or options.kind == .test_obj) and mem.eql(u8, name, "test"))
""
else
owner.fmt("{s} ", .{name});
@@ -385,6 +386,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
.lib => "zig build-lib",
.obj => "zig build-obj",
.@"test" => "zig test",
+ .test_obj => "zig test-obj",
},
name_adjusted,
@tagName(options.root_module.optimize orelse .Debug),
@@ -396,7 +398,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
.target = target,
.output_mode = switch (options.kind) {
.lib => .Lib,
- .obj => .Obj,
+ .obj, .test_obj => .Obj,
.exe, .@"test" => .Exe,
},
.link_mode = options.linkage,
@@ -1053,6 +1055,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
.exe => "build-exe",
.obj => "build-obj",
.@"test" => "test",
+ .test_obj => "test-obj",
};
try zig_args.append(cmd);
@@ -1222,9 +1225,9 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
switch (other.kind) {
.exe => return step.fail("cannot link with an executable build artifact", .{}),
.@"test" => return step.fail("cannot link with a test", .{}),
- .obj => {
+ .obj, .test_obj => {
const included_in_lib_or_obj = !my_responsibility and
- (dep_compile.kind == .lib or dep_compile.kind == .obj);
+ (dep_compile.kind == .lib or dep_compile.kind == .obj or dep_compile.kind == .test_obj);
if (!already_linked and !included_in_lib_or_obj) {
try zig_args.append(other.getEmittedBin().getPath2(b, step));
total_linker_objects += 1;
lib/std/Build/Step/InstallArtifact.zig
@@ -56,7 +56,7 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
const dest_dir: ?InstallDir = switch (options.dest_dir) {
.disabled => null,
.default => switch (artifact.kind) {
- .obj => @panic("object files have no standard installation procedure"),
+ .obj, .test_obj => @panic("object files have no standard installation procedure"),
.exe, .@"test" => .bin,
.lib => if (artifact.isDll()) .bin else .lib,
},
lib/std/Build/Module.zig
@@ -474,7 +474,7 @@ pub fn addObjectFile(m: *Module, object: LazyPath) void {
}
pub fn addObject(m: *Module, object: *Step.Compile) void {
- assert(object.kind == .obj);
+ assert(object.kind == .obj or object.kind == .test_obj);
m.linkLibraryOrObject(object);
}
lib/std/Build.zig
@@ -1019,6 +1019,10 @@ pub const TestOptions = struct {
use_llvm: ?bool = null,
use_lld: ?bool = null,
zig_lib_dir: ?LazyPath = null,
+ /// Emits an object file instead of a test binary.
+ /// The object must be linked separately.
+ /// Usually used in conjunction with a custom `test_runner`.
+ emit_object: bool = false,
/// Prefer populating this field (using e.g. `createModule`) instead of populating
/// the following fields (`root_source_file` etc). In a future release, those fields
@@ -1067,7 +1071,7 @@ pub fn addTest(b: *Build, options: TestOptions) *Step.Compile {
}
return .create(b, .{
.name = options.name,
- .kind = .@"test",
+ .kind = if (options.emit_object) .test_obj else .@"test",
.root_module = options.root_module orelse b.createModule(.{
.root_source_file = options.root_source_file orelse @panic("`root_module` and `root_source_file` cannot both be null"),
.target = options.target orelse b.graph.host,
src/main.zig
@@ -278,6 +278,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
} else if (mem.eql(u8, cmd, "test")) {
dev.check(.test_command);
return buildOutputType(gpa, arena, args, .zig_test);
+ } else if (mem.eql(u8, cmd, "test-obj")) {
+ dev.check(.test_command);
+ return buildOutputType(gpa, arena, args, .zig_test_obj);
} else if (mem.eql(u8, cmd, "run")) {
dev.check(.run_command);
return buildOutputType(gpa, arena, args, .run);
@@ -764,6 +767,7 @@ const ArgMode = union(enum) {
cpp,
translate_c,
zig_test,
+ zig_test_obj,
run,
};
@@ -975,7 +979,10 @@ fn buildOutputType(
.dynamic_linker = null,
.modules = .{},
.opts = .{
- .is_test = arg_mode == .zig_test,
+ .is_test = switch (arg_mode) {
+ .zig_test, .zig_test_obj => true,
+ .build, .cc, .cpp, .translate_c, .run => false,
+ },
// Populated while parsing CLI args.
.output_mode = undefined,
// Populated in the call to `createModule` for the root module.
@@ -1030,7 +1037,7 @@ fn buildOutputType(
var n_jobs: ?u32 = null;
switch (arg_mode) {
- .build, .translate_c, .zig_test, .run => {
+ .build, .translate_c, .zig_test, .zig_test_obj, .run => {
switch (arg_mode) {
.build => |m| {
create_module.opts.output_mode = m;
@@ -1042,6 +1049,9 @@ fn buildOutputType(
.zig_test, .run => {
create_module.opts.output_mode = .Exe;
},
+ .zig_test_obj => {
+ create_module.opts.output_mode = .Obj;
+ },
else => unreachable,
}
@@ -2834,6 +2844,10 @@ fn buildOutputType(
},
}
+ if (arg_mode == .zig_test_obj and !test_no_exec and listen == .none) {
+ fatal("test-obj requires --test-no-exec", .{});
+ }
+
if (arg_mode == .translate_c and create_module.c_source_files.items.len != 1) {
fatal("translate-c expects exactly 1 source file (found {d})", .{create_module.c_source_files.items.len});
}
@@ -2903,10 +2917,10 @@ fn buildOutputType(
create_module.opts.any_error_tracing = true;
const src_path = try introspect.resolvePath(arena, unresolved_src_path);
- const name = if (arg_mode == .zig_test)
- "test"
- else
- fs.path.stem(fs.path.basename(src_path));
+ const name = switch (arg_mode) {
+ .zig_test => "test",
+ .build, .cc, .cpp, .translate_c, .zig_test_obj, .run => fs.path.stem(fs.path.basename(src_path)),
+ };
try create_module.modules.put(arena, name, .{
.paths = .{
@@ -2935,7 +2949,7 @@ fn buildOutputType(
rc_source_files_owner_index = create_module.rc_source_files.items.len;
}
- if (!create_module.opts.have_zcu and arg_mode == .zig_test) {
+ if (!create_module.opts.have_zcu and create_module.opts.is_test) {
fatal("`zig test` expects a zig source file argument", .{});
}
@@ -3037,16 +3051,36 @@ fn buildOutputType(
break :m null;
};
- const root_mod = if (arg_mode == .zig_test) root_mod: {
- const test_mod = if (test_runner_path) |test_runner| test_mod: {
- const test_mod = try Package.Module.create(arena, .{
+ const root_mod = switch (arg_mode) {
+ .zig_test, .zig_test_obj => root_mod: {
+ const test_mod = if (test_runner_path) |test_runner| test_mod: {
+ const test_mod = try Package.Module.create(arena, .{
+ .global_cache_directory = global_cache_directory,
+ .paths = .{
+ .root = .{
+ .root_dir = Cache.Directory.cwd(),
+ .sub_path = fs.path.dirname(test_runner) orelse "",
+ },
+ .root_src_path = fs.path.basename(test_runner),
+ },
+ .fully_qualified_name = "root",
+ .cc_argv = &.{},
+ .inherited = .{},
+ .global = create_module.resolved_options,
+ .parent = main_mod,
+ .builtin_mod = main_mod.getBuiltinDependency(),
+ .builtin_modules = null, // `builtin_mod` is specified
+ });
+ test_mod.deps = try main_mod.deps.clone(arena);
+ break :test_mod test_mod;
+ } else try Package.Module.create(arena, .{
.global_cache_directory = global_cache_directory,
.paths = .{
.root = .{
- .root_dir = Cache.Directory.cwd(),
- .sub_path = fs.path.dirname(test_runner) orelse "",
+ .root_dir = zig_lib_directory,
+ .sub_path = "compiler",
},
- .root_src_path = fs.path.basename(test_runner),
+ .root_src_path = "test_runner.zig",
},
.fully_qualified_name = "root",
.cc_argv = &.{},
@@ -3056,28 +3090,11 @@ fn buildOutputType(
.builtin_mod = main_mod.getBuiltinDependency(),
.builtin_modules = null, // `builtin_mod` is specified
});
- test_mod.deps = try main_mod.deps.clone(arena);
- break :test_mod test_mod;
- } else try Package.Module.create(arena, .{
- .global_cache_directory = global_cache_directory,
- .paths = .{
- .root = .{
- .root_dir = zig_lib_directory,
- .sub_path = "compiler",
- },
- .root_src_path = "test_runner.zig",
- },
- .fully_qualified_name = "root",
- .cc_argv = &.{},
- .inherited = .{},
- .global = create_module.resolved_options,
- .parent = main_mod,
- .builtin_mod = main_mod.getBuiltinDependency(),
- .builtin_modules = null, // `builtin_mod` is specified
- });
- break :root_mod test_mod;
- } else main_mod;
+ break :root_mod test_mod;
+ },
+ else => main_mod,
+ };
const target = main_mod.resolved_target.result;
@@ -3202,7 +3219,7 @@ fn buildOutputType(
.directory = blk: {
switch (arg_mode) {
.run, .zig_test => break :blk null,
- else => {
+ .build, .cc, .cpp, .translate_c, .zig_test_obj => {
if (output_to_cache) {
break :blk null;
} else {
test/standalone/test_obj_link_run/src/main.zig
@@ -0,0 +1,17 @@
+test {
+ try std.testing.expect(true);
+}
+
+test "equality" {
+ try std.testing.expect(one() == 1);
+}
+
+test "arithmetic" {
+ try std.testing.expect(one() + 2 == 3);
+}
+
+fn one() u32 {
+ return 1;
+}
+
+const std = @import("std");
test/standalone/test_obj_link_run/build.zig
@@ -0,0 +1,32 @@
+pub fn build(b: *std.Build) void {
+ // To avoid having to explicitly link required system libraries into the final test
+ // executable (e.g. ntdll on Windows), we'll just link everything with libc here.
+
+ const test_obj = b.addTest(.{
+ .emit_object = true,
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = b.graph.host,
+ .link_libc = true,
+ }),
+ });
+
+ const test_exe_mod = b.createModule(.{
+ .root_source_file = null,
+ .target = b.graph.host,
+ .link_libc = true,
+ });
+ test_exe_mod.addObject(test_obj);
+ const test_exe = b.addExecutable(.{
+ .name = "test",
+ .root_module = test_exe_mod,
+ });
+
+ const test_step = b.step("test", "Test the program");
+ b.default_step = test_step;
+
+ const test_run = b.addRunArtifact(test_exe);
+ test_step.dependOn(&test_run.step);
+}
+
+const std = @import("std");
test/standalone/build.zig.zon
@@ -5,6 +5,9 @@
.simple = .{
.path = "simple",
},
+ .test_obj_link_run = .{
+ .path = "test_obj_link_run",
+ },
.test_runner_path = .{
.path = "test_runner_path",
},