authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-16 04:11:41-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-03-16 04:11:41-04:00
logbd242ce1ce9ef6ffb1af4432d892bf582dcdba8a
tree34caaa3f320e8830a5f1f1c93a4b5d70c0d192a8
parenta2c6ecd6dc0bdbe2396be9b055852324f16d34c9
parent7177b3994626114e57bf8df36ca84fd942bac282
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14647 from ziglang/build-parallel

zig build: run steps in parallel

235 files changed, 11850 insertions(+), 9055 deletions(-)

CMakeLists.txt+3-2
......@@ -506,7 +506,9 @@ set(ZIG_STAGE2_SOURCES
506506 "${CMAKE_SOURCE_DIR}/lib/std/Thread.zig"
507507 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Futex.zig"
508508 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.zig"
509 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Pool.zig"
509510 "${CMAKE_SOURCE_DIR}/lib/std/Thread/ResetEvent.zig"
511 "${CMAKE_SOURCE_DIR}/lib/std/Thread/WaitGroup.zig"
510512 "${CMAKE_SOURCE_DIR}/lib/std/time.zig"
511513 "${CMAKE_SOURCE_DIR}/lib/std/treap.zig"
512514 "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig"
......@@ -516,6 +518,7 @@ set(ZIG_STAGE2_SOURCES
516518 "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig"
517519 "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"
518520 "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"
521 "${CMAKE_SOURCE_DIR}/lib/std/zig/Server.zig"
519522 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
520523 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
521524 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"
......@@ -530,9 +533,7 @@ set(ZIG_STAGE2_SOURCES
530533 "${CMAKE_SOURCE_DIR}/src/Package.zig"
531534 "${CMAKE_SOURCE_DIR}/src/RangeSet.zig"
532535 "${CMAKE_SOURCE_DIR}/src/Sema.zig"
533 "${CMAKE_SOURCE_DIR}/src/ThreadPool.zig"
534536 "${CMAKE_SOURCE_DIR}/src/TypedValue.zig"
535 "${CMAKE_SOURCE_DIR}/src/WaitGroup.zig"
536537 "${CMAKE_SOURCE_DIR}/src/Zir.zig"
537538 "${CMAKE_SOURCE_DIR}/src/arch/aarch64/CodeGen.zig"
538539 "${CMAKE_SOURCE_DIR}/src/arch/aarch64/Emit.zig"
build.zig+109-108
......@@ -31,6 +31,11 @@ pub fn build(b: *std.Build) !void {
3131 const use_zig_libcxx = b.option(bool, "use-zig-libcxx", "If libc++ is needed, use zig's bundled version, don't try to integrate with the system") orelse false;
3232
3333 const test_step = b.step("test", "Run all the tests");
34 const deprecated_skip_install_lib_files = b.option(bool, "skip-install-lib-files", "deprecated. see no-lib") orelse false;
35 if (deprecated_skip_install_lib_files) {
36 std.log.warn("-Dskip-install-lib-files is deprecated in favor of -Dno-lib", .{});
37 }
38 const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files and langref to installation prefix. Useful for development") orelse deprecated_skip_install_lib_files;
3439
3540 const docgen_exe = b.addExecutable(.{
3641 .name = "docgen",
......@@ -40,28 +45,32 @@ pub fn build(b: *std.Build) !void {
4045 });
4146 docgen_exe.single_threaded = single_threaded;
4247
43 const langref_out_path = try b.cache_root.join(b.allocator, &.{"langref.html"});
44 const docgen_cmd = docgen_exe.run();
45 docgen_cmd.addArgs(&[_][]const u8{
46 "--zig",
47 b.zig_exe,
48 "doc" ++ fs.path.sep_str ++ "langref.html.in",
49 langref_out_path,
50 });
51 docgen_cmd.step.dependOn(&docgen_exe.step);
48 const docgen_cmd = b.addRunArtifact(docgen_exe);
49 docgen_cmd.addArgs(&.{ "--zig", b.zig_exe });
50 docgen_cmd.addFileSourceArg(.{ .path = "doc/langref.html.in" });
51 const langref_file = docgen_cmd.addOutputFileArg("langref.html");
52 const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "doc/langref.html");
53 if (!skip_install_lib_files) {
54 b.getInstallStep().dependOn(&install_langref.step);
55 }
5256
5357 const docs_step = b.step("docs", "Build documentation");
5458 docs_step.dependOn(&docgen_cmd.step);
5559
56 const test_cases = b.addTest(.{
57 .root_source_file = .{ .path = "src/test.zig" },
60 // This is for legacy reasons, to be removed after our CI scripts are upgraded to use
61 // the file from the install prefix instead.
62 const legacy_write_to_cache = b.addWriteFiles();
63 legacy_write_to_cache.addCopyFileToSource(langref_file, "zig-cache/langref.html");
64 docs_step.dependOn(&legacy_write_to_cache.step);
65
66 const check_case_exe = b.addExecutable(.{
67 .name = "check-case",
68 .root_source_file = .{ .path = "test/src/Cases.zig" },
5869 .optimize = optimize,
5970 });
60 test_cases.main_pkg_path = ".";
61 test_cases.stack_size = stack_size;
62 test_cases.single_threaded = single_threaded;
63
64 const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"});
71 check_case_exe.main_pkg_path = ".";
72 check_case_exe.stack_size = stack_size;
73 check_case_exe.single_threaded = single_threaded;
6574
6675 const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false;
6776 const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false;
......@@ -74,11 +83,6 @@ pub fn build(b: *std.Build) !void {
7483 const skip_stage1 = b.option(bool, "skip-stage1", "Main test suite skips stage1 compile error tests") orelse false;
7584 const skip_run_translated_c = b.option(bool, "skip-run-translated-c", "Main test suite skips run-translated-c tests") orelse false;
7685 const skip_stage2_tests = b.option(bool, "skip-stage2-tests", "Main test suite skips self-hosted compiler tests") orelse false;
77 const deprecated_skip_install_lib_files = b.option(bool, "skip-install-lib-files", "deprecated. see no-lib") orelse false;
78 if (deprecated_skip_install_lib_files) {
79 std.log.warn("-Dskip-install-lib-files is deprecated in favor of -Dno-lib", .{});
80 }
81 const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files to installation prefix. Useful for development") orelse deprecated_skip_install_lib_files;
8286
8387 const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;
8488
......@@ -175,13 +179,12 @@ pub fn build(b: *std.Build) !void {
175179 test_step.dependOn(&exe.step);
176180 }
177181
178 b.default_step.dependOn(&exe.step);
179182 exe.single_threaded = single_threaded;
180183
181184 if (target.isWindows() and target.getAbi() == .gnu) {
182185 // LTO is currently broken on mingw, this can be removed when it's fixed.
183186 exe.want_lto = false;
184 test_cases.want_lto = false;
187 check_case_exe.want_lto = false;
185188 }
186189
187190 const exe_options = b.addOptions();
......@@ -195,11 +198,11 @@ pub fn build(b: *std.Build) !void {
195198 exe_options.addOption(bool, "llvm_has_arc", llvm_has_arc);
196199 exe_options.addOption(bool, "force_gpa", force_gpa);
197200 exe_options.addOption(bool, "only_c", only_c);
198 exe_options.addOption(bool, "omit_pkg_fetching_code", false);
201 exe_options.addOption(bool, "omit_pkg_fetching_code", only_c);
199202
200203 if (link_libc) {
201204 exe.linkLibC();
202 test_cases.linkLibC();
205 check_case_exe.linkLibC();
203206 }
204207
205208 const is_debug = optimize == .Debug;
......@@ -285,14 +288,14 @@ pub fn build(b: *std.Build) !void {
285288 }
286289
287290 try addCmakeCfgOptionsToExe(b, cfg, exe, use_zig_libcxx);
288 try addCmakeCfgOptionsToExe(b, cfg, test_cases, use_zig_libcxx);
291 try addCmakeCfgOptionsToExe(b, cfg, check_case_exe, use_zig_libcxx);
289292 } else {
290293 // Here we are -Denable-llvm but no cmake integration.
291294 try addStaticLlvmOptionsToExe(exe);
292 try addStaticLlvmOptionsToExe(test_cases);
295 try addStaticLlvmOptionsToExe(check_case_exe);
293296 }
294297 if (target.isWindows()) {
295 inline for (.{ exe, test_cases }) |artifact| {
298 inline for (.{ exe, check_case_exe }) |artifact| {
296299 artifact.linkSystemLibrary("version");
297300 artifact.linkSystemLibrary("uuid");
298301 artifact.linkSystemLibrary("ole32");
......@@ -337,8 +340,9 @@ pub fn build(b: *std.Build) !void {
337340 const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
338341
339342 const test_cases_options = b.addOptions();
340 test_cases.addOptions("build_options", test_cases_options);
343 check_case_exe.addOptions("build_options", test_cases_options);
341344
345 test_cases_options.addOption(bool, "enable_tracy", false);
342346 test_cases_options.addOption(bool, "enable_logging", enable_logging);
343347 test_cases_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);
344348 test_cases_options.addOption(bool, "skip_non_native", skip_non_native);
......@@ -361,12 +365,6 @@ pub fn build(b: *std.Build) !void {
361365 test_cases_options.addOption(std.SemanticVersion, "semver", semver);
362366 test_cases_options.addOption(?[]const u8, "test_filter", test_filter);
363367
364 const test_cases_step = b.step("test-cases", "Run the main compiler test cases");
365 test_cases_step.dependOn(&test_cases.step);
366 if (!skip_stage2_tests) {
367 test_step.dependOn(test_cases_step);
368 }
369
370368 var chosen_opt_modes_buf: [4]builtin.Mode = undefined;
371369 var chosen_mode_index: usize = 0;
372370 if (!skip_debug) {
......@@ -387,96 +385,101 @@ pub fn build(b: *std.Build) !void {
387385 }
388386 const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index];
389387
390 // run stage1 `zig fmt` on this build.zig file just to make sure it works
391 test_step.dependOn(&fmt_build_zig.step);
392 const fmt_step = b.step("test-fmt", "Run zig fmt against build.zig to make sure it works");
393 fmt_step.dependOn(&fmt_build_zig.step);
394
395 test_step.dependOn(tests.addPkgTests(
396 b,
397 test_filter,
398 "test/behavior.zig",
399 "behavior",
400 "Run the behavior tests",
401 optimization_modes,
402 skip_single_threaded,
403 skip_non_native,
404 skip_libc,
405 skip_stage1,
406 skip_stage2_tests,
407 ));
388 const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" };
389 const fmt_exclude_paths = &.{"test/cases"};
390 const do_fmt = b.addFmt(.{
391 .paths = fmt_include_paths,
392 .exclude_paths = fmt_exclude_paths,
393 });
408394
409 test_step.dependOn(tests.addPkgTests(
410 b,
411 test_filter,
412 "lib/compiler_rt.zig",
413 "compiler-rt",
414 "Run the compiler_rt tests",
415 optimization_modes,
416 true, // skip_single_threaded
417 skip_non_native,
418 true, // skip_libc
419 skip_stage1,
420 skip_stage2_tests or true, // TODO get these all passing
421 ));
395 b.step("test-fmt", "Check source files having conforming formatting").dependOn(&b.addFmt(.{
396 .paths = fmt_include_paths,
397 .exclude_paths = fmt_exclude_paths,
398 .check = true,
399 }).step);
422400
423 test_step.dependOn(tests.addPkgTests(
424 b,
425 test_filter,
426 "lib/c.zig",
427 "universal-libc",
428 "Run the universal libc tests",
429 optimization_modes,
430 true, // skip_single_threaded
431 skip_non_native,
432 true, // skip_libc
433 skip_stage1,
434 skip_stage2_tests or true, // TODO get these all passing
435 ));
401 const test_cases_step = b.step("test-cases", "Run the main compiler test cases");
402 try tests.addCases(b, test_cases_step, test_filter, check_case_exe);
403 if (!skip_stage2_tests) test_step.dependOn(test_cases_step);
404
405 test_step.dependOn(tests.addModuleTests(b, .{
406 .test_filter = test_filter,
407 .root_src = "test/behavior.zig",
408 .name = "behavior",
409 .desc = "Run the behavior tests",
410 .optimize_modes = optimization_modes,
411 .skip_single_threaded = skip_single_threaded,
412 .skip_non_native = skip_non_native,
413 .skip_libc = skip_libc,
414 .skip_stage1 = skip_stage1,
415 .skip_stage2 = skip_stage2_tests,
416 .max_rss = 1 * 1024 * 1024 * 1024,
417 }));
418
419 test_step.dependOn(tests.addModuleTests(b, .{
420 .test_filter = test_filter,
421 .root_src = "lib/compiler_rt.zig",
422 .name = "compiler-rt",
423 .desc = "Run the compiler_rt tests",
424 .optimize_modes = optimization_modes,
425 .skip_single_threaded = true,
426 .skip_non_native = skip_non_native,
427 .skip_libc = true,
428 .skip_stage1 = skip_stage1,
429 .skip_stage2 = true, // TODO get all these passing
430 }));
431
432 test_step.dependOn(tests.addModuleTests(b, .{
433 .test_filter = test_filter,
434 .root_src = "lib/c.zig",
435 .name = "universal-libc",
436 .desc = "Run the universal libc tests",
437 .optimize_modes = optimization_modes,
438 .skip_single_threaded = true,
439 .skip_non_native = skip_non_native,
440 .skip_libc = true,
441 .skip_stage1 = skip_stage1,
442 .skip_stage2 = true, // TODO get all these passing
443 }));
436444
437445 test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes));
438446 test_step.dependOn(tests.addStandaloneTests(
439447 b,
440 test_filter,
441448 optimization_modes,
442 skip_non_native,
443449 enable_macos_sdk,
444 target,
445450 skip_stage2_tests,
446 b.enable_darling,
447 b.enable_qemu,
448 b.enable_rosetta,
449 b.enable_wasmtime,
450 b.enable_wine,
451451 enable_symlinks_windows,
452452 ));
453453 test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release));
454 test_step.dependOn(tests.addLinkTests(b, test_filter, optimization_modes, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows));
454 test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, skip_stage2_tests, enable_symlinks_windows));
455455 test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes));
456 test_step.dependOn(tests.addCliTests(b, test_filter, optimization_modes));
456 test_step.dependOn(tests.addCliTests(b));
457457 test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes));
458458 test_step.dependOn(tests.addTranslateCTests(b, test_filter));
459459 if (!skip_run_translated_c) {
460460 test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter, target));
461461 }
462 // tests for this feature are disabled until we have the self-hosted compiler available
463 // test_step.dependOn(tests.addGenHTests(b, test_filter));
464462
465 test_step.dependOn(tests.addPkgTests(
466 b,
467 test_filter,
468 "lib/std/std.zig",
469 "std",
470 "Run the standard library tests",
471 optimization_modes,
472 skip_single_threaded,
473 skip_non_native,
474 skip_libc,
475 skip_stage1,
476 true, // TODO get these all passing
477 ));
463 test_step.dependOn(tests.addModuleTests(b, .{
464 .test_filter = test_filter,
465 .root_src = "lib/std/std.zig",
466 .name = "std",
467 .desc = "Run the standard library tests",
468 .optimize_modes = optimization_modes,
469 .skip_single_threaded = skip_single_threaded,
470 .skip_non_native = skip_non_native,
471 .skip_libc = skip_libc,
472 .skip_stage1 = skip_stage1,
473 .skip_stage2 = true, // TODO get all these passing
474 // I observed a value of 3398275072 on my M1, and multiplied by 1.1 to
475 // get this amount:
476 .max_rss = 3738102579,
477 }));
478478
479479 try addWasiUpdateStep(b, version);
480
481 b.step("fmt", "Modify source files in place to have conforming formatting")
482 .dependOn(&do_fmt.step);
480483}
481484
482485fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
......@@ -505,6 +508,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
505508 exe_options.addOption(bool, "enable_tracy_callstack", false);
506509 exe_options.addOption(bool, "enable_tracy_allocation", false);
507510 exe_options.addOption(bool, "value_tracing", false);
511 exe_options.addOption(bool, "omit_pkg_fetching_code", true);
508512
509513 const run_opt = b.addSystemCommand(&.{ "wasm-opt", "-Oz", "--enable-bulk-memory" });
510514 run_opt.addArtifactArg(exe);
......@@ -676,10 +680,7 @@ fn addCxxKnownPath(
676680) !void {
677681 if (!std.process.can_spawn)
678682 return error.RequiredLibraryNotFound;
679 const path_padded = try b.exec(&[_][]const u8{
680 ctx.cxx_compiler,
681 b.fmt("-print-file-name={s}", .{objname}),
682 });
683 const path_padded = b.exec(&.{ ctx.cxx_compiler, b.fmt("-print-file-name={s}", .{objname}) });
683684 var tokenizer = mem.tokenize(u8, path_padded, "\r\n");
684685 const path_unpadded = tokenizer.next().?;
685686 if (mem.eql(u8, path_unpadded, objname)) {
ci/aarch64-linux-debug.sh+1-1
......@@ -67,7 +67,7 @@ stage3-debug/bin/zig build test docs \
6767 --zig-lib-dir "$(pwd)/../lib"
6868
6969# Look for HTML errors.
70tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html"
70tidy --drop-empty-elements no -qe "stage3-debug/doc/langref.html"
7171
7272# Produce the experimental std lib documentation.
7373stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
ci/aarch64-linux-release.sh+1-1
......@@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \
6767 --zig-lib-dir "$(pwd)/../lib"
6868
6969# Look for HTML errors.
70tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html"
70tidy --drop-empty-elements no -qe "stage3-release/doc/langref.html"
7171
7272# Produce the experimental std lib documentation.
7373stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
ci/x86_64-linux-debug.sh+1-1
......@@ -66,7 +66,7 @@ stage3-debug/bin/zig build test docs \
6666 --zig-lib-dir "$(pwd)/../lib"
6767
6868# Look for HTML errors.
69tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html"
69tidy --drop-empty-elements no -qe "stage3-debug/doc/langref.html"
7070
7171# Produce the experimental std lib documentation.
7272stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
ci/x86_64-linux-release.sh+1-1
......@@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \
6767 --zig-lib-dir "$(pwd)/../lib"
6868
6969# Look for HTML errors.
70tidy --drop-empty-elements no -qe "$ZIG_LOCAL_CACHE_DIR/langref.html"
70tidy --drop-empty-elements no -qe "stage3-release/doc/langref.html"
7171
7272# Produce the experimental std lib documentation.
7373stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib
doc/docgen.zig+1-1
......@@ -1270,7 +1270,7 @@ fn genHtml(
12701270 zig_exe: []const u8,
12711271 do_code_tests: bool,
12721272) !void {
1273 var progress = Progress{};
1273 var progress = Progress{ .dont_print_on_dumb = true };
12741274 const root_node = progress.start("Generating docgen examples", toc.nodes.len);
12751275 defer root_node.end();
12761276
lib/build_runner.zig+702-53
......@@ -1,12 +1,14 @@
11const root = @import("@build");
22const std = @import("std");
33const builtin = @import("builtin");
4const assert = std.debug.assert;
45const io = std.io;
56const fmt = std.fmt;
67const mem = std.mem;
78const process = std.process;
89const ArrayList = std.ArrayList;
910const File = std.fs.File;
11const Step = std.Build.Step;
1012
1113pub const dependencies = @import("@dependencies");
1214
......@@ -14,12 +16,15 @@ pub fn main() !void {
1416 // Here we use an ArenaAllocator backed by a DirectAllocator because a build is a short-lived,
1517 // one shot program. We don't need to waste time freeing memory and finding places to squish
1618 // bytes into. So we free everything all at once at the very end.
17 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
18 defer arena.deinit();
19 var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
20 defer single_threaded_arena.deinit();
1921
20 const allocator = arena.allocator();
21 var args = try process.argsAlloc(allocator);
22 defer process.argsFree(allocator, args);
22 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
23 .child_allocator = single_threaded_arena.allocator(),
24 };
25 const arena = thread_safe_arena.allocator();
26
27 var args = try process.argsAlloc(arena);
2328
2429 // skip my own exe name
2530 var arg_idx: usize = 1;
......@@ -59,18 +64,17 @@ pub fn main() !void {
5964 };
6065
6166 var cache: std.Build.Cache = .{
62 .gpa = allocator,
67 .gpa = arena,
6368 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
6469 };
6570 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
6671 cache.addPrefix(build_root_directory);
6772 cache.addPrefix(local_cache_directory);
6873 cache.addPrefix(global_cache_directory);
69
70 //cache.hash.addBytes(builtin.zig_version);
74 cache.hash.addBytes(builtin.zig_version_string);
7175
7276 const builder = try std.Build.create(
73 allocator,
77 arena,
7478 zig_exe,
7579 build_root_directory,
7680 local_cache_directory,
......@@ -80,35 +84,34 @@ pub fn main() !void {
8084 );
8185 defer builder.destroy();
8286
83 var targets = ArrayList([]const u8).init(allocator);
84 var debug_log_scopes = ArrayList([]const u8).init(allocator);
85
86 const stderr_stream = io.getStdErr().writer();
87 const stdout_stream = io.getStdOut().writer();
87 var targets = ArrayList([]const u8).init(arena);
88 var debug_log_scopes = ArrayList([]const u8).init(arena);
89 var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena };
8890
8991 var install_prefix: ?[]const u8 = null;
9092 var dir_list = std.Build.DirList{};
93 var enable_summary: ?bool = null;
94 var max_rss: usize = 0;
95 var color: Color = .auto;
9196
92 // before arg parsing, check for the NO_COLOR environment variable
93 // if it exists, default the color setting to .off
94 // explicit --color arguments will still override this setting.
95 builder.color = if (std.process.hasEnvVarConstant("NO_COLOR")) .off else .auto;
97 const stderr_stream = io.getStdErr().writer();
98 const stdout_stream = io.getStdOut().writer();
9699
97100 while (nextArg(args, &arg_idx)) |arg| {
98101 if (mem.startsWith(u8, arg, "-D")) {
99102 const option_contents = arg[2..];
100103 if (option_contents.len == 0) {
101104 std.debug.print("Expected option name after '-D'\n\n", .{});
102 return usageAndErr(builder, false, stderr_stream);
105 usageAndErr(builder, false, stderr_stream);
103106 }
104107 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
105108 const option_name = option_contents[0..name_end];
106109 const option_value = option_contents[name_end + 1 ..];
107110 if (try builder.addUserInputOption(option_name, option_value))
108 return usageAndErr(builder, false, stderr_stream);
111 usageAndErr(builder, false, stderr_stream);
109112 } else {
110113 if (try builder.addUserInputFlag(option_contents))
111 return usageAndErr(builder, false, stderr_stream);
114 usageAndErr(builder, false, stderr_stream);
112115 }
113116 } else if (mem.startsWith(u8, arg, "-")) {
114117 if (mem.eql(u8, arg, "--verbose")) {
......@@ -118,69 +121,83 @@ pub fn main() !void {
118121 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
119122 install_prefix = nextArg(args, &arg_idx) orelse {
120123 std.debug.print("Expected argument after {s}\n\n", .{arg});
121 return usageAndErr(builder, false, stderr_stream);
124 usageAndErr(builder, false, stderr_stream);
122125 };
123126 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
124127 return steps(builder, false, stdout_stream);
125128 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
126129 dir_list.lib_dir = nextArg(args, &arg_idx) orelse {
127130 std.debug.print("Expected argument after {s}\n\n", .{arg});
128 return usageAndErr(builder, false, stderr_stream);
131 usageAndErr(builder, false, stderr_stream);
129132 };
130133 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
131134 dir_list.exe_dir = nextArg(args, &arg_idx) orelse {
132135 std.debug.print("Expected argument after {s}\n\n", .{arg});
133 return usageAndErr(builder, false, stderr_stream);
136 usageAndErr(builder, false, stderr_stream);
134137 };
135138 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
136139 dir_list.include_dir = nextArg(args, &arg_idx) orelse {
137140 std.debug.print("Expected argument after {s}\n\n", .{arg});
138 return usageAndErr(builder, false, stderr_stream);
141 usageAndErr(builder, false, stderr_stream);
139142 };
140143 } else if (mem.eql(u8, arg, "--sysroot")) {
141144 const sysroot = nextArg(args, &arg_idx) orelse {
142145 std.debug.print("Expected argument after --sysroot\n\n", .{});
143 return usageAndErr(builder, false, stderr_stream);
146 usageAndErr(builder, false, stderr_stream);
144147 };
145148 builder.sysroot = sysroot;
149 } else if (mem.eql(u8, arg, "--maxrss")) {
150 const max_rss_text = nextArg(args, &arg_idx) orelse {
151 std.debug.print("Expected argument after --sysroot\n\n", .{});
152 usageAndErr(builder, false, stderr_stream);
153 };
154 // TODO: support shorthand such as "2GiB", "2GB", or "2G"
155 max_rss = std.fmt.parseInt(usize, max_rss_text, 10) catch |err| {
156 std.debug.print("invalid byte size: '{s}': {s}\n", .{
157 max_rss_text, @errorName(err),
158 });
159 process.exit(1);
160 };
146161 } else if (mem.eql(u8, arg, "--search-prefix")) {
147162 const search_prefix = nextArg(args, &arg_idx) orelse {
148163 std.debug.print("Expected argument after --search-prefix\n\n", .{});
149 return usageAndErr(builder, false, stderr_stream);
164 usageAndErr(builder, false, stderr_stream);
150165 };
151166 builder.addSearchPrefix(search_prefix);
152167 } else if (mem.eql(u8, arg, "--libc")) {
153168 const libc_file = nextArg(args, &arg_idx) orelse {
154169 std.debug.print("Expected argument after --libc\n\n", .{});
155 return usageAndErr(builder, false, stderr_stream);
170 usageAndErr(builder, false, stderr_stream);
156171 };
157172 builder.libc_file = libc_file;
158173 } else if (mem.eql(u8, arg, "--color")) {
159174 const next_arg = nextArg(args, &arg_idx) orelse {
160175 std.debug.print("expected [auto|on|off] after --color", .{});
161 return usageAndErr(builder, false, stderr_stream);
176 usageAndErr(builder, false, stderr_stream);
162177 };
163 builder.color = std.meta.stringToEnum(@TypeOf(builder.color), next_arg) orelse {
178 color = std.meta.stringToEnum(Color, next_arg) orelse {
164179 std.debug.print("expected [auto|on|off] after --color, found '{s}'", .{next_arg});
165 return usageAndErr(builder, false, stderr_stream);
180 usageAndErr(builder, false, stderr_stream);
166181 };
167182 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
168183 builder.zig_lib_dir = nextArg(args, &arg_idx) orelse {
169184 std.debug.print("Expected argument after --zig-lib-dir\n\n", .{});
170 return usageAndErr(builder, false, stderr_stream);
185 usageAndErr(builder, false, stderr_stream);
171186 };
172187 } else if (mem.eql(u8, arg, "--debug-log")) {
173188 const next_arg = nextArg(args, &arg_idx) orelse {
174189 std.debug.print("Expected argument after {s}\n\n", .{arg});
175 return usageAndErr(builder, false, stderr_stream);
190 usageAndErr(builder, false, stderr_stream);
176191 };
177192 try debug_log_scopes.append(next_arg);
193 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
194 builder.debug_pkg_config = true;
178195 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
179196 builder.debug_compile_errors = true;
180197 } else if (mem.eql(u8, arg, "--glibc-runtimes")) {
181198 builder.glibc_runtimes_dir = nextArg(args, &arg_idx) orelse {
182199 std.debug.print("Expected argument after --glibc-runtimes\n\n", .{});
183 return usageAndErr(builder, false, stderr_stream);
200 usageAndErr(builder, false, stderr_stream);
184201 };
185202 } else if (mem.eql(u8, arg, "--verbose-link")) {
186203 builder.verbose_link = true;
......@@ -194,8 +211,6 @@ pub fn main() !void {
194211 builder.verbose_cc = true;
195212 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
196213 builder.verbose_llvm_cpu_features = true;
197 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
198 builder.prominent_compile_errors = true;
199214 } else if (mem.eql(u8, arg, "-fwine")) {
200215 builder.enable_wine = true;
201216 } else if (mem.eql(u8, arg, "-fno-wine")) {
......@@ -216,6 +231,10 @@ pub fn main() !void {
216231 builder.enable_darling = true;
217232 } else if (mem.eql(u8, arg, "-fno-darling")) {
218233 builder.enable_darling = false;
234 } else if (mem.eql(u8, arg, "-fsummary")) {
235 enable_summary = true;
236 } else if (mem.eql(u8, arg, "-fno-summary")) {
237 enable_summary = false;
219238 } else if (mem.eql(u8, arg, "-freference-trace")) {
220239 builder.reference_trace = 256;
221240 } else if (mem.startsWith(u8, arg, "-freference-trace=")) {
......@@ -226,39 +245,639 @@ pub fn main() !void {
226245 };
227246 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
228247 builder.reference_trace = null;
248 } else if (mem.startsWith(u8, arg, "-j")) {
249 const num = arg["-j".len..];
250 const n_jobs = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
251 std.debug.print("unable to parse jobs count '{s}': {s}", .{
252 num, @errorName(err),
253 });
254 process.exit(1);
255 };
256 if (n_jobs < 1) {
257 std.debug.print("number of jobs must be at least 1\n", .{});
258 process.exit(1);
259 }
260 thread_pool_options.n_jobs = n_jobs;
229261 } else if (mem.eql(u8, arg, "--")) {
230262 builder.args = argsRest(args, arg_idx);
231263 break;
232264 } else {
233265 std.debug.print("Unrecognized argument: {s}\n\n", .{arg});
234 return usageAndErr(builder, false, stderr_stream);
266 usageAndErr(builder, false, stderr_stream);
235267 }
236268 } else {
237269 try targets.append(arg);
238270 }
239271 }
240272
273 const stderr = std.io.getStdErr();
274 const ttyconf = get_tty_conf(color, stderr);
275 switch (ttyconf) {
276 .no_color => try builder.env_map.put("NO_COLOR", "1"),
277 .escape_codes => try builder.env_map.put("ZIG_DEBUG_COLOR", "1"),
278 .windows_api => {},
279 }
280
281 var progress: std.Progress = .{ .dont_print_on_dumb = true };
282 const main_progress_node = progress.start("", 0);
283
241284 builder.debug_log_scopes = debug_log_scopes.items;
242285 builder.resolveInstallPrefix(install_prefix, dir_list);
243 try builder.runBuild(root);
286 {
287 var prog_node = main_progress_node.start("user build.zig logic", 0);
288 defer prog_node.end();
289 try builder.runBuild(root);
290 }
244291
245292 if (builder.validateUserInputDidItFail())
246 return usageAndErr(builder, true, stderr_stream);
293 usageAndErr(builder, true, stderr_stream);
247294
248 builder.make(targets.items) catch |err| {
249 switch (err) {
250 error.InvalidStepName => {
251 return usageAndErr(builder, true, stderr_stream);
295 var run: Run = .{
296 .max_rss = max_rss,
297 .max_rss_is_default = false,
298 .max_rss_mutex = .{},
299 .memory_blocked_steps = std.ArrayList(*Step).init(arena),
300
301 .claimed_rss = 0,
302 .enable_summary = enable_summary,
303 .ttyconf = ttyconf,
304 .stderr = stderr,
305 };
306
307 if (run.max_rss == 0) {
308 run.max_rss = process.totalSystemMemory() catch std.math.maxInt(usize);
309 run.max_rss_is_default = true;
310 }
311
312 runStepNames(
313 arena,
314 builder,
315 targets.items,
316 main_progress_node,
317 thread_pool_options,
318 &run,
319 ) catch |err| switch (err) {
320 error.UncleanExit => process.exit(1),
321 else => return err,
322 };
323}
324
325const Run = struct {
326 max_rss: usize,
327 max_rss_is_default: bool,
328 max_rss_mutex: std.Thread.Mutex,
329 memory_blocked_steps: std.ArrayList(*Step),
330
331 claimed_rss: usize,
332 enable_summary: ?bool,
333 ttyconf: std.debug.TTY.Config,
334 stderr: std.fs.File,
335};
336
337fn runStepNames(
338 arena: std.mem.Allocator,
339 b: *std.Build,
340 step_names: []const []const u8,
341 parent_prog_node: *std.Progress.Node,
342 thread_pool_options: std.Thread.Pool.Options,
343 run: *Run,
344) !void {
345 const gpa = b.allocator;
346 var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
347 defer step_stack.deinit(gpa);
348
349 if (step_names.len == 0) {
350 try step_stack.put(gpa, b.default_step, {});
351 } else {
352 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
353 for (0..step_names.len) |i| {
354 const step_name = step_names[step_names.len - i - 1];
355 const s = b.top_level_steps.get(step_name) orelse {
356 std.debug.print("no step named '{s}'. Access the help menu with 'zig build -h'\n", .{step_name});
357 process.exit(1);
358 };
359 step_stack.putAssumeCapacity(&s.step, {});
360 }
361 }
362
363 const starting_steps = try arena.dupe(*Step, step_stack.keys());
364 for (starting_steps) |s| {
365 checkForDependencyLoop(b, s, &step_stack) catch |err| switch (err) {
366 error.DependencyLoopDetected => return error.UncleanExit,
367 else => |e| return e,
368 };
369 }
370
371 {
372 // Check that we have enough memory to complete the build.
373 var any_problems = false;
374 for (step_stack.keys()) |s| {
375 if (s.max_rss == 0) continue;
376 if (s.max_rss > run.max_rss) {
377 std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{
378 s.owner.dep_prefix, s.name, s.max_rss, run.max_rss,
379 });
380 any_problems = true;
381 }
382 }
383 if (any_problems) {
384 if (run.max_rss_is_default) {
385 std.debug.print("note: use --maxrss to override the default", .{});
386 }
387 return error.UncleanExit;
388 }
389 }
390
391 var thread_pool: std.Thread.Pool = undefined;
392 try thread_pool.init(thread_pool_options);
393 defer thread_pool.deinit();
394
395 {
396 defer parent_prog_node.end();
397
398 var step_prog = parent_prog_node.start("steps", step_stack.count());
399 defer step_prog.end();
400
401 var wait_group: std.Thread.WaitGroup = .{};
402 defer wait_group.wait();
403
404 // Here we spawn the initial set of tasks with a nice heuristic -
405 // dependency order. Each worker when it finishes a step will then
406 // check whether it should run any dependants.
407 const steps_slice = step_stack.keys();
408 for (0..steps_slice.len) |i| {
409 const step = steps_slice[steps_slice.len - i - 1];
410
411 wait_group.start();
412 thread_pool.spawn(workerMakeOneStep, .{
413 &wait_group, &thread_pool, b, step, &step_prog, run,
414 }) catch @panic("OOM");
415 }
416 }
417 assert(run.memory_blocked_steps.items.len == 0);
418
419 var test_skip_count: usize = 0;
420 var test_fail_count: usize = 0;
421 var test_pass_count: usize = 0;
422 var test_leak_count: usize = 0;
423 var test_count: usize = 0;
424
425 var success_count: usize = 0;
426 var skipped_count: usize = 0;
427 var failure_count: usize = 0;
428 var pending_count: usize = 0;
429 var total_compile_errors: usize = 0;
430 var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{};
431 defer compile_error_steps.deinit(gpa);
432
433 for (step_stack.keys()) |s| {
434 test_fail_count += s.test_results.fail_count;
435 test_skip_count += s.test_results.skip_count;
436 test_leak_count += s.test_results.leak_count;
437 test_pass_count += s.test_results.passCount();
438 test_count += s.test_results.test_count;
439
440 switch (s.state) {
441 .precheck_unstarted => unreachable,
442 .precheck_started => unreachable,
443 .running => unreachable,
444 .precheck_done => {
445 // precheck_done is equivalent to dependency_failure in the case of
446 // transitive dependencies. For example:
447 // A -> B -> C (failure)
448 // B will be marked as dependency_failure, while A may never be queued, and thus
449 // remain in the initial state of precheck_done.
450 s.state = .dependency_failure;
451 pending_count += 1;
452 },
453 .dependency_failure => pending_count += 1,
454 .success => success_count += 1,
455 .skipped => skipped_count += 1,
456 .failure => {
457 failure_count += 1;
458 const compile_errors_len = s.result_error_bundle.errorMessageCount();
459 if (compile_errors_len > 0) {
460 total_compile_errors += compile_errors_len;
461 try compile_error_steps.append(gpa, s);
462 }
252463 },
253 error.UncleanExit => process.exit(1),
254 // This error is intended to indicate that the step has already
255 // logged an error message and so printing the error return trace
256 // here would be unwanted extra information, unless the user opts
257 // into it with a debug flag.
258 error.StepFailed => process.exit(1),
259 else => return err,
260464 }
261 };
465 }
466
467 // A proper command line application defaults to silently succeeding.
468 // The user may request verbose mode if they have a different preference.
469 if (failure_count == 0 and run.enable_summary != true) return cleanExit();
470
471 const ttyconf = run.ttyconf;
472 const stderr = run.stderr;
473
474 if (run.enable_summary != false) {
475 const total_count = success_count + failure_count + pending_count + skipped_count;
476 ttyconf.setColor(stderr, .Cyan) catch {};
477 stderr.writeAll("Build Summary:") catch {};
478 ttyconf.setColor(stderr, .Reset) catch {};
479 stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
480 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
481 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
482
483 if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
484 if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {};
485 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
486 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
487
488 if (run.enable_summary == null) {
489 ttyconf.setColor(stderr, .Dim) catch {};
490 stderr.writeAll(" (disable with -fno-summary)") catch {};
491 ttyconf.setColor(stderr, .Reset) catch {};
492 }
493 stderr.writeAll("\n") catch {};
494
495 // Print a fancy tree with build results.
496 var print_node: PrintNode = .{ .parent = null };
497 if (step_names.len == 0) {
498 print_node.last = true;
499 printTreeStep(b, b.default_step, stderr, ttyconf, &print_node, &step_stack) catch {};
500 } else {
501 for (step_names, 0..) |step_name, i| {
502 const tls = b.top_level_steps.get(step_name).?;
503 print_node.last = i + 1 == b.top_level_steps.count();
504 printTreeStep(b, &tls.step, stderr, ttyconf, &print_node, &step_stack) catch {};
505 }
506 }
507 }
508
509 if (failure_count == 0) return cleanExit();
510
511 // Finally, render compile errors at the bottom of the terminal.
512 // We use a separate compile_error_steps array list because step_stack is destructively
513 // mutated in printTreeStep above.
514 if (total_compile_errors > 0) {
515 for (compile_error_steps.items) |s| {
516 if (s.result_error_bundle.errorMessageCount() > 0) {
517 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
518 }
519 }
520
521 // Signal to parent process that we have printed compile errors. The
522 // parent process may choose to omit the "following command failed"
523 // line in this case.
524 process.exit(2);
525 }
526
527 process.exit(1);
528}
529
530const PrintNode = struct {
531 parent: ?*PrintNode,
532 last: bool = false,
533};
534
535fn printPrefix(node: *PrintNode, stderr: std.fs.File, ttyconf: std.debug.TTY.Config) !void {
536 const parent = node.parent orelse return;
537 if (parent.parent == null) return;
538 try printPrefix(parent, stderr, ttyconf);
539 if (parent.last) {
540 try stderr.writeAll(" ");
541 } else {
542 try stderr.writeAll(switch (ttyconf) {
543 .no_color, .windows_api => "| ",
544 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
545 });
546 }
547}
548
549fn printTreeStep(
550 b: *std.Build,
551 s: *Step,
552 stderr: std.fs.File,
553 ttyconf: std.debug.TTY.Config,
554 parent_node: *PrintNode,
555 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
556) !void {
557 const first = step_stack.swapRemove(s);
558 try printPrefix(parent_node, stderr, ttyconf);
559
560 if (!first) try ttyconf.setColor(stderr, .Dim);
561 if (parent_node.parent != null) {
562 if (parent_node.last) {
563 try stderr.writeAll(switch (ttyconf) {
564 .no_color, .windows_api => "+- ",
565 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
566 });
567 } else {
568 try stderr.writeAll(switch (ttyconf) {
569 .no_color, .windows_api => "+- ",
570 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
571 });
572 }
573 }
574
575 // dep_prefix omitted here because it is redundant with the tree.
576 try stderr.writeAll(s.name);
577
578 if (first) {
579 switch (s.state) {
580 .precheck_unstarted => unreachable,
581 .precheck_started => unreachable,
582 .precheck_done => unreachable,
583 .running => unreachable,
584
585 .dependency_failure => {
586 try ttyconf.setColor(stderr, .Dim);
587 try stderr.writeAll(" transitive failure\n");
588 try ttyconf.setColor(stderr, .Reset);
589 },
590
591 .success => {
592 try ttyconf.setColor(stderr, .Green);
593 if (s.result_cached) {
594 try stderr.writeAll(" cached");
595 } else if (s.test_results.test_count > 0) {
596 const pass_count = s.test_results.passCount();
597 try stderr.writer().print(" {d} passed", .{pass_count});
598 if (s.test_results.skip_count > 0) {
599 try ttyconf.setColor(stderr, .Yellow);
600 try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
601 }
602 } else {
603 try stderr.writeAll(" success");
604 }
605 try ttyconf.setColor(stderr, .Reset);
606 if (s.result_duration_ns) |ns| {
607 try ttyconf.setColor(stderr, .Dim);
608 if (ns >= std.time.ns_per_min) {
609 try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min});
610 } else if (ns >= std.time.ns_per_s) {
611 try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s});
612 } else if (ns >= std.time.ns_per_ms) {
613 try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms});
614 } else if (ns >= std.time.ns_per_us) {
615 try stderr.writer().print(" {d}us", .{ns / std.time.ns_per_us});
616 } else {
617 try stderr.writer().print(" {d}ns", .{ns});
618 }
619 try ttyconf.setColor(stderr, .Reset);
620 }
621 if (s.result_peak_rss != 0) {
622 const rss = s.result_peak_rss;
623 try ttyconf.setColor(stderr, .Dim);
624 if (rss >= 1000_000_000) {
625 try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000});
626 } else if (rss >= 1000_000) {
627 try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000});
628 } else if (rss >= 1000) {
629 try stderr.writer().print(" MaxRSS:{d}K", .{rss / 1000});
630 } else {
631 try stderr.writer().print(" MaxRSS:{d}B", .{rss});
632 }
633 try ttyconf.setColor(stderr, .Reset);
634 }
635 try stderr.writeAll("\n");
636 },
637
638 .skipped => {
639 try ttyconf.setColor(stderr, .Yellow);
640 try stderr.writeAll(" skipped\n");
641 try ttyconf.setColor(stderr, .Reset);
642 },
643
644 .failure => {
645 if (s.result_error_bundle.errorMessageCount() > 0) {
646 try ttyconf.setColor(stderr, .Red);
647 try stderr.writer().print(" {d} errors\n", .{
648 s.result_error_bundle.errorMessageCount(),
649 });
650 try ttyconf.setColor(stderr, .Reset);
651 } else if (!s.test_results.isSuccess()) {
652 try stderr.writer().print(" {d}/{d} passed", .{
653 s.test_results.passCount(), s.test_results.test_count,
654 });
655 if (s.test_results.fail_count > 0) {
656 try stderr.writeAll(", ");
657 try ttyconf.setColor(stderr, .Red);
658 try stderr.writer().print("{d} failed", .{
659 s.test_results.fail_count,
660 });
661 try ttyconf.setColor(stderr, .Reset);
662 }
663 if (s.test_results.skip_count > 0) {
664 try stderr.writeAll(", ");
665 try ttyconf.setColor(stderr, .Yellow);
666 try stderr.writer().print("{d} skipped", .{
667 s.test_results.skip_count,
668 });
669 try ttyconf.setColor(stderr, .Reset);
670 }
671 if (s.test_results.leak_count > 0) {
672 try stderr.writeAll(", ");
673 try ttyconf.setColor(stderr, .Red);
674 try stderr.writer().print("{d} leaked", .{
675 s.test_results.leak_count,
676 });
677 try ttyconf.setColor(stderr, .Reset);
678 }
679 try stderr.writeAll("\n");
680 } else {
681 try ttyconf.setColor(stderr, .Red);
682 try stderr.writeAll(" failure\n");
683 try ttyconf.setColor(stderr, .Reset);
684 }
685 },
686 }
687
688 for (s.dependencies.items, 0..) |dep, i| {
689 var print_node: PrintNode = .{
690 .parent = parent_node,
691 .last = i == s.dependencies.items.len - 1,
692 };
693 try printTreeStep(b, dep, stderr, ttyconf, &print_node, step_stack);
694 }
695 } else {
696 if (s.dependencies.items.len == 0) {
697 try stderr.writeAll(" (reused)\n");
698 } else {
699 try stderr.writer().print(" (+{d} more reused dependencies)\n", .{
700 s.dependencies.items.len,
701 });
702 }
703 try ttyconf.setColor(stderr, .Reset);
704 }
705}
706
707fn checkForDependencyLoop(
708 b: *std.Build,
709 s: *Step,
710 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
711) !void {
712 switch (s.state) {
713 .precheck_started => {
714 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
715 return error.DependencyLoopDetected;
716 },
717 .precheck_unstarted => {
718 s.state = .precheck_started;
719
720 try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len);
721 for (s.dependencies.items) |dep| {
722 try step_stack.put(b.allocator, dep, {});
723 try dep.dependants.append(b.allocator, s);
724 checkForDependencyLoop(b, dep, step_stack) catch |err| {
725 if (err == error.DependencyLoopDetected) {
726 std.debug.print(" {s}\n", .{s.name});
727 }
728 return err;
729 };
730 }
731
732 s.state = .precheck_done;
733 },
734 .precheck_done => {},
735
736 // These don't happen until we actually run the step graph.
737 .dependency_failure => unreachable,
738 .running => unreachable,
739 .success => unreachable,
740 .failure => unreachable,
741 .skipped => unreachable,
742 }
743}
744
745fn workerMakeOneStep(
746 wg: *std.Thread.WaitGroup,
747 thread_pool: *std.Thread.Pool,
748 b: *std.Build,
749 s: *Step,
750 prog_node: *std.Progress.Node,
751 run: *Run,
752) void {
753 defer wg.finish();
754
755 // First, check the conditions for running this step. If they are not met,
756 // then we return without doing the step, relying on another worker to
757 // queue this step up again when dependencies are met.
758 for (s.dependencies.items) |dep| {
759 switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) {
760 .success, .skipped => continue,
761 .failure, .dependency_failure => {
762 @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst);
763 return;
764 },
765 .precheck_done, .running => {
766 // dependency is not finished yet.
767 return;
768 },
769 .precheck_unstarted => unreachable,
770 .precheck_started => unreachable,
771 }
772 }
773
774 if (s.max_rss != 0) {
775 run.max_rss_mutex.lock();
776 defer run.max_rss_mutex.unlock();
777
778 // Avoid running steps twice.
779 if (s.state != .precheck_done) {
780 // Another worker got the job.
781 return;
782 }
783
784 const new_claimed_rss = run.claimed_rss + s.max_rss;
785 if (new_claimed_rss > run.max_rss) {
786 // Running this step right now could possibly exceed the allotted RSS.
787 // Add this step to the queue of memory-blocked steps.
788 run.memory_blocked_steps.append(s) catch @panic("OOM");
789 return;
790 }
791
792 run.claimed_rss = new_claimed_rss;
793 s.state = .running;
794 } else {
795 // Avoid running steps twice.
796 if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) {
797 // Another worker got the job.
798 return;
799 }
800 }
801
802 var sub_prog_node = prog_node.start(s.name, 0);
803 sub_prog_node.activate();
804 defer sub_prog_node.end();
805
806 const make_result = s.make(&sub_prog_node);
807
808 // No matter the result, we want to display error/warning messages.
809 if (s.result_error_msgs.items.len > 0) {
810 sub_prog_node.context.lock_stderr();
811 defer sub_prog_node.context.unlock_stderr();
812
813 const stderr = run.stderr;
814 const ttyconf = run.ttyconf;
815
816 for (s.result_error_msgs.items) |msg| {
817 // Sometimes it feels like you just can't catch a break. Finally,
818 // with Zig, you can.
819 ttyconf.setColor(stderr, .Bold) catch break;
820 stderr.writeAll(s.owner.dep_prefix) catch break;
821 stderr.writeAll(s.name) catch break;
822 stderr.writeAll(": ") catch break;
823 ttyconf.setColor(stderr, .Red) catch break;
824 stderr.writeAll("error: ") catch break;
825 ttyconf.setColor(stderr, .Reset) catch break;
826 stderr.writeAll(msg) catch break;
827 stderr.writeAll("\n") catch break;
828 }
829 }
830
831 handle_result: {
832 if (make_result) |_| {
833 @atomicStore(Step.State, &s.state, .success, .SeqCst);
834 } else |err| switch (err) {
835 error.MakeFailed => {
836 @atomicStore(Step.State, &s.state, .failure, .SeqCst);
837 break :handle_result;
838 },
839 error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst),
840 }
841
842 // Successful completion of a step, so we queue up its dependants as well.
843 for (s.dependants.items) |dep| {
844 wg.start();
845 thread_pool.spawn(workerMakeOneStep, .{
846 wg, thread_pool, b, dep, prog_node, run,
847 }) catch @panic("OOM");
848 }
849 }
850
851 // If this is a step that claims resources, we must now queue up other
852 // steps that are waiting for resources.
853 if (s.max_rss != 0) {
854 run.max_rss_mutex.lock();
855 defer run.max_rss_mutex.unlock();
856
857 // Give the memory back to the scheduler.
858 run.claimed_rss -= s.max_rss;
859 // Avoid kicking off too many tasks that we already know will not have
860 // enough resources.
861 var remaining = run.max_rss - run.claimed_rss;
862 var i: usize = 0;
863 var j: usize = 0;
864 while (j < run.memory_blocked_steps.items.len) : (j += 1) {
865 const dep = run.memory_blocked_steps.items[j];
866 assert(dep.max_rss != 0);
867 if (dep.max_rss <= remaining) {
868 remaining -= dep.max_rss;
869
870 wg.start();
871 thread_pool.spawn(workerMakeOneStep, .{
872 wg, thread_pool, b, dep, prog_node, run,
873 }) catch @panic("OOM");
874 } else {
875 run.memory_blocked_steps.items[i] = dep;
876 i += 1;
877 }
878 }
879 run.memory_blocked_steps.shrinkRetainingCapacity(i);
880 }
262881}
263882
264883fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void {
......@@ -269,7 +888,7 @@ fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
269888 }
270889
271890 const allocator = builder.allocator;
272 for (builder.top_level_steps.items) |top_level_step| {
891 for (builder.top_level_steps.values()) |top_level_step| {
273892 const name = if (&top_level_step.step == builder.default_step)
274893 try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
275894 else
......@@ -327,6 +946,10 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
327946 \\ --verbose Print commands before executing them
328947 \\ --color [auto|off|on] Enable or disable colored error messages
329948 \\ --prominent-compile-errors Output compile errors formatted for a human to read
949 \\ -fsummary Print the build summary, even on success
950 \\ -fno-summary Omit the build summary, even on failure
951 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
952 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
330953 \\
331954 \\Project-Specific Options:
332955 \\
......@@ -364,6 +987,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
364987 \\ --zig-lib-dir [arg] Override path to Zig lib directory
365988 \\ --build-runner [file] Override path to build runner
366989 \\ --debug-log [scope] Enable debugging the compiler
990 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
367991 \\ --verbose-link Enable compiler debug output for linking
368992 \\ --verbose-air Enable compiler debug output for Zig AIR
369993 \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR
......@@ -374,7 +998,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
374998 );
375999}
3761000
377fn usageAndErr(builder: *std.Build, already_ran_build: bool, out_stream: anytype) void {
1001fn usageAndErr(builder: *std.Build, already_ran_build: bool, out_stream: anytype) noreturn {
3781002 usage(builder, already_ran_build, out_stream) catch {};
3791003 process.exit(1);
3801004}
......@@ -389,3 +1013,28 @@ fn argsRest(args: [][]const u8, idx: usize) ?[][]const u8 {
3891013 if (idx >= args.len) return null;
3901014 return args[idx..];
3911015}
1016
1017fn cleanExit() void {
1018 // Perhaps in the future there could be an Advanced Options flag such as
1019 // --debug-build-runner-leaks which would make this function return instead
1020 // of calling exit.
1021 process.exit(0);
1022}
1023
1024const Color = enum { auto, off, on };
1025
1026fn get_tty_conf(color: Color, stderr: std.fs.File) std.debug.TTY.Config {
1027 return switch (color) {
1028 .auto => std.debug.detectTTYConfig(stderr),
1029 .on => .escape_codes,
1030 .off => .no_color,
1031 };
1032}
1033
1034fn renderOptions(ttyconf: std.debug.TTY.Config) std.zig.ErrorBundle.RenderOptions {
1035 return .{
1036 .ttyconf = ttyconf,
1037 .include_source_line = ttyconf != .no_color,
1038 .include_reference_trace = ttyconf != .no_color,
1039 };
1040}
lib/std/Build.zig+233-232
......@@ -32,14 +32,12 @@ pub const Step = @import("Build/Step.zig");
3232pub const CheckFileStep = @import("Build/CheckFileStep.zig");
3333pub const CheckObjectStep = @import("Build/CheckObjectStep.zig");
3434pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig");
35pub const EmulatableRunStep = @import("Build/EmulatableRunStep.zig");
3635pub const FmtStep = @import("Build/FmtStep.zig");
3736pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig");
3837pub const InstallDirStep = @import("Build/InstallDirStep.zig");
3938pub const InstallFileStep = @import("Build/InstallFileStep.zig");
4039pub const ObjCopyStep = @import("Build/ObjCopyStep.zig");
4140pub const CompileStep = @import("Build/CompileStep.zig");
42pub const LogStep = @import("Build/LogStep.zig");
4341pub const OptionsStep = @import("Build/OptionsStep.zig");
4442pub const RemoveDirStep = @import("Build/RemoveDirStep.zig");
4543pub const RunStep = @import("Build/RunStep.zig");
......@@ -59,15 +57,12 @@ verbose_air: bool,
5957verbose_llvm_ir: bool,
6058verbose_cimport: bool,
6159verbose_llvm_cpu_features: bool,
62/// The purpose of executing the command is for a human to read compile errors from the terminal
63prominent_compile_errors: bool,
64color: enum { auto, on, off } = .auto,
6560reference_trace: ?u32 = null,
6661invalid_user_input: bool,
6762zig_exe: []const u8,
6863default_step: *Step,
6964env_map: *EnvMap,
70top_level_steps: ArrayList(*TopLevelStep),
65top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep),
7166install_prefix: []const u8,
7267dest_dir: ?[]const u8,
7368lib_dir: []const u8,
......@@ -90,6 +85,7 @@ pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
9085args: ?[][]const u8 = null,
9186debug_log_scopes: []const []const u8 = &.{},
9287debug_compile_errors: bool = false,
88debug_pkg_config: bool = false,
9389
9490/// Experimental. Use system Darling installation to run cross compiled macOS build artifacts.
9591enable_darling: bool = false,
......@@ -198,7 +194,7 @@ pub fn create(
198194 env_map.* = try process.getEnvMap(allocator);
199195
200196 const self = try allocator.create(Build);
201 self.* = Build{
197 self.* = .{
202198 .zig_exe = zig_exe,
203199 .build_root = build_root,
204200 .cache_root = cache_root,
......@@ -211,13 +207,12 @@ pub fn create(
211207 .verbose_llvm_ir = false,
212208 .verbose_cimport = false,
213209 .verbose_llvm_cpu_features = false,
214 .prominent_compile_errors = false,
215210 .invalid_user_input = false,
216211 .allocator = allocator,
217212 .user_input_options = UserInputOptionsMap.init(allocator),
218213 .available_options_map = AvailableOptionsMap.init(allocator),
219214 .available_options_list = ArrayList(AvailableOption).init(allocator),
220 .top_level_steps = ArrayList(*TopLevelStep).init(allocator),
215 .top_level_steps = .{},
221216 .default_step = undefined,
222217 .env_map = env_map,
223218 .search_prefixes = ArrayList([]const u8).init(allocator),
......@@ -227,12 +222,21 @@ pub fn create(
227222 .h_dir = undefined,
228223 .dest_dir = env_map.get("DESTDIR"),
229224 .installed_files = ArrayList(InstalledFile).init(allocator),
230 .install_tls = TopLevelStep{
231 .step = Step.initNoOp(.top_level, "install", allocator),
225 .install_tls = .{
226 .step = Step.init(.{
227 .id = .top_level,
228 .name = "install",
229 .owner = self,
230 }),
232231 .description = "Copy build artifacts to prefix path",
233232 },
234 .uninstall_tls = TopLevelStep{
235 .step = Step.init(.top_level, "uninstall", allocator, makeUninstall),
233 .uninstall_tls = .{
234 .step = Step.init(.{
235 .id = .top_level,
236 .name = "uninstall",
237 .owner = self,
238 .makeFn = makeUninstall,
239 }),
236240 .description = "Remove build artifacts from prefix path",
237241 },
238242 .zig_lib_dir = null,
......@@ -241,8 +245,8 @@ pub fn create(
241245 .host = host,
242246 .modules = std.StringArrayHashMap(*Module).init(allocator),
243247 };
244 try self.top_level_steps.append(&self.install_tls);
245 try self.top_level_steps.append(&self.uninstall_tls);
248 try self.top_level_steps.put(allocator, self.install_tls.step.name, &self.install_tls);
249 try self.top_level_steps.put(allocator, self.uninstall_tls.step.name, &self.uninstall_tls);
246250 self.default_step = &self.install_tls.step;
247251 return self;
248252}
......@@ -264,11 +268,20 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
264268 child.* = .{
265269 .allocator = allocator,
266270 .install_tls = .{
267 .step = Step.initNoOp(.top_level, "install", allocator),
271 .step = Step.init(.{
272 .id = .top_level,
273 .name = "install",
274 .owner = child,
275 }),
268276 .description = "Copy build artifacts to prefix path",
269277 },
270278 .uninstall_tls = .{
271 .step = Step.init(.top_level, "uninstall", allocator, makeUninstall),
279 .step = Step.init(.{
280 .id = .top_level,
281 .name = "uninstall",
282 .owner = child,
283 .makeFn = makeUninstall,
284 }),
272285 .description = "Remove build artifacts from prefix path",
273286 },
274287 .user_input_options = UserInputOptionsMap.init(allocator),
......@@ -281,14 +294,12 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
281294 .verbose_llvm_ir = parent.verbose_llvm_ir,
282295 .verbose_cimport = parent.verbose_cimport,
283296 .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,
284 .prominent_compile_errors = parent.prominent_compile_errors,
285 .color = parent.color,
286297 .reference_trace = parent.reference_trace,
287298 .invalid_user_input = false,
288299 .zig_exe = parent.zig_exe,
289300 .default_step = undefined,
290301 .env_map = parent.env_map,
291 .top_level_steps = ArrayList(*TopLevelStep).init(allocator),
302 .top_level_steps = .{},
292303 .install_prefix = undefined,
293304 .dest_dir = parent.dest_dir,
294305 .lib_dir = parent.lib_dir,
......@@ -306,6 +317,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
306317 .zig_lib_dir = parent.zig_lib_dir,
307318 .debug_log_scopes = parent.debug_log_scopes,
308319 .debug_compile_errors = parent.debug_compile_errors,
320 .debug_pkg_config = parent.debug_pkg_config,
309321 .enable_darling = parent.enable_darling,
310322 .enable_qemu = parent.enable_qemu,
311323 .enable_rosetta = parent.enable_rosetta,
......@@ -316,8 +328,8 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
316328 .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),
317329 .modules = std.StringArrayHashMap(*Module).init(allocator),
318330 };
319 try child.top_level_steps.append(&child.install_tls);
320 try child.top_level_steps.append(&child.uninstall_tls);
331 try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls);
332 try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls);
321333 child.default_step = &child.install_tls.step;
322334 return child;
323335}
......@@ -372,27 +384,24 @@ fn applyArgs(b: *Build, args: anytype) !void {
372384 },
373385 }
374386 }
375 const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);
387
388 // Create an installation directory local to this package. This will be used when
389 // dependant packages require a standard prefix, such as include directories for C headers.
390 var hash = b.cache.hash;
376391 // Random bytes to make unique. Refresh this with new random bytes when
377392 // implementation is modified in a non-backwards-compatible way.
378 var hash = Hasher.init("ZaEsvQ5ClaA2IdH9");
379 hash.update(b.dep_prefix);
393 hash.add(@as(u32, 0xd8cb0055));
394 hash.addBytes(b.dep_prefix);
380395 // TODO additionally update the hash with `args`.
381
382 var digest: [16]u8 = undefined;
383 hash.final(&digest);
384 var hash_basename: [digest.len * 2]u8 = undefined;
385 _ = std.fmt.bufPrint(&hash_basename, "{s}", .{std.fmt.fmtSliceHexLower(&digest)}) catch
386 unreachable;
387
388 const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &hash_basename });
396 const digest = hash.final();
397 const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });
389398 b.resolveInstallPrefix(install_prefix, .{});
390399}
391400
392pub fn destroy(self: *Build) void {
393 self.env_map.deinit();
394 self.top_level_steps.deinit();
395 self.allocator.destroy(self);
401pub fn destroy(b: *Build) void {
402 b.env_map.deinit();
403 b.top_level_steps.deinit(b.allocator);
404 b.allocator.destroy(b);
396405}
397406
398407/// This function is intended to be called by lib/build_runner.zig, not a build.zig file.
......@@ -441,6 +450,7 @@ pub const ExecutableOptions = struct {
441450 target: CrossTarget = .{},
442451 optimize: std.builtin.Mode = .Debug,
443452 linkage: ?CompileStep.Linkage = null,
453 max_rss: usize = 0,
444454};
445455
446456pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {
......@@ -452,6 +462,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {
452462 .optimize = options.optimize,
453463 .kind = .exe,
454464 .linkage = options.linkage,
465 .max_rss = options.max_rss,
455466 });
456467}
457468
......@@ -460,6 +471,7 @@ pub const ObjectOptions = struct {
460471 root_source_file: ?FileSource = null,
461472 target: CrossTarget,
462473 optimize: std.builtin.Mode,
474 max_rss: usize = 0,
463475};
464476
465477pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {
......@@ -469,6 +481,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {
469481 .target = options.target,
470482 .optimize = options.optimize,
471483 .kind = .obj,
484 .max_rss = options.max_rss,
472485 });
473486}
474487
......@@ -478,6 +491,7 @@ pub const SharedLibraryOptions = struct {
478491 version: ?std.builtin.Version = null,
479492 target: CrossTarget,
480493 optimize: std.builtin.Mode,
494 max_rss: usize = 0,
481495};
482496
483497pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {
......@@ -489,6 +503,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {
489503 .version = options.version,
490504 .target = options.target,
491505 .optimize = options.optimize,
506 .max_rss = options.max_rss,
492507 });
493508}
494509
......@@ -498,6 +513,7 @@ pub const StaticLibraryOptions = struct {
498513 target: CrossTarget,
499514 optimize: std.builtin.Mode,
500515 version: ?std.builtin.Version = null,
516 max_rss: usize = 0,
501517};
502518
503519pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
......@@ -509,25 +525,27 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
509525 .version = options.version,
510526 .target = options.target,
511527 .optimize = options.optimize,
528 .max_rss = options.max_rss,
512529 });
513530}
514531
515532pub const TestOptions = struct {
516533 name: []const u8 = "test",
517 kind: CompileStep.Kind = .@"test",
518534 root_source_file: FileSource,
519535 target: CrossTarget = .{},
520536 optimize: std.builtin.Mode = .Debug,
521537 version: ?std.builtin.Version = null,
538 max_rss: usize = 0,
522539};
523540
524541pub fn addTest(b: *Build, options: TestOptions) *CompileStep {
525542 return CompileStep.create(b, .{
526543 .name = options.name,
527 .kind = options.kind,
544 .kind = .@"test",
528545 .root_source_file = options.root_source_file,
529546 .target = options.target,
530547 .optimize = options.optimize,
548 .max_rss = options.max_rss,
531549 });
532550}
533551
......@@ -536,6 +554,7 @@ pub const AssemblyOptions = struct {
536554 source_file: FileSource,
537555 target: CrossTarget,
538556 optimize: std.builtin.Mode,
557 max_rss: usize = 0,
539558};
540559
541560pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
......@@ -545,6 +564,7 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
545564 .root_source_file = null,
546565 .target = options.target,
547566 .optimize = options.optimize,
567 .max_rss = options.max_rss,
548568 });
549569 obj_step.addAssemblyFileSource(options.source_file.dupe(b));
550570 return obj_step;
......@@ -605,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep {
605625/// Creates a `RunStep` with an executable built with `addExecutable`.
606626/// Add command line arguments with methods of `RunStep`.
607627pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep {
608 assert(exe.kind == .exe or exe.kind == .test_exe);
609
610628 // It doesn't have to be native. We catch that if you actually try to run it.
611629 // Consider that this is declarative; the run step may not be run unless a user
612630 // option is supplied.
613 const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.step.name}));
631 const run_step = RunStep.create(b, b.fmt("run {s}", .{exe.name}));
614632 run_step.addArtifactArg(exe);
615633
616 if (exe.kind == .test_exe) {
617 run_step.addArg(b.zig_exe);
634 if (exe.kind == .@"test") {
635 run_step.stdio = .zig_test;
636 run_step.addArgs(&.{"--listen=-"});
618637 }
619638
620639 if (exe.vcpkg_bin_path) |path| {
......@@ -634,7 +653,11 @@ pub fn addConfigHeader(
634653 options: ConfigHeaderStep.Options,
635654 values: anytype,
636655) *ConfigHeaderStep {
637 const config_header_step = ConfigHeaderStep.create(b, options);
656 var options_copy = options;
657 if (options_copy.first_ret_addr == null)
658 options_copy.first_ret_addr = @returnAddress();
659
660 const config_header_step = ConfigHeaderStep.create(b, options_copy);
638661 config_header_step.addValues(values);
639662 return config_header_step;
640663}
......@@ -671,17 +694,8 @@ pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *Writ
671694 return write_file_step;
672695}
673696
674pub fn addWriteFiles(self: *Build) *WriteFileStep {
675 const write_file_step = self.allocator.create(WriteFileStep) catch @panic("OOM");
676 write_file_step.* = WriteFileStep.init(self);
677 return write_file_step;
678}
679
680pub fn addLog(self: *Build, comptime format: []const u8, args: anytype) *LogStep {
681 const data = self.fmt(format, args);
682 const log_step = self.allocator.create(LogStep) catch @panic("OOM");
683 log_step.* = LogStep.init(self, data);
684 return log_step;
697pub fn addWriteFiles(b: *Build) *WriteFileStep {
698 return WriteFileStep.create(b);
685699}
686700
687701pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {
......@@ -690,32 +704,14 @@ pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {
690704 return remove_dir_step;
691705}
692706
693pub fn addFmt(self: *Build, paths: []const []const u8) *FmtStep {
694 return FmtStep.create(self, paths);
707pub fn addFmt(b: *Build, options: FmtStep.Options) *FmtStep {
708 return FmtStep.create(b, options);
695709}
696710
697711pub fn addTranslateC(self: *Build, options: TranslateCStep.Options) *TranslateCStep {
698712 return TranslateCStep.create(self, options);
699713}
700714
701pub fn make(self: *Build, step_names: []const []const u8) !void {
702 var wanted_steps = ArrayList(*Step).init(self.allocator);
703 defer wanted_steps.deinit();
704
705 if (step_names.len == 0) {
706 try wanted_steps.append(self.default_step);
707 } else {
708 for (step_names) |step_name| {
709 const s = try self.getTopLevelStepByName(step_name);
710 try wanted_steps.append(s);
711 }
712 }
713
714 for (wanted_steps.items) |s| {
715 try self.makeOneStep(s);
716 }
717}
718
719715pub fn getInstallStep(self: *Build) *Step {
720716 return &self.install_tls.step;
721717}
......@@ -724,7 +720,8 @@ pub fn getUninstallStep(self: *Build) *Step {
724720 return &self.uninstall_tls.step;
725721}
726722
727fn makeUninstall(uninstall_step: *Step) anyerror!void {
723fn makeUninstall(uninstall_step: *Step, prog_node: *std.Progress.Node) anyerror!void {
724 _ = prog_node;
728725 const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step);
729726 const self = @fieldParentPtr(Build, "uninstall_tls", uninstall_tls);
730727
......@@ -739,37 +736,6 @@ fn makeUninstall(uninstall_step: *Step) anyerror!void {
739736 // TODO remove empty directories
740737}
741738
742fn makeOneStep(self: *Build, s: *Step) anyerror!void {
743 if (s.loop_flag) {
744 log.err("Dependency loop detected:\n {s}", .{s.name});
745 return error.DependencyLoopDetected;
746 }
747 s.loop_flag = true;
748
749 for (s.dependencies.items) |dep| {
750 self.makeOneStep(dep) catch |err| {
751 if (err == error.DependencyLoopDetected) {
752 log.err(" {s}", .{s.name});
753 }
754 return err;
755 };
756 }
757
758 s.loop_flag = false;
759
760 try s.make();
761}
762
763fn getTopLevelStepByName(self: *Build, name: []const u8) !*Step {
764 for (self.top_level_steps.items) |top_level_step| {
765 if (mem.eql(u8, top_level_step.step.name, name)) {
766 return &top_level_step.step;
767 }
768 }
769 log.err("Cannot run step '{s}' because it does not exist", .{name});
770 return error.InvalidStepName;
771}
772
773739pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T {
774740 const name = self.dupe(name_raw);
775741 const description = self.dupe(description_raw);
......@@ -906,11 +872,15 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_
906872
907873pub fn step(self: *Build, name: []const u8, description: []const u8) *Step {
908874 const step_info = self.allocator.create(TopLevelStep) catch @panic("OOM");
909 step_info.* = TopLevelStep{
910 .step = Step.initNoOp(.top_level, name, self.allocator),
875 step_info.* = .{
876 .step = Step.init(.{
877 .id = .top_level,
878 .name = name,
879 .owner = self,
880 }),
911881 .description = self.dupe(description),
912882 };
913 self.top_level_steps.append(step_info) catch @panic("OOM");
883 self.top_level_steps.put(self.allocator, step_info.step.name, step_info) catch @panic("OOM");
914884 return &step_info.step;
915885}
916886
......@@ -1178,50 +1148,18 @@ pub fn validateUserInputDidItFail(self: *Build) bool {
11781148 return self.invalid_user_input;
11791149}
11801150
1181pub fn spawnChild(self: *Build, argv: []const []const u8) !void {
1182 return self.spawnChildEnvMap(null, self.env_map, argv);
1183}
1184
1185fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void {
1186 if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd});
1151fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 {
1152 var buf = ArrayList(u8).init(ally);
1153 if (opt_cwd) |cwd| try buf.writer().print("cd {s} && ", .{cwd});
11871154 for (argv) |arg| {
1188 std.debug.print("{s} ", .{arg});
1155 try buf.writer().print("{s} ", .{arg});
11891156 }
1190 std.debug.print("\n", .{});
1157 return buf.toOwnedSlice();
11911158}
11921159
1193pub fn spawnChildEnvMap(self: *Build, cwd: ?[]const u8, env_map: *const EnvMap, argv: []const []const u8) !void {
1194 if (self.verbose) {
1195 printCmd(cwd, argv);
1196 }
1197
1198 if (!std.process.can_spawn)
1199 return error.ExecNotSupported;
1200
1201 var child = std.ChildProcess.init(argv, self.allocator);
1202 child.cwd = cwd;
1203 child.env_map = env_map;
1204
1205 const term = child.spawnAndWait() catch |err| {
1206 log.err("Unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
1207 return err;
1208 };
1209
1210 switch (term) {
1211 .Exited => |code| {
1212 if (code != 0) {
1213 log.err("The following command exited with error code {}:", .{code});
1214 printCmd(cwd, argv);
1215 return error.UncleanExit;
1216 }
1217 },
1218 else => {
1219 log.err("The following command terminated unexpectedly:", .{});
1220 printCmd(cwd, argv);
1221
1222 return error.UncleanExit;
1223 },
1224 }
1160fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {
1161 const text = allocPrintCmd(ally, cwd, argv) catch @panic("OOM");
1162 std.debug.print("{s}\n", .{text});
12251163}
12261164
12271165pub fn installArtifact(self: *Build, artifact: *CompileStep) void {
......@@ -1280,12 +1218,7 @@ pub fn addInstallFileWithDir(
12801218 install_dir: InstallDir,
12811219 dest_rel_path: []const u8,
12821220) *InstallFileStep {
1283 if (dest_rel_path.len == 0) {
1284 panic("dest_rel_path must be non-empty", .{});
1285 }
1286 const install_step = self.allocator.create(InstallFileStep) catch @panic("OOM");
1287 install_step.* = InstallFileStep.init(self, source.dupe(self), install_dir, dest_rel_path);
1288 return install_step;
1221 return InstallFileStep.create(self, source.dupe(self), install_dir, dest_rel_path);
12891222}
12901223
12911224pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *InstallDirStep {
......@@ -1294,6 +1227,14 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Inst
12941227 return install_step;
12951228}
12961229
1230pub fn addCheckFile(
1231 b: *Build,
1232 file_source: FileSource,
1233 options: CheckFileStep.Options,
1234) *CheckFileStep {
1235 return CheckFileStep.create(b, file_source, options);
1236}
1237
12971238pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void {
12981239 const file = InstalledFile{
12991240 .dir = dir,
......@@ -1302,18 +1243,6 @@ pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u
13021243 self.installed_files.append(file.dupe(self)) catch @panic("OOM");
13031244}
13041245
1305pub fn updateFile(self: *Build, source_path: []const u8, dest_path: []const u8) !void {
1306 if (self.verbose) {
1307 log.info("cp {s} {s} ", .{ source_path, dest_path });
1308 }
1309 const cwd = fs.cwd();
1310 const prev_status = try fs.Dir.updateFile(cwd, source_path, cwd, dest_path, .{});
1311 if (self.verbose) switch (prev_status) {
1312 .stale => log.info("# installed", .{}),
1313 .fresh => log.info("# up-to-date", .{}),
1314 };
1315}
1316
13171246pub fn truncateFile(self: *Build, dest_path: []const u8) !void {
13181247 if (self.verbose) {
13191248 log.info("truncate {s}", .{dest_path});
......@@ -1397,7 +1326,7 @@ pub fn execAllowFail(
13971326) ExecError![]u8 {
13981327 assert(argv.len != 0);
13991328
1400 if (!std.process.can_spawn)
1329 if (!process.can_spawn)
14011330 return error.ExecNotSupported;
14021331
14031332 const max_output_size = 400 * 1024;
......@@ -1430,59 +1359,27 @@ pub fn execAllowFail(
14301359 }
14311360}
14321361
1433pub fn execFromStep(self: *Build, argv: []const []const u8, src_step: ?*Step) ![]u8 {
1434 assert(argv.len != 0);
1435
1436 if (self.verbose) {
1437 printCmd(null, argv);
1438 }
1439
1440 if (!std.process.can_spawn) {
1441 if (src_step) |s| log.err("{s}...", .{s.name});
1442 log.err("Unable to spawn the following command: cannot spawn child process", .{});
1443 printCmd(null, argv);
1444 std.os.abort();
1362/// This is a helper function to be called from build.zig scripts, *not* from
1363/// inside step make() functions. If any errors occur, it fails the build with
1364/// a helpful message.
1365pub fn exec(b: *Build, argv: []const []const u8) []u8 {
1366 if (!process.can_spawn) {
1367 std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
1368 try allocPrintCmd(b.allocator, null, argv),
1369 });
1370 process.exit(1);
14451371 }
14461372
14471373 var code: u8 = undefined;
1448 return self.execAllowFail(argv, &code, .Inherit) catch |err| switch (err) {
1449 error.ExecNotSupported => {
1450 if (src_step) |s| log.err("{s}...", .{s.name});
1451 log.err("Unable to spawn the following command: cannot spawn child process", .{});
1452 printCmd(null, argv);
1453 std.os.abort();
1454 },
1455 error.FileNotFound => {
1456 if (src_step) |s| log.err("{s}...", .{s.name});
1457 log.err("Unable to spawn the following command: file not found", .{});
1458 printCmd(null, argv);
1459 std.os.exit(@truncate(u8, code));
1460 },
1461 error.ExitCodeFailure => {
1462 if (src_step) |s| log.err("{s}...", .{s.name});
1463 if (self.prominent_compile_errors) {
1464 log.err("The step exited with error code {d}", .{code});
1465 } else {
1466 log.err("The following command exited with error code {d}:", .{code});
1467 printCmd(null, argv);
1468 }
1469
1470 std.os.exit(@truncate(u8, code));
1471 },
1472 error.ProcessTerminated => {
1473 if (src_step) |s| log.err("{s}...", .{s.name});
1474 log.err("The following command terminated unexpectedly:", .{});
1475 printCmd(null, argv);
1476 std.os.exit(@truncate(u8, code));
1477 },
1478 else => |e| return e,
1374 return b.execAllowFail(argv, &code, .Inherit) catch |err| {
1375 const printed_cmd = allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");
1376 std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{
1377 @errorName(err), printed_cmd,
1378 });
1379 process.exit(1);
14791380 };
14801381}
14811382
1482pub fn exec(self: *Build, argv: []const []const u8) ![]u8 {
1483 return self.execFromStep(argv, null);
1484}
1485
14861383pub fn addSearchPrefix(self: *Build, search_prefix: []const u8) void {
14871384 self.search_prefixes.append(self.dupePath(search_prefix)) catch @panic("OOM");
14881385}
......@@ -1547,10 +1444,29 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
15471444
15481445 const full_path = b.pathFromRoot("build.zig.zon");
15491446 std.debug.print("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file.\n", .{ name, full_path });
1550 std.process.exit(1);
1447 process.exit(1);
1448}
1449
1450pub fn anonymousDependency(
1451 b: *Build,
1452 /// The path to the directory containing the dependency's build.zig file,
1453 /// relative to the current package's build.zig.
1454 relative_build_root: []const u8,
1455 /// A direct `@import` of the build.zig of the dependency.
1456 comptime build_zig: type,
1457 args: anytype,
1458) *Dependency {
1459 const arena = b.allocator;
1460 const build_root = b.build_root.join(arena, &.{relative_build_root}) catch @panic("OOM");
1461 const name = arena.dupe(u8, relative_build_root) catch @panic("OOM");
1462 for (name) |*byte| switch (byte.*) {
1463 '/', '\\' => byte.* = '.',
1464 else => continue,
1465 };
1466 return dependencyInner(b, name, build_root, build_zig, args);
15511467}
15521468
1553fn dependencyInner(
1469pub fn dependencyInner(
15541470 b: *Build,
15551471 name: []const u8,
15561472 build_root_string: []const u8,
......@@ -1563,7 +1479,7 @@ fn dependencyInner(
15631479 std.debug.print("unable to open '{s}': {s}\n", .{
15641480 build_root_string, @errorName(err),
15651481 });
1566 std.process.exit(1);
1482 process.exit(1);
15671483 },
15681484 };
15691485 const sub_builder = b.createChild(name, build_root, args) catch @panic("unhandled error");
......@@ -1607,7 +1523,7 @@ pub const GeneratedFile = struct {
16071523
16081524 pub fn getPath(self: GeneratedFile) []const u8 {
16091525 return self.path orelse std.debug.panic(
1610 "getPath() was called on a GeneratedFile that wasn't build yet. Is there a missing Step dependency on step '{s}'?",
1526 "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?",
16111527 .{self.step.name},
16121528 );
16131529 }
......@@ -1647,12 +1563,23 @@ pub const FileSource = union(enum) {
16471563 }
16481564
16491565 /// Should only be called during make(), returns a path relative to the build root or absolute.
1650 pub fn getPath(self: FileSource, builder: *Build) []const u8 {
1651 const path = switch (self) {
1652 .path => |p| builder.pathFromRoot(p),
1653 .generated => |gen| gen.getPath(),
1654 };
1655 return path;
1566 pub fn getPath(self: FileSource, src_builder: *Build) []const u8 {
1567 return getPath2(self, src_builder, null);
1568 }
1569
1570 /// Should only be called during make(), returns a path relative to the build root or absolute.
1571 /// asking_step is only used for debugging purposes; it's the step being run that is asking for
1572 /// the path.
1573 pub fn getPath2(self: FileSource, src_builder: *Build, asking_step: ?*Step) []const u8 {
1574 switch (self) {
1575 .path => |p| return src_builder.pathFromRoot(p),
1576 .generated => |gen| return gen.path orelse {
1577 std.debug.getStderrMutex().lock();
1578 const stderr = std.io.getStdErr();
1579 dumpBadGetPathHelp(gen.step, stderr, src_builder, asking_step) catch {};
1580 @panic("misconfigured build script");
1581 },
1582 }
16561583 }
16571584
16581585 /// Duplicates the file source for a given builder.
......@@ -1664,6 +1591,54 @@ pub const FileSource = union(enum) {
16641591 }
16651592};
16661593
1594/// In this function the stderr mutex has already been locked.
1595fn dumpBadGetPathHelp(
1596 s: *Step,
1597 stderr: fs.File,
1598 src_builder: *Build,
1599 asking_step: ?*Step,
1600) anyerror!void {
1601 const w = stderr.writer();
1602 try w.print(
1603 \\getPath() was called on a GeneratedFile that wasn't built yet.
1604 \\ source package path: {s}
1605 \\ Is there a missing Step dependency on step '{s}'?
1606 \\
1607 , .{
1608 src_builder.build_root.path orelse ".",
1609 s.name,
1610 });
1611
1612 const tty_config = std.debug.detectTTYConfig(stderr);
1613 tty_config.setColor(w, .Red) catch {};
1614 try stderr.writeAll(" The step was created by this stack trace:\n");
1615 tty_config.setColor(w, .Reset) catch {};
1616
1617 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
1618 try w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});
1619 return;
1620 };
1621 const ally = debug_info.allocator;
1622 std.debug.writeStackTrace(s.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
1623 try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
1624 return;
1625 };
1626 if (asking_step) |as| {
1627 tty_config.setColor(w, .Red) catch {};
1628 try stderr.writeAll(" The step that is missing a dependency on the above step was created by this stack trace:\n");
1629 tty_config.setColor(w, .Reset) catch {};
1630
1631 std.debug.writeStackTrace(as.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
1632 try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
1633 return;
1634 };
1635 }
1636
1637 tty_config.setColor(w, .Red) catch {};
1638 try stderr.writeAll(" Hope that helps. Proceeding to panic.\n");
1639 tty_config.setColor(w, .Reset) catch {};
1640}
1641
16671642/// Allocates a new string for assigning a value to a named macro.
16681643/// If the value is omitted, it is set to 1.
16691644/// `name` and `value` need not live longer than the function call.
......@@ -1703,9 +1678,7 @@ pub const InstallDir = union(enum) {
17031678 /// Duplicates the install directory including the path if set to custom.
17041679 pub fn dupe(self: InstallDir, builder: *Build) InstallDir {
17051680 if (self == .custom) {
1706 // Written with this temporary to avoid RLS problems
1707 const duped_path = builder.dupe(self.custom);
1708 return .{ .custom = duped_path };
1681 return .{ .custom = builder.dupe(self.custom) };
17091682 } else {
17101683 return self;
17111684 }
......@@ -1753,17 +1726,45 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {
17531726 }
17541727}
17551728
1729/// This function is intended to be called in the `configure` phase only.
1730/// It returns an absolute directory path, which is potentially going to be a
1731/// source of API breakage in the future, so keep that in mind when using this
1732/// function.
1733pub fn makeTempPath(b: *Build) []const u8 {
1734 const rand_int = std.crypto.random.int(u64);
1735 const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ hex64(rand_int);
1736 const result_path = b.cache_root.join(b.allocator, &.{tmp_dir_sub_path}) catch @panic("OOM");
1737 fs.cwd().makePath(result_path) catch |err| {
1738 std.debug.print("unable to make tmp path '{s}': {s}\n", .{
1739 result_path, @errorName(err),
1740 });
1741 };
1742 return result_path;
1743}
1744
1745/// There are a few copies of this function in miscellaneous places. Would be nice to find
1746/// a home for them.
1747fn hex64(x: u64) [16]u8 {
1748 const hex_charset = "0123456789abcdef";
1749 var result: [16]u8 = undefined;
1750 var i: usize = 0;
1751 while (i < 8) : (i += 1) {
1752 const byte = @truncate(u8, x >> @intCast(u6, 8 * i));
1753 result[i * 2 + 0] = hex_charset[byte >> 4];
1754 result[i * 2 + 1] = hex_charset[byte & 15];
1755 }
1756 return result;
1757}
1758
17561759test {
17571760 _ = CheckFileStep;
17581761 _ = CheckObjectStep;
1759 _ = EmulatableRunStep;
17601762 _ = FmtStep;
17611763 _ = InstallArtifactStep;
17621764 _ = InstallDirStep;
17631765 _ = InstallFileStep;
17641766 _ = ObjCopyStep;
17651767 _ = CompileStep;
1766 _ = LogStep;
17671768 _ = OptionsStep;
17681769 _ = RemoveDirStep;
17691770 _ = RunStep;
lib/std/Build/Cache.zig+74-61
......@@ -7,27 +7,27 @@ pub const Directory = struct {
77 /// directly, but it is needed when passing the directory to a child process.
88 /// `null` means cwd.
99 path: ?[]const u8,
10 handle: std.fs.Dir,
10 handle: fs.Dir,
1111
1212 pub fn join(self: Directory, allocator: Allocator, paths: []const []const u8) ![]u8 {
1313 if (self.path) |p| {
1414 // TODO clean way to do this with only 1 allocation
15 const part2 = try std.fs.path.join(allocator, paths);
15 const part2 = try fs.path.join(allocator, paths);
1616 defer allocator.free(part2);
17 return std.fs.path.join(allocator, &[_][]const u8{ p, part2 });
17 return fs.path.join(allocator, &[_][]const u8{ p, part2 });
1818 } else {
19 return std.fs.path.join(allocator, paths);
19 return fs.path.join(allocator, paths);
2020 }
2121 }
2222
2323 pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) ![:0]u8 {
2424 if (self.path) |p| {
2525 // TODO clean way to do this with only 1 allocation
26 const part2 = try std.fs.path.join(allocator, paths);
26 const part2 = try fs.path.join(allocator, paths);
2727 defer allocator.free(part2);
28 return std.fs.path.joinZ(allocator, &[_][]const u8{ p, part2 });
28 return fs.path.joinZ(allocator, &[_][]const u8{ p, part2 });
2929 } else {
30 return std.fs.path.joinZ(allocator, paths);
30 return fs.path.joinZ(allocator, paths);
3131 }
3232 }
3333
......@@ -39,6 +39,20 @@ pub const Directory = struct {
3939 if (self.path) |p| gpa.free(p);
4040 self.* = undefined;
4141 }
42
43 pub fn format(
44 self: Directory,
45 comptime fmt_string: []const u8,
46 options: fmt.FormatOptions,
47 writer: anytype,
48 ) !void {
49 _ = options;
50 if (fmt_string.len != 0) fmt.invalidFmtError(fmt, self);
51 if (self.path) |p| {
52 try writer.writeAll(p);
53 try writer.writeAll(fs.path.sep_str);
54 }
55 }
4256};
4357
4458gpa: Allocator,
......@@ -243,10 +257,10 @@ pub const HashHelper = struct {
243257 hh.hasher.final(&bin_digest);
244258
245259 var out_digest: [hex_digest_len]u8 = undefined;
246 _ = std.fmt.bufPrint(
260 _ = fmt.bufPrint(
247261 &out_digest,
248262 "{s}",
249 .{std.fmt.fmtSliceHexLower(&bin_digest)},
263 .{fmt.fmtSliceHexLower(&bin_digest)},
250264 ) catch unreachable;
251265 return out_digest;
252266 }
......@@ -365,10 +379,10 @@ pub const Manifest = struct {
365379 var bin_digest: BinDigest = undefined;
366380 self.hash.hasher.final(&bin_digest);
367381
368 _ = std.fmt.bufPrint(
382 _ = fmt.bufPrint(
369383 &self.hex_digest,
370384 "{s}",
371 .{std.fmt.fmtSliceHexLower(&bin_digest)},
385 .{fmt.fmtSliceHexLower(&bin_digest)},
372386 ) catch unreachable;
373387
374388 self.hash.hasher = hasher_init;
......@@ -408,7 +422,11 @@ pub const Manifest = struct {
408422 self.have_exclusive_lock = true;
409423 return false; // cache miss; exclusive lock already held
410424 } else |err| switch (err) {
411 error.WouldBlock => continue,
425 // There are no dir components, so you would think
426 // that this was unreachable, however we have
427 // observed on macOS two processes racing to do
428 // openat() with O_CREAT manifest in ENOENT.
429 error.WouldBlock, error.FileNotFound => continue,
412430 else => |e| return e,
413431 }
414432 },
......@@ -425,7 +443,10 @@ pub const Manifest = struct {
425443 self.manifest_file = manifest_file;
426444 self.have_exclusive_lock = true;
427445 } else |err| switch (err) {
428 error.WouldBlock => {
446 // There are no dir components, so you would think that this was
447 // unreachable, however we have observed on macOS two processes racing
448 // to do openat() with O_CREAT manifest in ENOENT.
449 error.WouldBlock, error.FileNotFound => {
429450 self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
430451 .lock = .Shared,
431452 });
......@@ -469,7 +490,7 @@ pub const Manifest = struct {
469490 cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;
470491 cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat;
471492 cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;
472 _ = std.fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat;
493 _ = fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat;
473494 const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat;
474495 if (prefix >= self.cache.prefixes_len) return error.InvalidFormat;
475496
......@@ -806,10 +827,10 @@ pub const Manifest = struct {
806827 self.hash.hasher.final(&bin_digest);
807828
808829 var out_digest: [hex_digest_len]u8 = undefined;
809 _ = std.fmt.bufPrint(
830 _ = fmt.bufPrint(
810831 &out_digest,
811832 "{s}",
812 .{std.fmt.fmtSliceHexLower(&bin_digest)},
833 .{fmt.fmtSliceHexLower(&bin_digest)},
813834 ) catch unreachable;
814835
815836 return out_digest;
......@@ -831,10 +852,10 @@ pub const Manifest = struct {
831852 var encoded_digest: [hex_digest_len]u8 = undefined;
832853
833854 for (self.files.items) |file| {
834 _ = std.fmt.bufPrint(
855 _ = fmt.bufPrint(
835856 &encoded_digest,
836857 "{s}",
837 .{std.fmt.fmtSliceHexLower(&file.bin_digest)},
858 .{fmt.fmtSliceHexLower(&file.bin_digest)},
838859 ) catch unreachable;
839860 try writer.print("{d} {d} {d} {s} {d} {s}\n", .{
840861 file.stat.size,
......@@ -955,16 +976,16 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void {
955976}
956977
957978// Create/Write a file, close it, then grab its stat.mtime timestamp.
958fn testGetCurrentFileTimestamp() !i128 {
979fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {
959980 const test_out_file = "test-filetimestamp.tmp";
960981
961 var file = try fs.cwd().createFile(test_out_file, .{
982 var file = try dir.createFile(test_out_file, .{
962983 .read = true,
963984 .truncate = true,
964985 });
965986 defer {
966987 file.close();
967 fs.cwd().deleteFile(test_out_file) catch {};
988 dir.deleteFile(test_out_file) catch {};
968989 }
969990
970991 return (try file.stat()).mtime;
......@@ -976,16 +997,17 @@ test "cache file and then recall it" {
976997 return error.SkipZigTest;
977998 }
978999
979 const cwd = fs.cwd();
1000 var tmp = testing.tmpDir(.{});
1001 defer tmp.cleanup();
9801002
9811003 const temp_file = "test.txt";
9821004 const temp_manifest_dir = "temp_manifest_dir";
9831005
984 try cwd.writeFile(temp_file, "Hello, world!\n");
1006 try tmp.dir.writeFile(temp_file, "Hello, world!\n");
9851007
9861008 // Wait for file timestamps to tick
987 const initial_time = try testGetCurrentFileTimestamp();
988 while ((try testGetCurrentFileTimestamp()) == initial_time) {
1009 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1010 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
9891011 std.time.sleep(1);
9901012 }
9911013
......@@ -995,9 +1017,9 @@ test "cache file and then recall it" {
9951017 {
9961018 var cache = Cache{
9971019 .gpa = testing.allocator,
998 .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
1020 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
9991021 };
1000 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
1022 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
10011023 defer cache.manifest_dir.close();
10021024
10031025 {
......@@ -1033,9 +1055,6 @@ test "cache file and then recall it" {
10331055
10341056 try testing.expectEqual(digest1, digest2);
10351057 }
1036
1037 try cwd.deleteTree(temp_manifest_dir);
1038 try cwd.deleteFile(temp_file);
10391058}
10401059
10411060test "check that changing a file makes cache fail" {
......@@ -1043,21 +1062,19 @@ test "check that changing a file makes cache fail" {
10431062 // https://github.com/ziglang/zig/issues/5437
10441063 return error.SkipZigTest;
10451064 }
1046 const cwd = fs.cwd();
1065 var tmp = testing.tmpDir(.{});
1066 defer tmp.cleanup();
10471067
10481068 const temp_file = "cache_hash_change_file_test.txt";
10491069 const temp_manifest_dir = "cache_hash_change_file_manifest_dir";
10501070 const original_temp_file_contents = "Hello, world!\n";
10511071 const updated_temp_file_contents = "Hello, world; but updated!\n";
10521072
1053 try cwd.deleteTree(temp_manifest_dir);
1054 try cwd.deleteTree(temp_file);
1055
1056 try cwd.writeFile(temp_file, original_temp_file_contents);
1073 try tmp.dir.writeFile(temp_file, original_temp_file_contents);
10571074
10581075 // Wait for file timestamps to tick
1059 const initial_time = try testGetCurrentFileTimestamp();
1060 while ((try testGetCurrentFileTimestamp()) == initial_time) {
1076 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1077 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
10611078 std.time.sleep(1);
10621079 }
10631080
......@@ -1067,9 +1084,9 @@ test "check that changing a file makes cache fail" {
10671084 {
10681085 var cache = Cache{
10691086 .gpa = testing.allocator,
1070 .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
1087 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
10711088 };
1072 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
1089 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
10731090 defer cache.manifest_dir.close();
10741091
10751092 {
......@@ -1089,7 +1106,7 @@ test "check that changing a file makes cache fail" {
10891106 try ch.writeManifest();
10901107 }
10911108
1092 try cwd.writeFile(temp_file, updated_temp_file_contents);
1109 try tmp.dir.writeFile(temp_file, updated_temp_file_contents);
10931110
10941111 {
10951112 var ch = cache.obtain();
......@@ -1111,9 +1128,6 @@ test "check that changing a file makes cache fail" {
11111128
11121129 try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));
11131130 }
1114
1115 try cwd.deleteTree(temp_manifest_dir);
1116 try cwd.deleteTree(temp_file);
11171131}
11181132
11191133test "no file inputs" {
......@@ -1121,18 +1135,20 @@ test "no file inputs" {
11211135 // https://github.com/ziglang/zig/issues/5437
11221136 return error.SkipZigTest;
11231137 }
1124 const cwd = fs.cwd();
1138
1139 var tmp = testing.tmpDir(.{});
1140 defer tmp.cleanup();
1141
11251142 const temp_manifest_dir = "no_file_inputs_manifest_dir";
1126 defer cwd.deleteTree(temp_manifest_dir) catch {};
11271143
11281144 var digest1: [hex_digest_len]u8 = undefined;
11291145 var digest2: [hex_digest_len]u8 = undefined;
11301146
11311147 var cache = Cache{
11321148 .gpa = testing.allocator,
1133 .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
1149 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
11341150 };
1135 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
1151 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
11361152 defer cache.manifest_dir.close();
11371153
11381154 {
......@@ -1167,18 +1183,19 @@ test "Manifest with files added after initial hash work" {
11671183 // https://github.com/ziglang/zig/issues/5437
11681184 return error.SkipZigTest;
11691185 }
1170 const cwd = fs.cwd();
1186 var tmp = testing.tmpDir(.{});
1187 defer tmp.cleanup();
11711188
11721189 const temp_file1 = "cache_hash_post_file_test1.txt";
11731190 const temp_file2 = "cache_hash_post_file_test2.txt";
11741191 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
11751192
1176 try cwd.writeFile(temp_file1, "Hello, world!\n");
1177 try cwd.writeFile(temp_file2, "Hello world the second!\n");
1193 try tmp.dir.writeFile(temp_file1, "Hello, world!\n");
1194 try tmp.dir.writeFile(temp_file2, "Hello world the second!\n");
11781195
11791196 // Wait for file timestamps to tick
1180 const initial_time = try testGetCurrentFileTimestamp();
1181 while ((try testGetCurrentFileTimestamp()) == initial_time) {
1197 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1198 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
11821199 std.time.sleep(1);
11831200 }
11841201
......@@ -1189,9 +1206,9 @@ test "Manifest with files added after initial hash work" {
11891206 {
11901207 var cache = Cache{
11911208 .gpa = testing.allocator,
1192 .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),
1209 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
11931210 };
1194 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
1211 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
11951212 defer cache.manifest_dir.close();
11961213
11971214 {
......@@ -1224,11 +1241,11 @@ test "Manifest with files added after initial hash work" {
12241241 try testing.expect(mem.eql(u8, &digest1, &digest2));
12251242
12261243 // Modify the file added after initial hash
1227 try cwd.writeFile(temp_file2, "Hello world the second, updated\n");
1244 try tmp.dir.writeFile(temp_file2, "Hello world the second, updated\n");
12281245
12291246 // Wait for file timestamps to tick
1230 const initial_time2 = try testGetCurrentFileTimestamp();
1231 while ((try testGetCurrentFileTimestamp()) == initial_time2) {
1247 const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
1248 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) {
12321249 std.time.sleep(1);
12331250 }
12341251
......@@ -1251,8 +1268,4 @@ test "Manifest with files added after initial hash work" {
12511268
12521269 try testing.expect(!mem.eql(u8, &digest1, &digest3));
12531270 }
1254
1255 try cwd.deleteTree(temp_manifest_dir);
1256 try cwd.deleteFile(temp_file1);
1257 try cwd.deleteFile(temp_file2);
12581271}
lib/std/Build/CheckFileStep.zig+62-25
......@@ -1,51 +1,88 @@
1const std = @import("../std.zig");
2const Step = std.Build.Step;
3const fs = std.fs;
4const mem = std.mem;
5
6const CheckFileStep = @This();
7
8pub const base_id = .check_file;
1//! Fail the build step if a file does not match certain checks.
2//! TODO: make this more flexible, supporting more kinds of checks.
3//! TODO: generalize the code in std.testing.expectEqualStrings and make this
4//! CheckFileStep produce those helpful diagnostics when there is not a match.
95
106step: Step,
11builder: *std.Build,
127expected_matches: []const []const u8,
8expected_exact: ?[]const u8,
139source: std.Build.FileSource,
1410max_bytes: usize = 20 * 1024 * 1024,
1511
12pub const base_id = .check_file;
13
14pub const Options = struct {
15 expected_matches: []const []const u8 = &.{},
16 expected_exact: ?[]const u8 = null,
17};
18
1619pub fn create(
17 builder: *std.Build,
20 owner: *std.Build,
1821 source: std.Build.FileSource,
19 expected_matches: []const []const u8,
22 options: Options,
2023) *CheckFileStep {
21 const self = builder.allocator.create(CheckFileStep) catch @panic("OOM");
22 self.* = CheckFileStep{
23 .builder = builder,
24 .step = Step.init(.check_file, "CheckFile", builder.allocator, make),
25 .source = source.dupe(builder),
26 .expected_matches = builder.dupeStrings(expected_matches),
24 const self = owner.allocator.create(CheckFileStep) catch @panic("OOM");
25 self.* = .{
26 .step = Step.init(.{
27 .id = .check_file,
28 .name = "CheckFile",
29 .owner = owner,
30 .makeFn = make,
31 }),
32 .source = source.dupe(owner),
33 .expected_matches = owner.dupeStrings(options.expected_matches),
34 .expected_exact = options.expected_exact,
2735 };
2836 self.source.addStepDependencies(&self.step);
2937 return self;
3038}
3139
32fn make(step: *Step) !void {
40pub fn setName(self: *CheckFileStep, name: []const u8) void {
41 self.step.name = name;
42}
43
44fn make(step: *Step, prog_node: *std.Progress.Node) !void {
45 _ = prog_node;
46 const b = step.owner;
3347 const self = @fieldParentPtr(CheckFileStep, "step", step);
3448
35 const src_path = self.source.getPath(self.builder);
36 const contents = try fs.cwd().readFileAlloc(self.builder.allocator, src_path, self.max_bytes);
49 const src_path = self.source.getPath(b);
50 const contents = fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes) catch |err| {
51 return step.fail("unable to read '{s}': {s}", .{
52 src_path, @errorName(err),
53 });
54 };
3755
3856 for (self.expected_matches) |expected_match| {
3957 if (mem.indexOf(u8, contents, expected_match) == null) {
40 std.debug.print(
58 return step.fail(
4159 \\
42 \\========= Expected to find: ===================
60 \\========= expected to find: ===================
4361 \\{s}
44 \\========= But file does not contain it: =======
62 \\========= but file does not contain it: =======
4563 \\{s}
46 \\
64 \\===============================================
4765 , .{ expected_match, contents });
48 return error.TestFailed;
66 }
67 }
68
69 if (self.expected_exact) |expected_exact| {
70 if (!mem.eql(u8, expected_exact, contents)) {
71 return step.fail(
72 \\
73 \\========= expected: =====================
74 \\{s}
75 \\========= but found: ====================
76 \\{s}
77 \\========= from the following file: ======
78 \\{s}
79 , .{ expected_exact, contents, src_path });
4980 }
5081 }
5182}
83
84const CheckFileStep = @This();
85const std = @import("../std.zig");
86const Step = std.Build.Step;
87const fs = std.fs;
88const mem = std.mem;
lib/std/Build/CheckObjectStep.zig+95-91
......@@ -10,25 +10,31 @@ const CheckObjectStep = @This();
1010
1111const Allocator = mem.Allocator;
1212const Step = std.Build.Step;
13const EmulatableRunStep = std.Build.EmulatableRunStep;
1413
1514pub const base_id = .check_object;
1615
1716step: Step,
18builder: *std.Build,
1917source: std.Build.FileSource,
2018max_bytes: usize = 20 * 1024 * 1024,
2119checks: std.ArrayList(Check),
2220dump_symtab: bool = false,
2321obj_format: std.Target.ObjectFormat,
2422
25pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep {
26 const gpa = builder.allocator;
23pub fn create(
24 owner: *std.Build,
25 source: std.Build.FileSource,
26 obj_format: std.Target.ObjectFormat,
27) *CheckObjectStep {
28 const gpa = owner.allocator;
2729 const self = gpa.create(CheckObjectStep) catch @panic("OOM");
2830 self.* = .{
29 .builder = builder,
30 .step = Step.init(.check_file, "CheckObject", gpa, make),
31 .source = source.dupe(builder),
31 .step = Step.init(.{
32 .id = .check_file,
33 .name = "CheckObject",
34 .owner = owner,
35 .makeFn = make,
36 }),
37 .source = source.dupe(owner),
3238 .checks = std.ArrayList(Check).init(gpa),
3339 .obj_format = obj_format,
3440 };
......@@ -38,14 +44,18 @@ pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std
3844
3945/// Runs and (optionally) compares the output of a binary.
4046/// Asserts `self` was generated from an executable step.
41pub fn runAndCompare(self: *CheckObjectStep) *EmulatableRunStep {
47/// TODO this doesn't actually compare, and there's no apparent reason for it
48/// to depend on the check object step. I don't see why this function should exist,
49/// the caller could just add the run step directly.
50pub fn runAndCompare(self: *CheckObjectStep) *std.Build.RunStep {
4251 const dependencies_len = self.step.dependencies.items.len;
4352 assert(dependencies_len > 0);
4453 const exe_step = self.step.dependencies.items[dependencies_len - 1];
4554 const exe = exe_step.cast(std.Build.CompileStep).?;
46 const emulatable_step = EmulatableRunStep.create(self.builder, "EmulatableRun", exe);
47 emulatable_step.step.dependOn(&self.step);
48 return emulatable_step;
55 const run = self.step.owner.addRunArtifact(exe);
56 run.skip_foreign_checks = true;
57 run.step.dependOn(&self.step);
58 return run;
4959}
5060
5161/// There two types of actions currently suported:
......@@ -123,7 +133,8 @@ const Action = struct {
123133 /// Will return true if the `phrase` is correctly parsed into an RPN program and
124134 /// its reduced, computed value compares using `op` with the expected value, either
125135 /// a literal or another extracted variable.
126 fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool {
136 fn computeCmp(act: Action, step: *Step, global_vars: anytype) !bool {
137 const gpa = step.owner.allocator;
127138 var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa);
128139 var values = std.ArrayList(u64).init(gpa);
129140
......@@ -140,11 +151,11 @@ const Action = struct {
140151 } else {
141152 const val = std.fmt.parseInt(u64, next, 0) catch blk: {
142153 break :blk global_vars.get(next) orelse {
143 std.debug.print(
154 try step.addError(
144155 \\
145 \\========= Variable was not extracted: ===========
156 \\========= variable was not extracted: ===========
146157 \\{s}
147 \\
158 \\=================================================
148159 , .{next});
149160 return error.UnknownVariable;
150161 };
......@@ -176,11 +187,11 @@ const Action = struct {
176187
177188 const exp_value = switch (act.expected.?.value) {
178189 .variable => |name| global_vars.get(name) orelse {
179 std.debug.print(
190 try step.addError(
180191 \\
181 \\========= Variable was not extracted: ===========
192 \\========= variable was not extracted: ===========
182193 \\{s}
183 \\
194 \\=================================================
184195 , .{name});
185196 return error.UnknownVariable;
186197 },
......@@ -249,7 +260,7 @@ const Check = struct {
249260
250261/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase.
251262pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void {
252 var new_check = Check.create(self.builder);
263 var new_check = Check.create(self.step.owner);
253264 new_check.match(phrase);
254265 self.checks.append(new_check) catch @panic("OOM");
255266}
......@@ -291,34 +302,34 @@ pub fn checkComputeCompare(
291302 program: []const u8,
292303 expected: ComputeCompareExpected,
293304) void {
294 var new_check = Check.create(self.builder);
305 var new_check = Check.create(self.step.owner);
295306 new_check.computeCmp(program, expected);
296307 self.checks.append(new_check) catch @panic("OOM");
297308}
298309
299fn make(step: *Step) !void {
310fn make(step: *Step, prog_node: *std.Progress.Node) !void {
311 _ = prog_node;
312 const b = step.owner;
313 const gpa = b.allocator;
300314 const self = @fieldParentPtr(CheckObjectStep, "step", step);
301315
302 const gpa = self.builder.allocator;
303 const src_path = self.source.getPath(self.builder);
304 const contents = try fs.cwd().readFileAllocOptions(
316 const src_path = self.source.getPath(b);
317 const contents = fs.cwd().readFileAllocOptions(
305318 gpa,
306319 src_path,
307320 self.max_bytes,
308321 null,
309322 @alignOf(u64),
310323 null,
311 );
324 ) catch |err| return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err) });
312325
313326 const output = switch (self.obj_format) {
314 .macho => try MachODumper.parseAndDump(contents, .{
315 .gpa = gpa,
327 .macho => try MachODumper.parseAndDump(step, contents, .{
316328 .dump_symtab = self.dump_symtab,
317329 }),
318330 .elf => @panic("TODO elf parser"),
319331 .coff => @panic("TODO coff parser"),
320 .wasm => try WasmDumper.parseAndDump(contents, .{
321 .gpa = gpa,
332 .wasm => try WasmDumper.parseAndDump(step, contents, .{
322333 .dump_symtab = self.dump_symtab,
323334 }),
324335 else => unreachable,
......@@ -334,54 +345,50 @@ fn make(step: *Step) !void {
334345 while (it.next()) |line| {
335346 if (try act.match(line, &vars)) break;
336347 } else {
337 std.debug.print(
348 return step.fail(
338349 \\
339 \\========= Expected to find: ==========================
350 \\========= expected to find: ==========================
340351 \\{s}
341 \\========= But parsed file does not contain it: =======
352 \\========= but parsed file does not contain it: =======
342353 \\{s}
343 \\
354 \\======================================================
344355 , .{ act.phrase, output });
345 return error.TestFailed;
346356 }
347357 },
348358 .not_present => {
349359 while (it.next()) |line| {
350360 if (try act.match(line, &vars)) {
351 std.debug.print(
361 return step.fail(
352362 \\
353 \\========= Expected not to find: ===================
363 \\========= expected not to find: ===================
354364 \\{s}
355 \\========= But parsed file does contain it: ========
365 \\========= but parsed file does contain it: ========
356366 \\{s}
357 \\
367 \\===================================================
358368 , .{ act.phrase, output });
359 return error.TestFailed;
360369 }
361370 }
362371 },
363372 .compute_cmp => {
364 const res = act.computeCmp(gpa, vars) catch |err| switch (err) {
373 const res = act.computeCmp(step, vars) catch |err| switch (err) {
365374 error.UnknownVariable => {
366 std.debug.print(
367 \\========= From parsed file: =====================
375 return step.fail(
376 \\========= from parsed file: =====================
368377 \\{s}
369 \\
378 \\=================================================
370379 , .{output});
371 return error.TestFailed;
372380 },
373381 else => |e| return e,
374382 };
375383 if (!res) {
376 std.debug.print(
384 return step.fail(
377385 \\
378 \\========= Comparison failed for action: ===========
386 \\========= comparison failed for action: ===========
379387 \\{s} {}
380 \\========= From parsed file: =======================
388 \\========= from parsed file: =======================
381389 \\{s}
382 \\
390 \\===================================================
383391 , .{ act.phrase, act.expected.?, output });
384 return error.TestFailed;
385392 }
386393 },
387394 }
......@@ -390,7 +397,6 @@ fn make(step: *Step) !void {
390397}
391398
392399const Opts = struct {
393 gpa: ?Allocator = null,
394400 dump_symtab: bool = false,
395401};
396402
......@@ -398,8 +404,8 @@ const MachODumper = struct {
398404 const LoadCommandIterator = macho.LoadCommandIterator;
399405 const symtab_label = "symtab";
400406
401 fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {
402 const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator
407 fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {
408 const gpa = step.owner.allocator;
403409 var stream = std.io.fixedBufferStream(bytes);
404410 const reader = stream.reader();
405411
......@@ -681,8 +687,8 @@ const MachODumper = struct {
681687const WasmDumper = struct {
682688 const symtab_label = "symbols";
683689
684 fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 {
685 const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator
690 fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 {
691 const gpa = step.owner.allocator;
686692 if (opts.dump_symtab) {
687693 @panic("TODO: Implement symbol table parsing and dumping");
688694 }
......@@ -703,20 +709,24 @@ const WasmDumper = struct {
703709 const writer = output.writer();
704710
705711 while (reader.readByte()) |current_byte| {
706 const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| {
707 std.debug.print("Found invalid section id '{d}'\n", .{current_byte});
708 return err;
712 const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch {
713 return step.fail("Found invalid section id '{d}'", .{current_byte});
709714 };
710715
711716 const section_length = try std.leb.readULEB128(u32, reader);
712 try parseAndDumpSection(section, bytes[fbs.pos..][0..section_length], writer);
717 try parseAndDumpSection(step, section, bytes[fbs.pos..][0..section_length], writer);
713718 fbs.pos += section_length;
714719 } else |_| {} // reached end of stream
715720
716721 return output.toOwnedSlice();
717722 }
718723
719 fn parseAndDumpSection(section: std.wasm.Section, data: []const u8, writer: anytype) !void {
724 fn parseAndDumpSection(
725 step: *Step,
726 section: std.wasm.Section,
727 data: []const u8,
728 writer: anytype,
729 ) !void {
720730 var fbs = std.io.fixedBufferStream(data);
721731 const reader = fbs.reader();
722732
......@@ -739,7 +749,7 @@ const WasmDumper = struct {
739749 => {
740750 const entries = try std.leb.readULEB128(u32, reader);
741751 try writer.print("\nentries {d}\n", .{entries});
742 try dumpSection(section, data[fbs.pos..], entries, writer);
752 try dumpSection(step, section, data[fbs.pos..], entries, writer);
743753 },
744754 .custom => {
745755 const name_length = try std.leb.readULEB128(u32, reader);
......@@ -748,7 +758,7 @@ const WasmDumper = struct {
748758 try writer.print("\nname {s}\n", .{name});
749759
750760 if (mem.eql(u8, name, "name")) {
751 try parseDumpNames(reader, writer, data);
761 try parseDumpNames(step, reader, writer, data);
752762 } else if (mem.eql(u8, name, "producers")) {
753763 try parseDumpProducers(reader, writer, data);
754764 } else if (mem.eql(u8, name, "target_features")) {
......@@ -764,7 +774,7 @@ const WasmDumper = struct {
764774 }
765775 }
766776
767 fn dumpSection(section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {
777 fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {
768778 var fbs = std.io.fixedBufferStream(data);
769779 const reader = fbs.reader();
770780
......@@ -774,19 +784,18 @@ const WasmDumper = struct {
774784 while (i < entries) : (i += 1) {
775785 const func_type = try reader.readByte();
776786 if (func_type != std.wasm.function_type) {
777 std.debug.print("Expected function type, found byte '{d}'\n", .{func_type});
778 return error.UnexpectedByte;
787 return step.fail("expected function type, found byte '{d}'", .{func_type});
779788 }
780789 const params = try std.leb.readULEB128(u32, reader);
781790 try writer.print("params {d}\n", .{params});
782791 var index: u32 = 0;
783792 while (index < params) : (index += 1) {
784 try parseDumpType(std.wasm.Valtype, reader, writer);
793 try parseDumpType(step, std.wasm.Valtype, reader, writer);
785794 } else index = 0;
786795 const returns = try std.leb.readULEB128(u32, reader);
787796 try writer.print("returns {d}\n", .{returns});
788797 while (index < returns) : (index += 1) {
789 try parseDumpType(std.wasm.Valtype, reader, writer);
798 try parseDumpType(step, std.wasm.Valtype, reader, writer);
790799 }
791800 }
792801 },
......@@ -800,9 +809,8 @@ const WasmDumper = struct {
800809 const name = data[fbs.pos..][0..name_len];
801810 fbs.pos += name_len;
802811
803 const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| {
804 std.debug.print("Invalid import kind\n", .{});
805 return err;
812 const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch {
813 return step.fail("invalid import kind", .{});
806814 };
807815
808816 try writer.print(
......@@ -819,11 +827,11 @@ const WasmDumper = struct {
819827 try parseDumpLimits(reader, writer);
820828 },
821829 .global => {
822 try parseDumpType(std.wasm.Valtype, reader, writer);
830 try parseDumpType(step, std.wasm.Valtype, reader, writer);
823831 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)});
824832 },
825833 .table => {
826 try parseDumpType(std.wasm.RefType, reader, writer);
834 try parseDumpType(step, std.wasm.RefType, reader, writer);
827835 try parseDumpLimits(reader, writer);
828836 },
829837 }
......@@ -838,7 +846,7 @@ const WasmDumper = struct {
838846 .table => {
839847 var i: u32 = 0;
840848 while (i < entries) : (i += 1) {
841 try parseDumpType(std.wasm.RefType, reader, writer);
849 try parseDumpType(step, std.wasm.RefType, reader, writer);
842850 try parseDumpLimits(reader, writer);
843851 }
844852 },
......@@ -851,9 +859,9 @@ const WasmDumper = struct {
851859 .global => {
852860 var i: u32 = 0;
853861 while (i < entries) : (i += 1) {
854 try parseDumpType(std.wasm.Valtype, reader, writer);
862 try parseDumpType(step, std.wasm.Valtype, reader, writer);
855863 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)});
856 try parseDumpInit(reader, writer);
864 try parseDumpInit(step, reader, writer);
857865 }
858866 },
859867 .@"export" => {
......@@ -863,9 +871,8 @@ const WasmDumper = struct {
863871 const name = data[fbs.pos..][0..name_len];
864872 fbs.pos += name_len;
865873 const kind_byte = try std.leb.readULEB128(u8, reader);
866 const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| {
867 std.debug.print("invalid export kind value '{d}'\n", .{kind_byte});
868 return err;
874 const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch {
875 return step.fail("invalid export kind value '{d}'", .{kind_byte});
869876 };
870877 const index = try std.leb.readULEB128(u32, reader);
871878 try writer.print(
......@@ -880,7 +887,7 @@ const WasmDumper = struct {
880887 var i: u32 = 0;
881888 while (i < entries) : (i += 1) {
882889 try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)});
883 try parseDumpInit(reader, writer);
890 try parseDumpInit(step, reader, writer);
884891
885892 const function_indexes = try std.leb.readULEB128(u32, reader);
886893 var function_index: u32 = 0;
......@@ -896,7 +903,7 @@ const WasmDumper = struct {
896903 while (i < entries) : (i += 1) {
897904 const index = try std.leb.readULEB128(u32, reader);
898905 try writer.print("memory index 0x{x}\n", .{index});
899 try parseDumpInit(reader, writer);
906 try parseDumpInit(step, reader, writer);
900907 const size = try std.leb.readULEB128(u32, reader);
901908 try writer.print("size {d}\n", .{size});
902909 try reader.skipBytes(size, .{}); // we do not care about the content of the segments
......@@ -906,11 +913,10 @@ const WasmDumper = struct {
906913 }
907914 }
908915
909 fn parseDumpType(comptime WasmType: type, reader: anytype, writer: anytype) !void {
916 fn parseDumpType(step: *Step, comptime WasmType: type, reader: anytype, writer: anytype) !void {
910917 const type_byte = try reader.readByte();
911 const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| {
912 std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte});
913 return err;
918 const valtype = std.meta.intToEnum(WasmType, type_byte) catch {
919 return step.fail("Invalid wasm type value '{d}'", .{type_byte});
914920 };
915921 try writer.print("type {s}\n", .{@tagName(valtype)});
916922 }
......@@ -925,11 +931,10 @@ const WasmDumper = struct {
925931 }
926932 }
927933
928 fn parseDumpInit(reader: anytype, writer: anytype) !void {
934 fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void {
929935 const byte = try std.leb.readULEB128(u8, reader);
930 const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| {
931 std.debug.print("invalid wasm opcode '{d}'\n", .{byte});
932 return err;
936 const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch {
937 return step.fail("invalid wasm opcode '{d}'", .{byte});
933938 };
934939 switch (opcode) {
935940 .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}),
......@@ -941,14 +946,13 @@ const WasmDumper = struct {
941946 }
942947 const end_opcode = try std.leb.readULEB128(u8, reader);
943948 if (end_opcode != std.wasm.opcode(.end)) {
944 std.debug.print("expected 'end' opcode in init expression\n", .{});
945 return error.MissingEndOpcode;
949 return step.fail("expected 'end' opcode in init expression", .{});
946950 }
947951 }
948952
949 fn parseDumpNames(reader: anytype, writer: anytype, data: []const u8) !void {
953 fn parseDumpNames(step: *Step, reader: anytype, writer: anytype, data: []const u8) !void {
950954 while (reader.context.pos < data.len) {
951 try parseDumpType(std.wasm.NameSubsection, reader, writer);
955 try parseDumpType(step, std.wasm.NameSubsection, reader, writer);
952956 const size = try std.leb.readULEB128(u32, reader);
953957 const entries = try std.leb.readULEB128(u32, reader);
954958 try writer.print(
lib/std/Build/CompileStep.zig+443-376
......@@ -1,7 +1,6 @@
11const builtin = @import("builtin");
22const std = @import("../std.zig");
33const mem = std.mem;
4const log = std.log;
54const fs = std.fs;
65const assert = std.debug.assert;
76const panic = std.debug.panic;
......@@ -22,7 +21,6 @@ const InstallDir = std.Build.InstallDir;
2221const InstallArtifactStep = std.Build.InstallArtifactStep;
2322const GeneratedFile = std.Build.GeneratedFile;
2423const ObjCopyStep = std.Build.ObjCopyStep;
25const EmulatableRunStep = std.Build.EmulatableRunStep;
2624const CheckObjectStep = std.Build.CheckObjectStep;
2725const RunStep = std.Build.RunStep;
2826const OptionsStep = std.Build.OptionsStep;
......@@ -32,7 +30,6 @@ const CompileStep = @This();
3230pub const base_id: Step.Id = .compile;
3331
3432step: Step,
35builder: *std.Build,
3633name: []const u8,
3734target: CrossTarget,
3835target_info: NativeTargetInfo,
......@@ -49,9 +46,9 @@ strip: ?bool,
4946unwind_tables: ?bool,
5047// keep in sync with src/link.zig:CompressDebugSections
5148compress_debug_sections: enum { none, zlib } = .none,
52lib_paths: ArrayList([]const u8),
53rpaths: ArrayList([]const u8),
54framework_dirs: ArrayList([]const u8),
49lib_paths: ArrayList(FileSource),
50rpaths: ArrayList(FileSource),
51framework_dirs: ArrayList(FileSource),
5552frameworks: StringHashMap(FrameworkLinkInfo),
5653verbose_link: bool,
5754verbose_cc: bool,
......@@ -86,7 +83,6 @@ c_std: std.Build.CStd,
8683zig_lib_dir: ?[]const u8,
8784main_pkg_path: ?[]const u8,
8885exec_cmd_args: ?[]const ?[]const u8,
89name_prefix: []const u8,
9086filter: ?[]const u8,
9187test_evented_io: bool = false,
9288test_runner: ?[]const u8,
......@@ -210,10 +206,17 @@ want_lto: ?bool = null,
210206use_llvm: ?bool = null,
211207use_lld: ?bool = null,
212208
209/// This is an advanced setting that can change the intent of this CompileStep.
210/// If this slice has nonzero length, it means that this CompileStep exists to
211/// check for compile errors and return *success* if they match, and failure
212/// otherwise.
213expect_errors: []const []const u8 = &.{},
214
213215output_path_source: GeneratedFile,
214216output_lib_path_source: GeneratedFile,
215217output_h_path_source: GeneratedFile,
216218output_pdb_path_source: GeneratedFile,
219output_dirname_source: GeneratedFile,
217220
218221pub const CSourceFiles = struct {
219222 files: []const []const u8,
......@@ -277,6 +280,7 @@ pub const Options = struct {
277280 kind: Kind,
278281 linkage: ?Linkage = null,
279282 version: ?std.builtin.Version = null,
283 max_rss: usize = 0,
280284};
281285
282286pub const Kind = enum {
......@@ -284,7 +288,6 @@ pub const Kind = enum {
284288 lib,
285289 obj,
286290 @"test",
287 test_exe,
288291};
289292
290293pub const Linkage = enum { dynamic, static };
......@@ -305,18 +308,35 @@ pub const EmitOption = union(enum) {
305308 }
306309};
307310
308pub fn create(builder: *std.Build, options: Options) *CompileStep {
309 const name = builder.dupe(options.name);
310 const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(builder) else null;
311pub fn create(owner: *std.Build, options: Options) *CompileStep {
312 const name = owner.dupe(options.name);
313 const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null;
311314 if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) {
312315 panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name});
313316 }
314317
315 const self = builder.allocator.create(CompileStep) catch @panic("OOM");
318 // Avoid the common case of the step name looking like "zig test test".
319 const name_adjusted = if (options.kind == .@"test" and mem.eql(u8, name, "test"))
320 ""
321 else
322 owner.fmt("{s} ", .{name});
323
324 const step_name = owner.fmt("{s} {s}{s} {s}", .{
325 switch (options.kind) {
326 .exe => "zig build-exe",
327 .lib => "zig build-lib",
328 .obj => "zig build-obj",
329 .@"test" => "zig test",
330 },
331 name_adjusted,
332 @tagName(options.optimize),
333 options.target.zigTriple(owner.allocator) catch @panic("OOM"),
334 });
335
336 const self = owner.allocator.create(CompileStep) catch @panic("OOM");
316337 self.* = CompileStep{
317338 .strip = null,
318339 .unwind_tables = null,
319 .builder = builder,
320340 .verbose_link = false,
321341 .verbose_cc = false,
322342 .optimize = options.optimize,
......@@ -325,29 +345,34 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {
325345 .kind = options.kind,
326346 .root_src = root_src,
327347 .name = name,
328 .frameworks = StringHashMap(FrameworkLinkInfo).init(builder.allocator),
329 .step = Step.init(base_id, name, builder.allocator, make),
348 .frameworks = StringHashMap(FrameworkLinkInfo).init(owner.allocator),
349 .step = Step.init(.{
350 .id = base_id,
351 .name = step_name,
352 .owner = owner,
353 .makeFn = make,
354 .max_rss = options.max_rss,
355 }),
330356 .version = options.version,
331357 .out_filename = undefined,
332 .out_h_filename = builder.fmt("{s}.h", .{name}),
358 .out_h_filename = owner.fmt("{s}.h", .{name}),
333359 .out_lib_filename = undefined,
334 .out_pdb_filename = builder.fmt("{s}.pdb", .{name}),
360 .out_pdb_filename = owner.fmt("{s}.pdb", .{name}),
335361 .major_only_filename = null,
336362 .name_only_filename = null,
337 .modules = std.StringArrayHashMap(*Module).init(builder.allocator),
338 .include_dirs = ArrayList(IncludeDir).init(builder.allocator),
339 .link_objects = ArrayList(LinkObject).init(builder.allocator),
340 .c_macros = ArrayList([]const u8).init(builder.allocator),
341 .lib_paths = ArrayList([]const u8).init(builder.allocator),
342 .rpaths = ArrayList([]const u8).init(builder.allocator),
343 .framework_dirs = ArrayList([]const u8).init(builder.allocator),
344 .installed_headers = ArrayList(*Step).init(builder.allocator),
363 .modules = std.StringArrayHashMap(*Module).init(owner.allocator),
364 .include_dirs = ArrayList(IncludeDir).init(owner.allocator),
365 .link_objects = ArrayList(LinkObject).init(owner.allocator),
366 .c_macros = ArrayList([]const u8).init(owner.allocator),
367 .lib_paths = ArrayList(FileSource).init(owner.allocator),
368 .rpaths = ArrayList(FileSource).init(owner.allocator),
369 .framework_dirs = ArrayList(FileSource).init(owner.allocator),
370 .installed_headers = ArrayList(*Step).init(owner.allocator),
345371 .object_src = undefined,
346372 .c_std = std.Build.CStd.C99,
347373 .zig_lib_dir = null,
348374 .main_pkg_path = null,
349375 .exec_cmd_args = null,
350 .name_prefix = "",
351376 .filter = null,
352377 .test_runner = null,
353378 .disable_stack_probing = false,
......@@ -363,6 +388,7 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {
363388 .output_lib_path_source = GeneratedFile{ .step = &self.step },
364389 .output_h_path_source = GeneratedFile{ .step = &self.step },
365390 .output_pdb_path_source = GeneratedFile{ .step = &self.step },
391 .output_dirname_source = GeneratedFile{ .step = &self.step },
366392
367393 .target_info = NativeTargetInfo.detect(self.target) catch @panic("unhandled error"),
368394 };
......@@ -372,15 +398,16 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {
372398}
373399
374400fn computeOutFileNames(self: *CompileStep) void {
401 const b = self.step.owner;
375402 const target = self.target_info.target;
376403
377 self.out_filename = std.zig.binNameAlloc(self.builder.allocator, .{
404 self.out_filename = std.zig.binNameAlloc(b.allocator, .{
378405 .root_name = self.name,
379406 .target = target,
380407 .output_mode = switch (self.kind) {
381408 .lib => .Lib,
382409 .obj => .Obj,
383 .exe, .@"test", .test_exe => .Exe,
410 .exe, .@"test" => .Exe,
384411 },
385412 .link_mode = if (self.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
386413 .dynamic => .Dynamic,
......@@ -394,30 +421,30 @@ fn computeOutFileNames(self: *CompileStep) void {
394421 self.out_lib_filename = self.out_filename;
395422 } else if (self.version) |version| {
396423 if (target.isDarwin()) {
397 self.major_only_filename = self.builder.fmt("lib{s}.{d}.dylib", .{
424 self.major_only_filename = b.fmt("lib{s}.{d}.dylib", .{
398425 self.name,
399426 version.major,
400427 });
401 self.name_only_filename = self.builder.fmt("lib{s}.dylib", .{self.name});
428 self.name_only_filename = b.fmt("lib{s}.dylib", .{self.name});
402429 self.out_lib_filename = self.out_filename;
403430 } else if (target.os.tag == .windows) {
404 self.out_lib_filename = self.builder.fmt("{s}.lib", .{self.name});
431 self.out_lib_filename = b.fmt("{s}.lib", .{self.name});
405432 } else {
406 self.major_only_filename = self.builder.fmt("lib{s}.so.{d}", .{ self.name, version.major });
407 self.name_only_filename = self.builder.fmt("lib{s}.so", .{self.name});
433 self.major_only_filename = b.fmt("lib{s}.so.{d}", .{ self.name, version.major });
434 self.name_only_filename = b.fmt("lib{s}.so", .{self.name});
408435 self.out_lib_filename = self.out_filename;
409436 }
410437 } else {
411438 if (target.isDarwin()) {
412439 self.out_lib_filename = self.out_filename;
413440 } else if (target.os.tag == .windows) {
414 self.out_lib_filename = self.builder.fmt("{s}.lib", .{self.name});
441 self.out_lib_filename = b.fmt("{s}.lib", .{self.name});
415442 } else {
416443 self.out_lib_filename = self.out_filename;
417444 }
418445 }
419446 if (self.output_dir != null) {
420 self.output_lib_path_source.path = self.builder.pathJoin(
447 self.output_lib_path_source.path = b.pathJoin(
421448 &.{ self.output_dir.?, self.out_lib_filename },
422449 );
423450 }
......@@ -425,17 +452,20 @@ fn computeOutFileNames(self: *CompileStep) void {
425452}
426453
427454pub fn setOutputDir(self: *CompileStep, dir: []const u8) void {
428 self.output_dir = self.builder.dupePath(dir);
455 const b = self.step.owner;
456 self.output_dir = b.dupePath(dir);
429457}
430458
431459pub fn install(self: *CompileStep) void {
432 self.builder.installArtifact(self);
460 const b = self.step.owner;
461 b.installArtifact(self);
433462}
434463
435pub fn installHeader(a: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void {
436 const install_file = a.builder.addInstallHeaderFile(src_path, dest_rel_path);
437 a.builder.getInstallStep().dependOn(&install_file.step);
438 a.installed_headers.append(&install_file.step) catch @panic("OOM");
464pub fn installHeader(cs: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void {
465 const b = cs.step.owner;
466 const install_file = b.addInstallHeaderFile(src_path, dest_rel_path);
467 b.getInstallStep().dependOn(&install_file.step);
468 cs.installed_headers.append(&install_file.step) catch @panic("OOM");
439469}
440470
441471pub const InstallConfigHeaderOptions = struct {
......@@ -449,13 +479,14 @@ pub fn installConfigHeader(
449479 options: InstallConfigHeaderOptions,
450480) void {
451481 const dest_rel_path = options.dest_rel_path orelse config_header.include_path;
452 const install_file = cs.builder.addInstallFileWithDir(
482 const b = cs.step.owner;
483 const install_file = b.addInstallFileWithDir(
453484 .{ .generated = &config_header.output_file },
454485 options.install_dir,
455486 dest_rel_path,
456487 );
457488 install_file.step.dependOn(&config_header.step);
458 cs.builder.getInstallStep().dependOn(&install_file.step);
489 b.getInstallStep().dependOn(&install_file.step);
459490 cs.installed_headers.append(&install_file.step) catch @panic("OOM");
460491}
461492
......@@ -472,91 +503,83 @@ pub fn installHeadersDirectory(
472503}
473504
474505pub fn installHeadersDirectoryOptions(
475 a: *CompileStep,
506 cs: *CompileStep,
476507 options: std.Build.InstallDirStep.Options,
477508) void {
478 const install_dir = a.builder.addInstallDirectory(options);
479 a.builder.getInstallStep().dependOn(&install_dir.step);
480 a.installed_headers.append(&install_dir.step) catch @panic("OOM");
509 const b = cs.step.owner;
510 const install_dir = b.addInstallDirectory(options);
511 b.getInstallStep().dependOn(&install_dir.step);
512 cs.installed_headers.append(&install_dir.step) catch @panic("OOM");
481513}
482514
483pub fn installLibraryHeaders(a: *CompileStep, l: *CompileStep) void {
515pub fn installLibraryHeaders(cs: *CompileStep, l: *CompileStep) void {
484516 assert(l.kind == .lib);
485 const install_step = a.builder.getInstallStep();
517 const b = cs.step.owner;
518 const install_step = b.getInstallStep();
486519 // Copy each element from installed_headers, modifying the builder
487520 // to be the new parent's builder.
488521 for (l.installed_headers.items) |step| {
489522 const step_copy = switch (step.id) {
490523 inline .install_file, .install_dir => |id| blk: {
491524 const T = id.Type();
492 const ptr = a.builder.allocator.create(T) catch @panic("OOM");
525 const ptr = b.allocator.create(T) catch @panic("OOM");
493526 ptr.* = step.cast(T).?.*;
494 ptr.override_source_builder = ptr.builder;
495 ptr.builder = a.builder;
527 ptr.dest_builder = b;
496528 break :blk &ptr.step;
497529 },
498530 else => unreachable,
499531 };
500 a.installed_headers.append(step_copy) catch @panic("OOM");
532 cs.installed_headers.append(step_copy) catch @panic("OOM");
501533 install_step.dependOn(step_copy);
502534 }
503 a.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM");
535 cs.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM");
504536}
505537
506538pub fn addObjCopy(cs: *CompileStep, options: ObjCopyStep.Options) *ObjCopyStep {
539 const b = cs.step.owner;
507540 var copy = options;
508541 if (copy.basename == null) {
509542 if (options.format) |f| {
510 copy.basename = cs.builder.fmt("{s}.{s}", .{ cs.name, @tagName(f) });
543 copy.basename = b.fmt("{s}.{s}", .{ cs.name, @tagName(f) });
511544 } else {
512545 copy.basename = cs.name;
513546 }
514547 }
515 return cs.builder.addObjCopy(cs.getOutputSource(), copy);
548 return b.addObjCopy(cs.getOutputSource(), copy);
516549}
517550
518551/// Deprecated: use `std.Build.addRunArtifact`
519552/// This function will run in the context of the package that created the executable,
520553/// which is undesirable when running an executable provided by a dependency package.
521pub fn run(exe: *CompileStep) *RunStep {
522 return exe.builder.addRunArtifact(exe);
523}
524
525/// Creates an `EmulatableRunStep` with an executable built with `addExecutable`.
526/// Allows running foreign binaries through emulation platforms such as Qemu or Rosetta.
527/// When a binary cannot be ran through emulation or the option is disabled, a warning
528/// will be printed and the binary will *NOT* be ran.
529pub fn runEmulatable(exe: *CompileStep) *EmulatableRunStep {
530 assert(exe.kind == .exe or exe.kind == .test_exe);
531
532 const run_step = EmulatableRunStep.create(exe.builder, exe.builder.fmt("run {s}", .{exe.step.name}), exe);
533 if (exe.vcpkg_bin_path) |path| {
534 RunStep.addPathDirInternal(&run_step.step, exe.builder, path);
535 }
536 return run_step;
554pub fn run(cs: *CompileStep) *RunStep {
555 return cs.step.owner.addRunArtifact(cs);
537556}
538557
539pub fn checkObject(self: *CompileStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep {
540 return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format);
558pub fn checkObject(self: *CompileStep) *CheckObjectStep {
559 return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt);
541560}
542561
543562pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void {
544 self.linker_script = source.dupe(self.builder);
563 const b = self.step.owner;
564 self.linker_script = source.dupe(b);
545565 source.addStepDependencies(&self.step);
546566}
547567
548568pub fn linkFramework(self: *CompileStep, framework_name: []const u8) void {
549 self.frameworks.put(self.builder.dupe(framework_name), .{}) catch @panic("OOM");
569 const b = self.step.owner;
570 self.frameworks.put(b.dupe(framework_name), .{}) catch @panic("OOM");
550571}
551572
552573pub fn linkFrameworkNeeded(self: *CompileStep, framework_name: []const u8) void {
553 self.frameworks.put(self.builder.dupe(framework_name), .{
574 const b = self.step.owner;
575 self.frameworks.put(b.dupe(framework_name), .{
554576 .needed = true,
555577 }) catch @panic("OOM");
556578}
557579
558580pub fn linkFrameworkWeak(self: *CompileStep, framework_name: []const u8) void {
559 self.frameworks.put(self.builder.dupe(framework_name), .{
581 const b = self.step.owner;
582 self.frameworks.put(b.dupe(framework_name), .{
560583 .weak = true,
561584 }) catch @panic("OOM");
562585}
......@@ -595,7 +618,7 @@ pub fn producesPdbFile(self: *CompileStep) bool {
595618 if (!self.target.isWindows() and !self.target.isUefi()) return false;
596619 if (self.target.getObjectFormat() == .c) return false;
597620 if (self.strip == true) return false;
598 return self.isDynamicLibrary() or self.kind == .exe or self.kind == .test_exe;
621 return self.isDynamicLibrary() or self.kind == .exe or self.kind == .@"test";
599622}
600623
601624pub fn linkLibC(self: *CompileStep) void {
......@@ -609,21 +632,24 @@ pub fn linkLibCpp(self: *CompileStep) void {
609632/// If the value is omitted, it is set to 1.
610633/// `name` and `value` need not live longer than the function call.
611634pub fn defineCMacro(self: *CompileStep, name: []const u8, value: ?[]const u8) void {
612 const macro = std.Build.constructCMacro(self.builder.allocator, name, value);
635 const b = self.step.owner;
636 const macro = std.Build.constructCMacro(b.allocator, name, value);
613637 self.c_macros.append(macro) catch @panic("OOM");
614638}
615639
616640/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1.
617641pub fn defineCMacroRaw(self: *CompileStep, name_and_value: []const u8) void {
618 self.c_macros.append(self.builder.dupe(name_and_value)) catch @panic("OOM");
642 const b = self.step.owner;
643 self.c_macros.append(b.dupe(name_and_value)) catch @panic("OOM");
619644}
620645
621646/// This one has no integration with anything, it just puts -lname on the command line.
622647/// Prefer to use `linkSystemLibrary` instead.
623648pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void {
649 const b = self.step.owner;
624650 self.link_objects.append(.{
625651 .system_lib = .{
626 .name = self.builder.dupe(name),
652 .name = b.dupe(name),
627653 .needed = false,
628654 .weak = false,
629655 .use_pkg_config = .no,
......@@ -634,9 +660,10 @@ pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void {
634660/// This one has no integration with anything, it just puts -needed-lname on the command line.
635661/// Prefer to use `linkSystemLibraryNeeded` instead.
636662pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void {
663 const b = self.step.owner;
637664 self.link_objects.append(.{
638665 .system_lib = .{
639 .name = self.builder.dupe(name),
666 .name = b.dupe(name),
640667 .needed = true,
641668 .weak = false,
642669 .use_pkg_config = .no,
......@@ -647,9 +674,10 @@ pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void {
647674/// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the
648675/// command line. Prefer to use `linkSystemLibraryWeak` instead.
649676pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void {
677 const b = self.step.owner;
650678 self.link_objects.append(.{
651679 .system_lib = .{
652 .name = self.builder.dupe(name),
680 .name = b.dupe(name),
653681 .needed = false,
654682 .weak = true,
655683 .use_pkg_config = .no,
......@@ -660,9 +688,10 @@ pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void {
660688/// This links against a system library, exclusively using pkg-config to find the library.
661689/// Prefer to use `linkSystemLibrary` instead.
662690pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void {
691 const b = self.step.owner;
663692 self.link_objects.append(.{
664693 .system_lib = .{
665 .name = self.builder.dupe(lib_name),
694 .name = b.dupe(lib_name),
666695 .needed = false,
667696 .weak = false,
668697 .use_pkg_config = .force,
......@@ -673,9 +702,10 @@ pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8)
673702/// This links against a system library, exclusively using pkg-config to find the library.
674703/// Prefer to use `linkSystemLibraryNeeded` instead.
675704pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void {
705 const b = self.step.owner;
676706 self.link_objects.append(.{
677707 .system_lib = .{
678 .name = self.builder.dupe(lib_name),
708 .name = b.dupe(lib_name),
679709 .needed = true,
680710 .weak = false,
681711 .use_pkg_config = .force,
......@@ -685,14 +715,15 @@ pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []cons
685715
686716/// Run pkg-config for the given library name and parse the output, returning the arguments
687717/// that should be passed to zig to link the given library.
688pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u8 {
718fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u8 {
719 const b = self.step.owner;
689720 const pkg_name = match: {
690721 // First we have to map the library name to pkg config name. Unfortunately,
691722 // there are several examples where this is not straightforward:
692723 // -lSDL2 -> pkg-config sdl2
693724 // -lgdk-3 -> pkg-config gdk-3.0
694725 // -latk-1.0 -> pkg-config atk
695 const pkgs = try getPkgConfigList(self.builder);
726 const pkgs = try getPkgConfigList(b);
696727
697728 // Exact match means instant winner.
698729 for (pkgs) |pkg| {
......@@ -732,7 +763,7 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u
732763 };
733764
734765 var code: u8 = undefined;
735 const stdout = if (self.builder.execAllowFail(&[_][]const u8{
766 const stdout = if (b.execAllowFail(&[_][]const u8{
736767 "pkg-config",
737768 pkg_name,
738769 "--cflags",
......@@ -745,7 +776,7 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u
745776 else => return err,
746777 };
747778
748 var zig_args = ArrayList([]const u8).init(self.builder.allocator);
779 var zig_args = ArrayList([]const u8).init(b.allocator);
749780 defer zig_args.deinit();
750781
751782 var it = mem.tokenize(u8, stdout, " \r\n\t");
......@@ -770,8 +801,8 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u
770801 try zig_args.appendSlice(&[_][]const u8{ "-D", macro });
771802 } else if (mem.startsWith(u8, tok, "-D")) {
772803 try zig_args.append(tok);
773 } else if (self.builder.verbose) {
774 log.warn("Ignoring pkg-config flag '{s}'", .{tok});
804 } else if (b.debug_pkg_config) {
805 return self.step.fail("unknown pkg-config flag '{s}'", .{tok});
775806 }
776807 }
777808
......@@ -794,6 +825,7 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {
794825 needed: bool = false,
795826 weak: bool = false,
796827}) void {
828 const b = self.step.owner;
797829 if (isLibCLibrary(name)) {
798830 self.linkLibC();
799831 return;
......@@ -805,7 +837,7 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {
805837
806838 self.link_objects.append(.{
807839 .system_lib = .{
808 .name = self.builder.dupe(name),
840 .name = b.dupe(name),
809841 .needed = opts.needed,
810842 .weak = opts.weak,
811843 .use_pkg_config = .yes,
......@@ -813,27 +845,31 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {
813845 }) catch @panic("OOM");
814846}
815847
816pub fn setNamePrefix(self: *CompileStep, text: []const u8) void {
817 assert(self.kind == .@"test" or self.kind == .test_exe);
818 self.name_prefix = self.builder.dupe(text);
848pub fn setName(self: *CompileStep, text: []const u8) void {
849 const b = self.step.owner;
850 assert(self.kind == .@"test");
851 self.name = b.dupe(text);
819852}
820853
821854pub fn setFilter(self: *CompileStep, text: ?[]const u8) void {
822 assert(self.kind == .@"test" or self.kind == .test_exe);
823 self.filter = if (text) |t| self.builder.dupe(t) else null;
855 const b = self.step.owner;
856 assert(self.kind == .@"test");
857 self.filter = if (text) |t| b.dupe(t) else null;
824858}
825859
826860pub fn setTestRunner(self: *CompileStep, path: ?[]const u8) void {
827 assert(self.kind == .@"test" or self.kind == .test_exe);
828 self.test_runner = if (path) |p| self.builder.dupePath(p) else null;
861 const b = self.step.owner;
862 assert(self.kind == .@"test");
863 self.test_runner = if (path) |p| b.dupePath(p) else null;
829864}
830865
831866/// Handy when you have many C/C++ source files and want them all to have the same flags.
832867pub fn addCSourceFiles(self: *CompileStep, files: []const []const u8, flags: []const []const u8) void {
833 const c_source_files = self.builder.allocator.create(CSourceFiles) catch @panic("OOM");
868 const b = self.step.owner;
869 const c_source_files = b.allocator.create(CSourceFiles) catch @panic("OOM");
834870
835 const files_copy = self.builder.dupeStrings(files);
836 const flags_copy = self.builder.dupeStrings(flags);
871 const files_copy = b.dupeStrings(files);
872 const flags_copy = b.dupeStrings(flags);
837873
838874 c_source_files.* = .{
839875 .files = files_copy,
......@@ -850,8 +886,9 @@ pub fn addCSourceFile(self: *CompileStep, file: []const u8, flags: []const []con
850886}
851887
852888pub fn addCSourceFileSource(self: *CompileStep, source: CSourceFile) void {
853 const c_source_file = self.builder.allocator.create(CSourceFile) catch @panic("OOM");
854 c_source_file.* = source.dupe(self.builder);
889 const b = self.step.owner;
890 const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM");
891 c_source_file.* = source.dupe(b);
855892 self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM");
856893 source.source.addStepDependencies(&self.step);
857894}
......@@ -865,52 +902,61 @@ pub fn setVerboseCC(self: *CompileStep, value: bool) void {
865902}
866903
867904pub fn overrideZigLibDir(self: *CompileStep, dir_path: []const u8) void {
868 self.zig_lib_dir = self.builder.dupePath(dir_path);
905 const b = self.step.owner;
906 self.zig_lib_dir = b.dupePath(dir_path);
869907}
870908
871909pub fn setMainPkgPath(self: *CompileStep, dir_path: []const u8) void {
872 self.main_pkg_path = self.builder.dupePath(dir_path);
910 const b = self.step.owner;
911 self.main_pkg_path = b.dupePath(dir_path);
873912}
874913
875914pub fn setLibCFile(self: *CompileStep, libc_file: ?FileSource) void {
876 self.libc_file = if (libc_file) |f| f.dupe(self.builder) else null;
915 const b = self.step.owner;
916 self.libc_file = if (libc_file) |f| f.dupe(b) else null;
877917}
878918
879919/// Returns the generated executable, library or object file.
880920/// To run an executable built with zig build, use `run`, or create an install step and invoke it.
881921pub fn getOutputSource(self: *CompileStep) FileSource {
882 return FileSource{ .generated = &self.output_path_source };
922 return .{ .generated = &self.output_path_source };
923}
924
925pub fn getOutputDirectorySource(self: *CompileStep) FileSource {
926 return .{ .generated = &self.output_dirname_source };
883927}
884928
885929/// Returns the generated import library. This function can only be called for libraries.
886930pub fn getOutputLibSource(self: *CompileStep) FileSource {
887931 assert(self.kind == .lib);
888 return FileSource{ .generated = &self.output_lib_path_source };
932 return .{ .generated = &self.output_lib_path_source };
889933}
890934
891935/// Returns the generated header file.
892936/// This function can only be called for libraries or object files which have `emit_h` set.
893937pub fn getOutputHSource(self: *CompileStep) FileSource {
894 assert(self.kind != .exe and self.kind != .test_exe and self.kind != .@"test");
938 assert(self.kind != .exe and self.kind != .@"test");
895939 assert(self.emit_h);
896 return FileSource{ .generated = &self.output_h_path_source };
940 return .{ .generated = &self.output_h_path_source };
897941}
898942
899943/// Returns the generated PDB file. This function can only be called for Windows and UEFI.
900944pub fn getOutputPdbSource(self: *CompileStep) FileSource {
901945 // TODO: Is this right? Isn't PDB for *any* PE/COFF file?
902946 assert(self.target.isWindows() or self.target.isUefi());
903 return FileSource{ .generated = &self.output_pdb_path_source };
947 return .{ .generated = &self.output_pdb_path_source };
904948}
905949
906950pub fn addAssemblyFile(self: *CompileStep, path: []const u8) void {
951 const b = self.step.owner;
907952 self.link_objects.append(.{
908 .assembly_file = .{ .path = self.builder.dupe(path) },
953 .assembly_file = .{ .path = b.dupe(path) },
909954 }) catch @panic("OOM");
910955}
911956
912957pub fn addAssemblyFileSource(self: *CompileStep, source: FileSource) void {
913 const source_duped = source.dupe(self.builder);
958 const b = self.step.owner;
959 const source_duped = source.dupe(b);
914960 self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM");
915961 source_duped.addStepDependencies(&self.step);
916962}
......@@ -920,7 +966,8 @@ pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void {
920966}
921967
922968pub fn addObjectFileSource(self: *CompileStep, source: FileSource) void {
923 self.link_objects.append(.{ .static_path = source.dupe(self.builder) }) catch @panic("OOM");
969 const b = self.step.owner;
970 self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM");
924971 source.addStepDependencies(&self.step);
925972}
926973
......@@ -935,11 +982,13 @@ pub const addLibPath = @compileError("deprecated, use addLibraryPath");
935982pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath");
936983
937984pub fn addSystemIncludePath(self: *CompileStep, path: []const u8) void {
938 self.include_dirs.append(IncludeDir{ .raw_path_system = self.builder.dupe(path) }) catch @panic("OOM");
985 const b = self.step.owner;
986 self.include_dirs.append(IncludeDir{ .raw_path_system = b.dupe(path) }) catch @panic("OOM");
939987}
940988
941989pub fn addIncludePath(self: *CompileStep, path: []const u8) void {
942 self.include_dirs.append(IncludeDir{ .raw_path = self.builder.dupe(path) }) catch @panic("OOM");
990 const b = self.step.owner;
991 self.include_dirs.append(IncludeDir{ .raw_path = b.dupe(path) }) catch @panic("OOM");
943992}
944993
945994pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) void {
......@@ -948,23 +997,42 @@ pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) voi
948997}
949998
950999pub fn addLibraryPath(self: *CompileStep, path: []const u8) void {
951 self.lib_paths.append(self.builder.dupe(path)) catch @panic("OOM");
1000 const b = self.step.owner;
1001 self.lib_paths.append(.{ .path = b.dupe(path) }) catch @panic("OOM");
1002}
1003
1004pub fn addLibraryPathDirectorySource(self: *CompileStep, directory_source: FileSource) void {
1005 self.lib_paths.append(directory_source) catch @panic("OOM");
1006 directory_source.addStepDependencies(&self.step);
9521007}
9531008
9541009pub fn addRPath(self: *CompileStep, path: []const u8) void {
955 self.rpaths.append(self.builder.dupe(path)) catch @panic("OOM");
1010 const b = self.step.owner;
1011 self.rpaths.append(.{ .path = b.dupe(path) }) catch @panic("OOM");
1012}
1013
1014pub fn addRPathDirectorySource(self: *CompileStep, directory_source: FileSource) void {
1015 self.rpaths.append(directory_source) catch @panic("OOM");
1016 directory_source.addStepDependencies(&self.step);
9561017}
9571018
9581019pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void {
959 self.framework_dirs.append(self.builder.dupe(dir_path)) catch @panic("OOM");
1020 const b = self.step.owner;
1021 self.framework_dirs.append(.{ .path = b.dupe(dir_path) }) catch @panic("OOM");
1022}
1023
1024pub fn addFrameworkPathDirectorySource(self: *CompileStep, directory_source: FileSource) void {
1025 self.framework_dirs.append(directory_source) catch @panic("OOM");
1026 directory_source.addStepDependencies(&self.step);
9601027}
9611028
9621029/// Adds a module to be used with `@import` and exposing it in the current
9631030/// package's module table using `name`.
9641031pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void {
965 cs.modules.put(cs.builder.dupe(name), module) catch @panic("OOM");
1032 const b = cs.step.owner;
1033 cs.modules.put(b.dupe(name), module) catch @panic("OOM");
9661034
967 var done = std.AutoHashMap(*Module, void).init(cs.builder.allocator);
1035 var done = std.AutoHashMap(*Module, void).init(b.allocator);
9681036 defer done.deinit();
9691037 cs.addRecursiveBuildDeps(module, &done) catch @panic("OOM");
9701038}
......@@ -972,7 +1040,8 @@ pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void {
9721040/// Adds a module to be used with `@import` without exposing it in the current
9731041/// package's module table.
9741042pub fn addAnonymousModule(cs: *CompileStep, name: []const u8, options: std.Build.CreateModuleOptions) void {
975 const module = cs.builder.createModule(options);
1043 const b = cs.step.owner;
1044 const module = b.createModule(options);
9761045 return addModule(cs, name, module);
9771046}
9781047
......@@ -992,12 +1061,13 @@ fn addRecursiveBuildDeps(cs: *CompileStep, module: *Module, done: *std.AutoHashM
9921061/// If Vcpkg was found on the system, it will be added to include and lib
9931062/// paths for the specified target.
9941063pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void {
1064 const b = self.step.owner;
9951065 // Ideally in the Unattempted case we would call the function recursively
9961066 // after findVcpkgRoot and have only one switch statement, but the compiler
9971067 // cannot resolve the error set.
998 switch (self.builder.vcpkg_root) {
1068 switch (b.vcpkg_root) {
9991069 .unattempted => {
1000 self.builder.vcpkg_root = if (try findVcpkgRoot(self.builder.allocator)) |root|
1070 b.vcpkg_root = if (try findVcpkgRoot(b.allocator)) |root|
10011071 VcpkgRoot{ .found = root }
10021072 else
10031073 .not_found;
......@@ -1006,31 +1076,32 @@ pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void {
10061076 .found => {},
10071077 }
10081078
1009 switch (self.builder.vcpkg_root) {
1079 switch (b.vcpkg_root) {
10101080 .unattempted => unreachable,
10111081 .not_found => return error.VcpkgNotFound,
10121082 .found => |root| {
1013 const allocator = self.builder.allocator;
1083 const allocator = b.allocator;
10141084 const triplet = try self.target.vcpkgTriplet(allocator, if (linkage == .static) .Static else .Dynamic);
1015 defer self.builder.allocator.free(triplet);
1085 defer b.allocator.free(triplet);
10161086
1017 const include_path = self.builder.pathJoin(&.{ root, "installed", triplet, "include" });
1087 const include_path = b.pathJoin(&.{ root, "installed", triplet, "include" });
10181088 errdefer allocator.free(include_path);
10191089 try self.include_dirs.append(IncludeDir{ .raw_path = include_path });
10201090
1021 const lib_path = self.builder.pathJoin(&.{ root, "installed", triplet, "lib" });
1022 try self.lib_paths.append(lib_path);
1091 const lib_path = b.pathJoin(&.{ root, "installed", triplet, "lib" });
1092 try self.lib_paths.append(.{ .path = lib_path });
10231093
1024 self.vcpkg_bin_path = self.builder.pathJoin(&.{ root, "installed", triplet, "bin" });
1094 self.vcpkg_bin_path = b.pathJoin(&.{ root, "installed", triplet, "bin" });
10251095 },
10261096 }
10271097}
10281098
10291099pub fn setExecCmd(self: *CompileStep, args: []const ?[]const u8) void {
1100 const b = self.step.owner;
10301101 assert(self.kind == .@"test");
1031 const duped_args = self.builder.allocator.alloc(?[]u8, args.len) catch @panic("OOM");
1102 const duped_args = b.allocator.alloc(?[]u8, args.len) catch @panic("OOM");
10321103 for (args, 0..) |arg, i| {
1033 duped_args[i] = if (arg) |a| self.builder.dupe(a) else null;
1104 duped_args[i] = if (arg) |a| b.dupe(a) else null;
10341105 }
10351106 self.exec_cmd_args = duped_args;
10361107}
......@@ -1039,22 +1110,27 @@ fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void {
10391110 self.step.dependOn(&other.step);
10401111 self.link_objects.append(.{ .other_step = other }) catch @panic("OOM");
10411112 self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM");
1113
1114 for (other.installed_headers.items) |install_step| {
1115 self.step.dependOn(install_step);
1116 }
10421117}
10431118
10441119fn appendModuleArgs(
10451120 cs: *CompileStep,
10461121 zig_args: *ArrayList([]const u8),
10471122) error{OutOfMemory}!void {
1123 const b = cs.step.owner;
10481124 // First, traverse the whole dependency graph and give every module a unique name, ideally one
10491125 // named after what it's called somewhere in the graph. It will help here to have both a mapping
10501126 // from module to name and a set of all the currently-used names.
1051 var mod_names = std.AutoHashMap(*Module, []const u8).init(cs.builder.allocator);
1052 var names = std.StringHashMap(void).init(cs.builder.allocator);
1127 var mod_names = std.AutoHashMap(*Module, []const u8).init(b.allocator);
1128 var names = std.StringHashMap(void).init(b.allocator);
10531129
10541130 var to_name = std.ArrayList(struct {
10551131 name: []const u8,
10561132 mod: *Module,
1057 }).init(cs.builder.allocator);
1133 }).init(b.allocator);
10581134 {
10591135 var it = cs.modules.iterator();
10601136 while (it.next()) |kv| {
......@@ -1075,7 +1151,7 @@ fn appendModuleArgs(
10751151 if (mod_names.contains(dep.mod)) continue;
10761152
10771153 // We'll use this buffer to store the name we decide on
1078 var buf = try cs.builder.allocator.alloc(u8, dep.name.len + 32);
1154 var buf = try b.allocator.alloc(u8, dep.name.len + 32);
10791155 // First, try just the exposed dependency name
10801156 std.mem.copy(u8, buf, dep.name);
10811157 var name = buf[0..dep.name.len];
......@@ -1112,15 +1188,15 @@ fn appendModuleArgs(
11121188 const mod = kv.key_ptr.*;
11131189 const name = kv.value_ptr.*;
11141190
1115 const deps_str = try constructDepString(cs.builder.allocator, mod_names, mod.dependencies);
1191 const deps_str = try constructDepString(b.allocator, mod_names, mod.dependencies);
11161192 const src = mod.builder.pathFromRoot(mod.source_file.getPath(mod.builder));
11171193 try zig_args.append("--mod");
1118 try zig_args.append(try std.fmt.allocPrint(cs.builder.allocator, "{s}:{s}:{s}", .{ name, deps_str, src }));
1194 try zig_args.append(try std.fmt.allocPrint(b.allocator, "{s}:{s}:{s}", .{ name, deps_str, src }));
11191195 }
11201196 }
11211197
11221198 // Lastly, output the root dependencies
1123 const deps_str = try constructDepString(cs.builder.allocator, mod_names, cs.modules);
1199 const deps_str = try constructDepString(b.allocator, mod_names, cs.modules);
11241200 if (deps_str.len > 0) {
11251201 try zig_args.append("--deps");
11261202 try zig_args.append(deps_str);
......@@ -1150,43 +1226,36 @@ fn constructDepString(
11501226 }
11511227}
11521228
1153fn make(step: *Step) !void {
1229fn make(step: *Step, prog_node: *std.Progress.Node) !void {
1230 const b = step.owner;
11541231 const self = @fieldParentPtr(CompileStep, "step", step);
1155 const builder = self.builder;
11561232
11571233 if (self.root_src == null and self.link_objects.items.len == 0) {
1158 log.err("{s}: linker needs 1 or more objects to link", .{self.step.name});
1159 return error.NeedAnObject;
1234 return step.fail("the linker needs one or more objects to link", .{});
11601235 }
11611236
1162 var zig_args = ArrayList([]const u8).init(builder.allocator);
1237 var zig_args = ArrayList([]const u8).init(b.allocator);
11631238 defer zig_args.deinit();
11641239
1165 try zig_args.append(builder.zig_exe);
1240 try zig_args.append(b.zig_exe);
11661241
11671242 const cmd = switch (self.kind) {
11681243 .lib => "build-lib",
11691244 .exe => "build-exe",
11701245 .obj => "build-obj",
11711246 .@"test" => "test",
1172 .test_exe => "test",
11731247 };
11741248 try zig_args.append(cmd);
11751249
1176 if (builder.color != .auto) {
1177 try zig_args.append("--color");
1178 try zig_args.append(@tagName(builder.color));
1179 }
1180
1181 if (builder.reference_trace) |some| {
1182 try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-freference-trace={d}", .{some}));
1250 if (b.reference_trace) |some| {
1251 try zig_args.append(try std.fmt.allocPrint(b.allocator, "-freference-trace={d}", .{some}));
11831252 }
11841253
11851254 try addFlag(&zig_args, "LLVM", self.use_llvm);
11861255 try addFlag(&zig_args, "LLD", self.use_lld);
11871256
11881257 if (self.target.ofmt) |ofmt| {
1189 try zig_args.append(try std.fmt.allocPrint(builder.allocator, "-ofmt={s}", .{@tagName(ofmt)}));
1258 try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)}));
11901259 }
11911260
11921261 if (self.entry_symbol_name) |entry| {
......@@ -1196,18 +1265,18 @@ fn make(step: *Step) !void {
11961265
11971266 if (self.stack_size) |stack_size| {
11981267 try zig_args.append("--stack");
1199 try zig_args.append(try std.fmt.allocPrint(builder.allocator, "{}", .{stack_size}));
1268 try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size}));
12001269 }
12011270
1202 if (self.root_src) |root_src| try zig_args.append(root_src.getPath(builder));
1271 if (self.root_src) |root_src| try zig_args.append(root_src.getPath(b));
12031272
12041273 // We will add link objects from transitive dependencies, but we want to keep
12051274 // all link objects in the same order provided.
12061275 // This array is used to keep self.link_objects immutable.
12071276 var transitive_deps: TransitiveDeps = .{
1208 .link_objects = ArrayList(LinkObject).init(builder.allocator),
1209 .seen_system_libs = StringHashMap(void).init(builder.allocator),
1210 .seen_steps = std.AutoHashMap(*const Step, void).init(builder.allocator),
1277 .link_objects = ArrayList(LinkObject).init(b.allocator),
1278 .seen_system_libs = StringHashMap(void).init(b.allocator),
1279 .seen_steps = std.AutoHashMap(*const Step, void).init(b.allocator),
12111280 .is_linking_libcpp = self.is_linking_libcpp,
12121281 .is_linking_libc = self.is_linking_libc,
12131282 .frameworks = &self.frameworks,
......@@ -1220,14 +1289,13 @@ fn make(step: *Step) !void {
12201289
12211290 for (transitive_deps.link_objects.items) |link_object| {
12221291 switch (link_object) {
1223 .static_path => |static_path| try zig_args.append(static_path.getPath(builder)),
1292 .static_path => |static_path| try zig_args.append(static_path.getPath(b)),
12241293
12251294 .other_step => |other| switch (other.kind) {
12261295 .exe => @panic("Cannot link with an executable build artifact"),
1227 .test_exe => @panic("Cannot link with an executable build artifact"),
12281296 .@"test" => @panic("Cannot link with a test"),
12291297 .obj => {
1230 try zig_args.append(other.getOutputSource().getPath(builder));
1298 try zig_args.append(other.getOutputSource().getPath(b));
12311299 },
12321300 .lib => l: {
12331301 if (self.isStaticLibrary() and other.isStaticLibrary()) {
......@@ -1235,7 +1303,7 @@ fn make(step: *Step) !void {
12351303 break :l;
12361304 }
12371305
1238 const full_path_lib = other.getOutputLibSource().getPath(builder);
1306 const full_path_lib = other.getOutputLibSource().getPath(b);
12391307 try zig_args.append(full_path_lib);
12401308
12411309 if (other.linkage == Linkage.dynamic and !self.target.isWindows()) {
......@@ -1250,14 +1318,11 @@ fn make(step: *Step) !void {
12501318 .system_lib => |system_lib| {
12511319 const prefix: []const u8 = prefix: {
12521320 if (system_lib.needed) break :prefix "-needed-l";
1253 if (system_lib.weak) {
1254 if (self.target.isDarwin()) break :prefix "-weak-l";
1255 log.warn("Weak library import used for a non-darwin target, this will be converted to normally library import `-lname`", .{});
1256 }
1321 if (system_lib.weak) break :prefix "-weak-l";
12571322 break :prefix "-l";
12581323 };
12591324 switch (system_lib.use_pkg_config) {
1260 .no => try zig_args.append(builder.fmt("{s}{s}", .{ prefix, system_lib.name })),
1325 .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })),
12611326 .yes, .force => {
12621327 if (self.runPkgConfig(system_lib.name)) |args| {
12631328 try zig_args.appendSlice(args);
......@@ -1271,7 +1336,7 @@ fn make(step: *Step) !void {
12711336 .yes => {
12721337 // pkg-config failed, so fall back to linking the library
12731338 // by name directly.
1274 try zig_args.append(builder.fmt("{s}{s}", .{
1339 try zig_args.append(b.fmt("{s}{s}", .{
12751340 prefix,
12761341 system_lib.name,
12771342 }));
......@@ -1294,7 +1359,7 @@ fn make(step: *Step) !void {
12941359 try zig_args.append("--");
12951360 prev_has_extra_flags = false;
12961361 }
1297 try zig_args.append(asm_file.getPath(builder));
1362 try zig_args.append(asm_file.getPath(b));
12981363 },
12991364
13001365 .c_source_file => |c_source_file| {
......@@ -1311,7 +1376,7 @@ fn make(step: *Step) !void {
13111376 }
13121377 try zig_args.append("--");
13131378 }
1314 try zig_args.append(c_source_file.source.getPath(builder));
1379 try zig_args.append(c_source_file.source.getPath(b));
13151380 },
13161381
13171382 .c_source_files => |c_source_files| {
......@@ -1329,7 +1394,7 @@ fn make(step: *Step) !void {
13291394 try zig_args.append("--");
13301395 }
13311396 for (c_source_files.files) |file| {
1332 try zig_args.append(builder.pathFromRoot(file));
1397 try zig_args.append(b.pathFromRoot(file));
13331398 }
13341399 },
13351400 }
......@@ -1345,7 +1410,7 @@ fn make(step: *Step) !void {
13451410
13461411 if (self.image_base) |image_base| {
13471412 try zig_args.append("--image-base");
1348 try zig_args.append(builder.fmt("0x{x}", .{image_base}));
1413 try zig_args.append(b.fmt("0x{x}", .{image_base}));
13491414 }
13501415
13511416 if (self.filter) |filter| {
......@@ -1357,39 +1422,34 @@ fn make(step: *Step) !void {
13571422 try zig_args.append("--test-evented-io");
13581423 }
13591424
1360 if (self.name_prefix.len != 0) {
1361 try zig_args.append("--test-name-prefix");
1362 try zig_args.append(self.name_prefix);
1363 }
1364
13651425 if (self.test_runner) |test_runner| {
13661426 try zig_args.append("--test-runner");
1367 try zig_args.append(builder.pathFromRoot(test_runner));
1427 try zig_args.append(b.pathFromRoot(test_runner));
13681428 }
13691429
1370 for (builder.debug_log_scopes) |log_scope| {
1430 for (b.debug_log_scopes) |log_scope| {
13711431 try zig_args.append("--debug-log");
13721432 try zig_args.append(log_scope);
13731433 }
13741434
1375 if (builder.debug_compile_errors) {
1435 if (b.debug_compile_errors) {
13761436 try zig_args.append("--debug-compile-errors");
13771437 }
13781438
1379 if (builder.verbose_cimport) try zig_args.append("--verbose-cimport");
1380 if (builder.verbose_air) try zig_args.append("--verbose-air");
1381 if (builder.verbose_llvm_ir) try zig_args.append("--verbose-llvm-ir");
1382 if (builder.verbose_link or self.verbose_link) try zig_args.append("--verbose-link");
1383 if (builder.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc");
1384 if (builder.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features");
1439 if (b.verbose_cimport) try zig_args.append("--verbose-cimport");
1440 if (b.verbose_air) try zig_args.append("--verbose-air");
1441 if (b.verbose_llvm_ir) try zig_args.append("--verbose-llvm-ir");
1442 if (b.verbose_link or self.verbose_link) try zig_args.append("--verbose-link");
1443 if (b.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc");
1444 if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features");
13851445
1386 if (self.emit_analysis.getArg(builder, "emit-analysis")) |arg| try zig_args.append(arg);
1387 if (self.emit_asm.getArg(builder, "emit-asm")) |arg| try zig_args.append(arg);
1388 if (self.emit_bin.getArg(builder, "emit-bin")) |arg| try zig_args.append(arg);
1389 if (self.emit_docs.getArg(builder, "emit-docs")) |arg| try zig_args.append(arg);
1390 if (self.emit_implib.getArg(builder, "emit-implib")) |arg| try zig_args.append(arg);
1391 if (self.emit_llvm_bc.getArg(builder, "emit-llvm-bc")) |arg| try zig_args.append(arg);
1392 if (self.emit_llvm_ir.getArg(builder, "emit-llvm-ir")) |arg| try zig_args.append(arg);
1446 if (self.emit_analysis.getArg(b, "emit-analysis")) |arg| try zig_args.append(arg);
1447 if (self.emit_asm.getArg(b, "emit-asm")) |arg| try zig_args.append(arg);
1448 if (self.emit_bin.getArg(b, "emit-bin")) |arg| try zig_args.append(arg);
1449 if (self.emit_docs.getArg(b, "emit-docs")) |arg| try zig_args.append(arg);
1450 if (self.emit_implib.getArg(b, "emit-implib")) |arg| try zig_args.append(arg);
1451 if (self.emit_llvm_bc.getArg(b, "emit-llvm-bc")) |arg| try zig_args.append(arg);
1452 if (self.emit_llvm_ir.getArg(b, "emit-llvm-ir")) |arg| try zig_args.append(arg);
13931453
13941454 if (self.emit_h) try zig_args.append("-femit-h");
13951455
......@@ -1430,31 +1490,31 @@ fn make(step: *Step) !void {
14301490 }
14311491 if (self.link_z_common_page_size) |size| {
14321492 try zig_args.append("-z");
1433 try zig_args.append(builder.fmt("common-page-size={d}", .{size}));
1493 try zig_args.append(b.fmt("common-page-size={d}", .{size}));
14341494 }
14351495 if (self.link_z_max_page_size) |size| {
14361496 try zig_args.append("-z");
1437 try zig_args.append(builder.fmt("max-page-size={d}", .{size}));
1497 try zig_args.append(b.fmt("max-page-size={d}", .{size}));
14381498 }
14391499
14401500 if (self.libc_file) |libc_file| {
14411501 try zig_args.append("--libc");
1442 try zig_args.append(libc_file.getPath(builder));
1443 } else if (builder.libc_file) |libc_file| {
1502 try zig_args.append(libc_file.getPath(b));
1503 } else if (b.libc_file) |libc_file| {
14441504 try zig_args.append("--libc");
14451505 try zig_args.append(libc_file);
14461506 }
14471507
14481508 switch (self.optimize) {
14491509 .Debug => {}, // Skip since it's the default.
1450 else => try zig_args.append(builder.fmt("-O{s}", .{@tagName(self.optimize)})),
1510 else => try zig_args.append(b.fmt("-O{s}", .{@tagName(self.optimize)})),
14511511 }
14521512
14531513 try zig_args.append("--cache-dir");
1454 try zig_args.append(builder.cache_root.path orelse ".");
1514 try zig_args.append(b.cache_root.path orelse ".");
14551515
14561516 try zig_args.append("--global-cache-dir");
1457 try zig_args.append(builder.global_cache_root.path orelse ".");
1517 try zig_args.append(b.global_cache_root.path orelse ".");
14581518
14591519 try zig_args.append("--name");
14601520 try zig_args.append(self.name);
......@@ -1466,11 +1526,11 @@ fn make(step: *Step) !void {
14661526 if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic) {
14671527 if (self.version) |version| {
14681528 try zig_args.append("--version");
1469 try zig_args.append(builder.fmt("{}", .{version}));
1529 try zig_args.append(b.fmt("{}", .{version}));
14701530 }
14711531
14721532 if (self.target.isDarwin()) {
1473 const install_name = self.install_name orelse builder.fmt("@rpath/{s}{s}{s}", .{
1533 const install_name = self.install_name orelse b.fmt("@rpath/{s}{s}{s}", .{
14741534 self.target.libPrefix(),
14751535 self.name,
14761536 self.target.dynamicLibSuffix(),
......@@ -1484,7 +1544,7 @@ fn make(step: *Step) !void {
14841544 try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements });
14851545 }
14861546 if (self.pagezero_size) |pagezero_size| {
1487 const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{pagezero_size});
1547 const size = try std.fmt.allocPrint(b.allocator, "{x}", .{pagezero_size});
14881548 try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size });
14891549 }
14901550 if (self.search_strategy) |strat| switch (strat) {
......@@ -1492,7 +1552,7 @@ fn make(step: *Step) !void {
14921552 .dylibs_first => try zig_args.append("-search_dylibs_first"),
14931553 };
14941554 if (self.headerpad_size) |headerpad_size| {
1495 const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{headerpad_size});
1555 const size = try std.fmt.allocPrint(b.allocator, "{x}", .{headerpad_size});
14961556 try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size });
14971557 }
14981558 if (self.headerpad_max_install_names) {
......@@ -1540,16 +1600,16 @@ fn make(step: *Step) !void {
15401600 try zig_args.append("--export-table");
15411601 }
15421602 if (self.initial_memory) |initial_memory| {
1543 try zig_args.append(builder.fmt("--initial-memory={d}", .{initial_memory}));
1603 try zig_args.append(b.fmt("--initial-memory={d}", .{initial_memory}));
15441604 }
15451605 if (self.max_memory) |max_memory| {
1546 try zig_args.append(builder.fmt("--max-memory={d}", .{max_memory}));
1606 try zig_args.append(b.fmt("--max-memory={d}", .{max_memory}));
15471607 }
15481608 if (self.shared_memory) {
15491609 try zig_args.append("--shared-memory");
15501610 }
15511611 if (self.global_base) |global_base| {
1552 try zig_args.append(builder.fmt("--global-base={d}", .{global_base}));
1612 try zig_args.append(b.fmt("--global-base={d}", .{global_base}));
15531613 }
15541614
15551615 if (self.code_model != .default) {
......@@ -1557,16 +1617,16 @@ fn make(step: *Step) !void {
15571617 try zig_args.append(@tagName(self.code_model));
15581618 }
15591619 if (self.wasi_exec_model) |model| {
1560 try zig_args.append(builder.fmt("-mexec-model={s}", .{@tagName(model)}));
1620 try zig_args.append(b.fmt("-mexec-model={s}", .{@tagName(model)}));
15611621 }
15621622 for (self.export_symbol_names) |symbol_name| {
1563 try zig_args.append(builder.fmt("--export={s}", .{symbol_name}));
1623 try zig_args.append(b.fmt("--export={s}", .{symbol_name}));
15641624 }
15651625
15661626 if (!self.target.isNative()) {
15671627 try zig_args.appendSlice(&.{
1568 "-target", try self.target.zigTriple(builder.allocator),
1569 "-mcpu", try std.Build.serializeCpu(builder.allocator, self.target.getCpu()),
1628 "-target", try self.target.zigTriple(b.allocator),
1629 "-mcpu", try std.Build.serializeCpu(b.allocator, self.target.getCpu()),
15701630 });
15711631
15721632 if (self.target.dynamic_linker.get()) |dynamic_linker| {
......@@ -1577,12 +1637,12 @@ fn make(step: *Step) !void {
15771637
15781638 if (self.linker_script) |linker_script| {
15791639 try zig_args.append("--script");
1580 try zig_args.append(linker_script.getPath(builder));
1640 try zig_args.append(linker_script.getPath(b));
15811641 }
15821642
15831643 if (self.version_script) |version_script| {
15841644 try zig_args.append("--version-script");
1585 try zig_args.append(builder.pathFromRoot(version_script));
1645 try zig_args.append(b.pathFromRoot(version_script));
15861646 }
15871647
15881648 if (self.kind == .@"test") {
......@@ -1595,83 +1655,7 @@ fn make(step: *Step) !void {
15951655 try zig_args.append("--test-cmd-bin");
15961656 }
15971657 }
1598 } else {
1599 const need_cross_glibc = self.target.isGnuLibC() and transitive_deps.is_linking_libc;
1600
1601 switch (builder.host.getExternalExecutor(self.target_info, .{
1602 .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,
1603 .link_libc = transitive_deps.is_linking_libc,
1604 })) {
1605 .native => {},
1606 .bad_dl, .bad_os_or_cpu => {
1607 try zig_args.append("--test-no-exec");
1608 },
1609 .rosetta => if (builder.enable_rosetta) {
1610 try zig_args.append("--test-cmd-bin");
1611 } else {
1612 try zig_args.append("--test-no-exec");
1613 },
1614 .qemu => |bin_name| ok: {
1615 if (builder.enable_qemu) qemu: {
1616 const glibc_dir_arg = if (need_cross_glibc)
1617 builder.glibc_runtimes_dir orelse break :qemu
1618 else
1619 null;
1620 try zig_args.append("--test-cmd");
1621 try zig_args.append(bin_name);
1622 if (glibc_dir_arg) |dir| {
1623 // TODO look into making this a call to `linuxTriple`. This
1624 // needs the directory to be called "i686" rather than
1625 // "x86" which is why we do it manually here.
1626 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
1627 const cpu_arch = self.target.getCpuArch();
1628 const os_tag = self.target.getOsTag();
1629 const abi = self.target.getAbi();
1630 const cpu_arch_name: []const u8 = if (cpu_arch == .x86)
1631 "i686"
1632 else
1633 @tagName(cpu_arch);
1634 const full_dir = try std.fmt.allocPrint(builder.allocator, fmt_str, .{
1635 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
1636 });
1637
1638 try zig_args.append("--test-cmd");
1639 try zig_args.append("-L");
1640 try zig_args.append("--test-cmd");
1641 try zig_args.append(full_dir);
1642 }
1643 try zig_args.append("--test-cmd-bin");
1644 break :ok;
1645 }
1646 try zig_args.append("--test-no-exec");
1647 },
1648 .wine => |bin_name| if (builder.enable_wine) {
1649 try zig_args.append("--test-cmd");
1650 try zig_args.append(bin_name);
1651 try zig_args.append("--test-cmd-bin");
1652 } else {
1653 try zig_args.append("--test-no-exec");
1654 },
1655 .wasmtime => |bin_name| if (builder.enable_wasmtime) {
1656 try zig_args.append("--test-cmd");
1657 try zig_args.append(bin_name);
1658 try zig_args.append("--test-cmd");
1659 try zig_args.append("--dir=.");
1660 try zig_args.append("--test-cmd-bin");
1661 } else {
1662 try zig_args.append("--test-no-exec");
1663 },
1664 .darling => |bin_name| if (builder.enable_darling) {
1665 try zig_args.append("--test-cmd");
1666 try zig_args.append(bin_name);
1667 try zig_args.append("--test-cmd-bin");
1668 } else {
1669 try zig_args.append("--test-no-exec");
1670 },
1671 }
16721658 }
1673 } else if (self.kind == .test_exe) {
1674 try zig_args.append("--test-no-exec");
16751659 }
16761660
16771661 try self.appendModuleArgs(&zig_args);
......@@ -1680,18 +1664,18 @@ fn make(step: *Step) !void {
16801664 switch (include_dir) {
16811665 .raw_path => |include_path| {
16821666 try zig_args.append("-I");
1683 try zig_args.append(builder.pathFromRoot(include_path));
1667 try zig_args.append(b.pathFromRoot(include_path));
16841668 },
16851669 .raw_path_system => |include_path| {
1686 if (builder.sysroot != null) {
1670 if (b.sysroot != null) {
16871671 try zig_args.append("-iwithsysroot");
16881672 } else {
16891673 try zig_args.append("-isystem");
16901674 }
16911675
1692 const resolved_include_path = builder.pathFromRoot(include_path);
1676 const resolved_include_path = b.pathFromRoot(include_path);
16931677
1694 const common_include_path = if (builtin.os.tag == .windows and builder.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: {
1678 const common_include_path = if (builtin.os.tag == .windows and b.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: {
16951679 // We need to check for disk designator and strip it out from dir path so
16961680 // that zig/clang can concat resolved_include_path with sysroot.
16971681 const disk_designator = fs.path.diskDesignatorWindows(resolved_include_path);
......@@ -1707,17 +1691,14 @@ fn make(step: *Step) !void {
17071691 },
17081692 .other_step => |other| {
17091693 if (other.emit_h) {
1710 const h_path = other.getOutputHSource().getPath(builder);
1694 const h_path = other.getOutputHSource().getPath(b);
17111695 try zig_args.append("-isystem");
17121696 try zig_args.append(fs.path.dirname(h_path).?);
17131697 }
17141698 if (other.installed_headers.items.len > 0) {
1715 for (other.installed_headers.items) |install_step| {
1716 try install_step.make();
1717 }
17181699 try zig_args.append("-I");
1719 try zig_args.append(builder.pathJoin(&.{
1720 other.builder.install_prefix, "include",
1700 try zig_args.append(b.pathJoin(&.{
1701 other.step.owner.install_prefix, "include",
17211702 }));
17221703 }
17231704 },
......@@ -1729,33 +1710,35 @@ fn make(step: *Step) !void {
17291710 }
17301711 }
17311712
1732 for (self.lib_paths.items) |lib_path| {
1733 try zig_args.append("-L");
1734 try zig_args.append(lib_path);
1713 for (self.c_macros.items) |c_macro| {
1714 try zig_args.append("-D");
1715 try zig_args.append(c_macro);
17351716 }
17361717
1737 for (self.rpaths.items) |rpath| {
1738 try zig_args.append("-rpath");
1739 try zig_args.append(rpath);
1718 try zig_args.ensureUnusedCapacity(2 * self.lib_paths.items.len);
1719 for (self.lib_paths.items) |lib_path| {
1720 zig_args.appendAssumeCapacity("-L");
1721 zig_args.appendAssumeCapacity(lib_path.getPath2(b, step));
17401722 }
17411723
1742 for (self.c_macros.items) |c_macro| {
1743 try zig_args.append("-D");
1744 try zig_args.append(c_macro);
1724 try zig_args.ensureUnusedCapacity(2 * self.rpaths.items.len);
1725 for (self.rpaths.items) |rpath| {
1726 zig_args.appendAssumeCapacity("-rpath");
1727 zig_args.appendAssumeCapacity(rpath.getPath2(b, step));
17451728 }
17461729
1747 if (self.target.isDarwin()) {
1748 for (self.framework_dirs.items) |dir| {
1749 if (builder.sysroot != null) {
1750 try zig_args.append("-iframeworkwithsysroot");
1751 } else {
1752 try zig_args.append("-iframework");
1753 }
1754 try zig_args.append(dir);
1755 try zig_args.append("-F");
1756 try zig_args.append(dir);
1730 for (self.framework_dirs.items) |directory_source| {
1731 if (b.sysroot != null) {
1732 try zig_args.append("-iframeworkwithsysroot");
1733 } else {
1734 try zig_args.append("-iframework");
17571735 }
1736 try zig_args.append(directory_source.getPath2(b, step));
1737 try zig_args.append("-F");
1738 try zig_args.append(directory_source.getPath2(b, step));
1739 }
17581740
1741 {
17591742 var it = self.frameworks.iterator();
17601743 while (it.next()) |entry| {
17611744 const name = entry.key_ptr.*;
......@@ -1769,29 +1752,45 @@ fn make(step: *Step) !void {
17691752 }
17701753 try zig_args.append(name);
17711754 }
1772 } else {
1773 if (self.framework_dirs.items.len > 0) {
1774 log.info("Framework directories have been added for a non-darwin target, this will have no affect on the build", .{});
1775 }
1776
1777 if (self.frameworks.count() > 0) {
1778 log.info("Frameworks have been added for a non-darwin target, this will have no affect on the build", .{});
1779 }
17801755 }
17811756
1782 if (builder.sysroot) |sysroot| {
1757 if (b.sysroot) |sysroot| {
17831758 try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot });
17841759 }
17851760
1786 for (builder.search_prefixes.items) |search_prefix| {
1787 try zig_args.append("-L");
1788 try zig_args.append(builder.pathJoin(&.{
1789 search_prefix, "lib",
1790 }));
1791 try zig_args.append("-I");
1792 try zig_args.append(builder.pathJoin(&.{
1793 search_prefix, "include",
1794 }));
1761 for (b.search_prefixes.items) |search_prefix| {
1762 var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| {
1763 return step.fail("unable to open prefix directory '{s}': {s}", .{
1764 search_prefix, @errorName(err),
1765 });
1766 };
1767 defer prefix_dir.close();
1768
1769 // Avoid passing -L and -I flags for nonexistent directories.
1770 // This prevents a warning, that should probably be upgraded to an error in Zig's
1771 // CLI parsing code, when the linker sees an -L directory that does not exist.
1772
1773 if (prefix_dir.accessZ("lib", .{})) |_| {
1774 try zig_args.appendSlice(&.{
1775 "-L", try fs.path.join(b.allocator, &.{ search_prefix, "lib" }),
1776 });
1777 } else |err| switch (err) {
1778 error.FileNotFound => {},
1779 else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{
1780 search_prefix, @errorName(e),
1781 }),
1782 }
1783
1784 if (prefix_dir.accessZ("include", .{})) |_| {
1785 try zig_args.appendSlice(&.{
1786 "-I", try fs.path.join(b.allocator, &.{ search_prefix, "include" }),
1787 });
1788 } else |err| switch (err) {
1789 error.FileNotFound => {},
1790 else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{
1791 search_prefix, @errorName(e),
1792 }),
1793 }
17951794 }
17961795
17971796 try addFlag(&zig_args, "valgrind", self.valgrind_support);
......@@ -1800,15 +1799,15 @@ fn make(step: *Step) !void {
18001799
18011800 if (self.zig_lib_dir) |dir| {
18021801 try zig_args.append("--zig-lib-dir");
1803 try zig_args.append(builder.pathFromRoot(dir));
1804 } else if (builder.zig_lib_dir) |dir| {
1802 try zig_args.append(b.pathFromRoot(dir));
1803 } else if (b.zig_lib_dir) |dir| {
18051804 try zig_args.append("--zig-lib-dir");
18061805 try zig_args.append(dir);
18071806 }
18081807
18091808 if (self.main_pkg_path) |dir| {
18101809 try zig_args.append("--main-pkg-path");
1811 try zig_args.append(builder.pathFromRoot(dir));
1810 try zig_args.append(b.pathFromRoot(dir));
18121811 }
18131812
18141813 try addFlag(&zig_args, "PIC", self.force_pic);
......@@ -1830,6 +1829,7 @@ fn make(step: *Step) !void {
18301829 }
18311830
18321831 try zig_args.append("--enable-cache");
1832 try zig_args.append("--listen=-");
18331833
18341834 // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux
18351835 // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and
......@@ -1840,15 +1840,15 @@ fn make(step: *Step) !void {
18401840 args_length += arg.len + 1; // +1 to account for null terminator
18411841 }
18421842 if (args_length >= 30 * 1024) {
1843 try builder.cache_root.handle.makePath("args");
1843 try b.cache_root.handle.makePath("args");
18441844
18451845 const args_to_escape = zig_args.items[2..];
1846 var escaped_args = try ArrayList([]const u8).initCapacity(builder.allocator, args_to_escape.len);
1846 var escaped_args = try ArrayList([]const u8).initCapacity(b.allocator, args_to_escape.len);
18471847 arg_blk: for (args_to_escape) |arg| {
18481848 for (arg, 0..) |c, arg_idx| {
18491849 if (c == '\\' or c == '"') {
18501850 // Slow path for arguments that need to be escaped. We'll need to allocate and copy
1851 var escaped = try ArrayList(u8).initCapacity(builder.allocator, arg.len + 1);
1851 var escaped = try ArrayList(u8).initCapacity(b.allocator, arg.len + 1);
18521852 const writer = escaped.writer();
18531853 try writer.writeAll(arg[0..arg_idx]);
18541854 for (arg[arg_idx..]) |to_escape| {
......@@ -1864,8 +1864,8 @@ fn make(step: *Step) !void {
18641864
18651865 // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with
18661866 // other zig build commands running in parallel.
1867 const partially_quoted = try std.mem.join(builder.allocator, "\" \"", escaped_args.items);
1868 const args = try std.mem.concat(builder.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" });
1867 const partially_quoted = try std.mem.join(b.allocator, "\" \"", escaped_args.items);
1868 const args = try std.mem.concat(b.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" });
18691869
18701870 var args_hash: [Sha256.digest_length]u8 = undefined;
18711871 Sha256.hash(args, &args_hash, .{});
......@@ -1877,28 +1877,35 @@ fn make(step: *Step) !void {
18771877 );
18781878
18791879 const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash;
1880 try builder.cache_root.handle.writeFile(args_file, args);
1880 try b.cache_root.handle.writeFile(args_file, args);
18811881
1882 const resolved_args_file = try mem.concat(builder.allocator, u8, &.{
1882 const resolved_args_file = try mem.concat(b.allocator, u8, &.{
18831883 "@",
1884 try builder.cache_root.join(builder.allocator, &.{args_file}),
1884 try b.cache_root.join(b.allocator, &.{args_file}),
18851885 });
18861886
18871887 zig_args.shrinkRetainingCapacity(2);
18881888 try zig_args.append(resolved_args_file);
18891889 }
18901890
1891 const output_dir_nl = try builder.execFromStep(zig_args.items, &self.step);
1892 const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");
1891 const output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) {
1892 error.NeedCompileErrorCheck => {
1893 assert(self.expect_errors.len != 0);
1894 try checkCompileErrors(self);
1895 return;
1896 },
1897 else => |e| return e,
1898 };
1899 const build_output_dir = fs.path.dirname(output_bin_path).?;
18931900
18941901 if (self.output_dir) |output_dir| {
1895 var src_dir = try std.fs.cwd().openIterableDir(build_output_dir, .{});
1902 var src_dir = try fs.cwd().openIterableDir(build_output_dir, .{});
18961903 defer src_dir.close();
18971904
18981905 // Create the output directory if it doesn't exist.
1899 try std.fs.cwd().makePath(output_dir);
1906 try fs.cwd().makePath(output_dir);
19001907
1901 var dest_dir = try std.fs.cwd().openDir(output_dir, .{});
1908 var dest_dir = try fs.cwd().openDir(output_dir, .{});
19021909 defer dest_dir.close();
19031910
19041911 var it = src_dir.iterate();
......@@ -1922,25 +1929,34 @@ fn make(step: *Step) !void {
19221929
19231930 // Update generated files
19241931 if (self.output_dir != null) {
1925 self.output_path_source.path = builder.pathJoin(
1932 self.output_dirname_source.path = self.output_dir.?;
1933
1934 self.output_path_source.path = b.pathJoin(
19261935 &.{ self.output_dir.?, self.out_filename },
19271936 );
19281937
19291938 if (self.emit_h) {
1930 self.output_h_path_source.path = builder.pathJoin(
1939 self.output_h_path_source.path = b.pathJoin(
19311940 &.{ self.output_dir.?, self.out_h_filename },
19321941 );
19331942 }
19341943
19351944 if (self.target.isWindows() or self.target.isUefi()) {
1936 self.output_pdb_path_source.path = builder.pathJoin(
1945 self.output_pdb_path_source.path = b.pathJoin(
19371946 &.{ self.output_dir.?, self.out_pdb_filename },
19381947 );
19391948 }
19401949 }
19411950
1942 if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and self.version != null and self.target.wantSharedLibSymLinks()) {
1943 try doAtomicSymLinks(builder.allocator, self.getOutputSource().getPath(builder), self.major_only_filename.?, self.name_only_filename.?);
1951 if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and
1952 self.version != null and self.target.wantSharedLibSymLinks())
1953 {
1954 try doAtomicSymLinks(
1955 step,
1956 self.getOutputSource().getPath(b),
1957 self.major_only_filename.?,
1958 self.name_only_filename.?,
1959 );
19441960 }
19451961}
19461962
......@@ -1982,30 +1998,27 @@ fn findVcpkgRoot(allocator: Allocator) !?[]const u8 {
19821998}
19831999
19842000pub fn doAtomicSymLinks(
1985 allocator: Allocator,
2001 step: *Step,
19862002 output_path: []const u8,
19872003 filename_major_only: []const u8,
19882004 filename_name_only: []const u8,
19892005) !void {
2006 const arena = step.owner.allocator;
19902007 const out_dir = fs.path.dirname(output_path) orelse ".";
19912008 const out_basename = fs.path.basename(output_path);
19922009 // sym link for libfoo.so.1 to libfoo.so.1.2.3
1993 const major_only_path = try fs.path.join(
1994 allocator,
1995 &[_][]const u8{ out_dir, filename_major_only },
1996 );
1997 fs.atomicSymLink(allocator, out_basename, major_only_path) catch |err| {
1998 log.err("Unable to symlink {s} -> {s}", .{ major_only_path, out_basename });
1999 return err;
2010 const major_only_path = try fs.path.join(arena, &.{ out_dir, filename_major_only });
2011 fs.atomicSymLink(arena, out_basename, major_only_path) catch |err| {
2012 return step.fail("unable to symlink {s} -> {s}: {s}", .{
2013 major_only_path, out_basename, @errorName(err),
2014 });
20002015 };
20012016 // sym link for libfoo.so to libfoo.so.1
2002 const name_only_path = try fs.path.join(
2003 allocator,
2004 &[_][]const u8{ out_dir, filename_name_only },
2005 );
2006 fs.atomicSymLink(allocator, filename_major_only, name_only_path) catch |err| {
2007 log.err("Unable to symlink {s} -> {s}", .{ name_only_path, filename_major_only });
2008 return err;
2017 const name_only_path = try fs.path.join(arena, &.{ out_dir, filename_name_only });
2018 fs.atomicSymLink(arena, filename_major_only, name_only_path) catch |err| {
2019 return step.fail("Unable to symlink {s} -> {s}: {s}", .{
2020 name_only_path, filename_major_only, @errorName(err),
2021 });
20092022 };
20102023}
20112024
......@@ -2117,3 +2130,57 @@ const TransitiveDeps = struct {
21172130 }
21182131 }
21192132};
2133
2134fn checkCompileErrors(self: *CompileStep) !void {
2135 // Clear this field so that it does not get printed by the build runner.
2136 const actual_eb = self.step.result_error_bundle;
2137 self.step.result_error_bundle = std.zig.ErrorBundle.empty;
2138
2139 const arena = self.step.owner.allocator;
2140
2141 var actual_stderr_list = std.ArrayList(u8).init(arena);
2142 try actual_eb.renderToWriter(.{
2143 .ttyconf = .no_color,
2144 .include_reference_trace = false,
2145 .include_source_line = false,
2146 }, actual_stderr_list.writer());
2147 const actual_stderr = try actual_stderr_list.toOwnedSlice();
2148
2149 // Render the expected lines into a string that we can compare verbatim.
2150 var expected_generated = std.ArrayList(u8).init(arena);
2151
2152 var actual_line_it = mem.split(u8, actual_stderr, "\n");
2153 for (self.expect_errors) |expect_line| {
2154 const actual_line = actual_line_it.next() orelse {
2155 try expected_generated.appendSlice(expect_line);
2156 try expected_generated.append('\n');
2157 continue;
2158 };
2159 if (mem.endsWith(u8, actual_line, expect_line)) {
2160 try expected_generated.appendSlice(actual_line);
2161 try expected_generated.append('\n');
2162 continue;
2163 }
2164 if (mem.startsWith(u8, expect_line, ":?:?: ")) {
2165 if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) {
2166 try expected_generated.appendSlice(actual_line);
2167 try expected_generated.append('\n');
2168 continue;
2169 }
2170 }
2171 try expected_generated.appendSlice(expect_line);
2172 try expected_generated.append('\n');
2173 }
2174
2175 if (mem.eql(u8, expected_generated.items, actual_stderr)) return;
2176
2177 // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile
2178 return self.step.fail(
2179 \\
2180 \\========= expected: =====================
2181 \\{s}
2182 \\========= but found: ====================
2183 \\{s}
2184 \\=========================================
2185 , .{ expected_generated.items, actual_stderr });
2186}
lib/std/Build/ConfigHeaderStep.zig+82-73
......@@ -1,9 +1,3 @@
1const std = @import("../std.zig");
2const ConfigHeaderStep = @This();
3const Step = std.Build.Step;
4
5pub const base_id: Step.Id = .config_header;
6
71pub const Style = union(enum) {
82 /// The configure format supported by autotools. It uses `#undef foo` to
93 /// mark lines that can be substituted with different values.
......@@ -34,7 +28,6 @@ pub const Value = union(enum) {
3428};
3529
3630step: Step,
37builder: *std.Build,
3831values: std.StringArrayHashMap(Value),
3932output_file: std.Build.GeneratedFile,
4033
......@@ -42,43 +35,57 @@ style: Style,
4235max_bytes: usize,
4336include_path: []const u8,
4437
38pub const base_id: Step.Id = .config_header;
39
4540pub const Options = struct {
4641 style: Style = .blank,
4742 max_bytes: usize = 2 * 1024 * 1024,
4843 include_path: ?[]const u8 = null,
44 first_ret_addr: ?usize = null,
4945};
5046
51pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep {
52 const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM");
53 const name = if (options.style.getFileSource()) |s|
54 builder.fmt("configure {s} header {s}", .{ @tagName(options.style), s.getDisplayName() })
55 else
56 builder.fmt("configure {s} header", .{@tagName(options.style)});
57 self.* = .{
58 .builder = builder,
59 .step = Step.init(base_id, name, builder.allocator, make),
60 .style = options.style,
61 .values = std.StringArrayHashMap(Value).init(builder.allocator),
47pub fn create(owner: *std.Build, options: Options) *ConfigHeaderStep {
48 const self = owner.allocator.create(ConfigHeaderStep) catch @panic("OOM");
6249
63 .max_bytes = options.max_bytes,
64 .include_path = "config.h",
65 .output_file = .{ .step = &self.step },
66 };
50 var include_path: []const u8 = "config.h";
6751
6852 if (options.style.getFileSource()) |s| switch (s) {
6953 .path => |p| {
7054 const basename = std.fs.path.basename(p);
7155 if (std.mem.endsWith(u8, basename, ".h.in")) {
72 self.include_path = basename[0 .. basename.len - 3];
56 include_path = basename[0 .. basename.len - 3];
7357 }
7458 },
7559 else => {},
7660 };
7761
78 if (options.include_path) |include_path| {
79 self.include_path = include_path;
62 if (options.include_path) |p| {
63 include_path = p;
8064 }
8165
66 const name = if (options.style.getFileSource()) |s|
67 owner.fmt("configure {s} header {s} to {s}", .{
68 @tagName(options.style), s.getDisplayName(), include_path,
69 })
70 else
71 owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path });
72
73 self.* = .{
74 .step = Step.init(.{
75 .id = base_id,
76 .name = name,
77 .owner = owner,
78 .makeFn = make,
79 .first_ret_addr = options.first_ret_addr orelse @returnAddress(),
80 }),
81 .style = options.style,
82 .values = std.StringArrayHashMap(Value).init(owner.allocator),
83
84 .max_bytes = options.max_bytes,
85 .include_path = include_path,
86 .output_file = .{ .step = &self.step },
87 };
88
8289 return self;
8390}
8491
......@@ -146,26 +153,20 @@ fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v
146153 }
147154}
148155
149fn make(step: *Step) !void {
156fn make(step: *Step, prog_node: *std.Progress.Node) !void {
157 _ = prog_node;
158 const b = step.owner;
150159 const self = @fieldParentPtr(ConfigHeaderStep, "step", step);
151 const gpa = self.builder.allocator;
152
153 // The cache is used here not really as a way to speed things up - because writing
154 // the data to a file would probably be very fast - but as a way to find a canonical
155 // location to put build artifacts.
160 const gpa = b.allocator;
161 const arena = b.allocator;
156162
157 // If, for example, a hard-coded path was used as the location to put ConfigHeaderStep
158 // files, then two ConfigHeaderStep executing in parallel might clobber each other.
163 var man = b.cache.obtain();
164 defer man.deinit();
159165
160 // TODO port the cache system from the compiler to zig std lib. Until then
161 // we construct the path directly, and no "cache hit" detection happens;
162 // the files are always written.
163 // Note there is very similar code over in WriteFileStep
164 const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);
165166 // Random bytes to make ConfigHeaderStep unique. Refresh this with new
166167 // random bytes when ConfigHeaderStep implementation is modified in a
167168 // non-backwards-compatible way.
168 var hash = Hasher.init("PGuDTpidxyMqnkGM");
169 man.hash.add(@as(u32, 0xdef08d23));
169170
170171 var output = std.ArrayList(u8).init(gpa);
171172 defer output.deinit();
......@@ -177,15 +178,15 @@ fn make(step: *Step) !void {
177178 switch (self.style) {
178179 .autoconf => |file_source| {
179180 try output.appendSlice(c_generated_line);
180 const src_path = file_source.getPath(self.builder);
181 const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
182 try render_autoconf(contents, &output, self.values, src_path);
181 const src_path = file_source.getPath(b);
182 const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes);
183 try render_autoconf(step, contents, &output, self.values, src_path);
183184 },
184185 .cmake => |file_source| {
185186 try output.appendSlice(c_generated_line);
186 const src_path = file_source.getPath(self.builder);
187 const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
188 try render_cmake(contents, &output, self.values, src_path);
187 const src_path = file_source.getPath(b);
188 const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes);
189 try render_cmake(step, contents, &output, self.values, src_path);
189190 },
190191 .blank => {
191192 try output.appendSlice(c_generated_line);
......@@ -197,43 +198,44 @@ fn make(step: *Step) !void {
197198 },
198199 }
199200
200 hash.update(output.items);
201 man.hash.addBytes(output.items);
201202
202 var digest: [16]u8 = undefined;
203 hash.final(&digest);
204 var hash_basename: [digest.len * 2]u8 = undefined;
205 _ = std.fmt.bufPrint(
206 &hash_basename,
207 "{s}",
208 .{std.fmt.fmtSliceHexLower(&digest)},
209 ) catch unreachable;
203 if (try step.cacheHit(&man)) {
204 const digest = man.final();
205 self.output_file.path = try b.cache_root.join(arena, &.{
206 "o", &digest, self.include_path,
207 });
208 return;
209 }
210210
211 const output_dir = try self.builder.cache_root.join(gpa, &.{ "o", &hash_basename });
211 const digest = man.final();
212212
213213 // If output_path has directory parts, deal with them. Example:
214214 // output_dir is zig-cache/o/HASH
215215 // output_path is libavutil/avconfig.h
216216 // We want to open directory zig-cache/o/HASH/libavutil/
217217 // but keep output_dir as zig-cache/o/HASH for -I include
218 const sub_dir_path = if (std.fs.path.dirname(self.include_path)) |d|
219 try std.fs.path.join(gpa, &.{ output_dir, d })
220 else
221 output_dir;
218 const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, self.include_path });
219 const sub_path_dirname = std.fs.path.dirname(sub_path).?;
222220
223 var dir = std.fs.cwd().makeOpenPath(sub_dir_path, .{}) catch |err| {
224 std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });
225 return err;
221 b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
222 return step.fail("unable to make path '{}{s}': {s}", .{
223 b.cache_root, sub_path_dirname, @errorName(err),
224 });
226225 };
227 defer dir.close();
228226
229 try dir.writeFile(std.fs.path.basename(self.include_path), output.items);
227 b.cache_root.handle.writeFile(sub_path, output.items) catch |err| {
228 return step.fail("unable to write file '{}{s}': {s}", .{
229 b.cache_root, sub_path, @errorName(err),
230 });
231 };
230232
231 self.output_file.path = try std.fs.path.join(self.builder.allocator, &.{
232 output_dir, self.include_path,
233 });
233 self.output_file.path = try b.cache_root.join(arena, &.{sub_path});
234 try man.writeManifest();
234235}
235236
236237fn render_autoconf(
238 step: *Step,
237239 contents: []const u8,
238240 output: *std.ArrayList(u8),
239241 values: std.StringArrayHashMap(Value),
......@@ -260,7 +262,7 @@ fn render_autoconf(
260262 }
261263 const name = it.rest();
262264 const kv = values_copy.fetchSwapRemove(name) orelse {
263 std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{
265 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
264266 src_path, line_index + 1, name,
265267 });
266268 any_errors = true;
......@@ -270,15 +272,17 @@ fn render_autoconf(
270272 }
271273
272274 for (values_copy.keys()) |name| {
273 std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name });
275 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
276 any_errors = true;
274277 }
275278
276279 if (any_errors) {
277 return error.HeaderConfigFailed;
280 return error.MakeFailed;
278281 }
279282}
280283
281284fn render_cmake(
285 step: *Step,
282286 contents: []const u8,
283287 output: *std.ArrayList(u8),
284288 values: std.StringArrayHashMap(Value),
......@@ -304,14 +308,14 @@ fn render_cmake(
304308 continue;
305309 }
306310 const name = it.next() orelse {
307 std.debug.print("{s}:{d}: error: missing define name\n", .{
311 try step.addError("{s}:{d}: error: missing define name", .{
308312 src_path, line_index + 1,
309313 });
310314 any_errors = true;
311315 continue;
312316 };
313317 const kv = values_copy.fetchSwapRemove(name) orelse {
314 std.debug.print("{s}:{d}: error: unspecified config header value: '{s}'\n", .{
318 try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{
315319 src_path, line_index + 1, name,
316320 });
317321 any_errors = true;
......@@ -321,7 +325,8 @@ fn render_cmake(
321325 }
322326
323327 for (values_copy.keys()) |name| {
324 std.debug.print("{s}: error: config header value unused: '{s}'\n", .{ src_path, name });
328 try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name });
329 any_errors = true;
325330 }
326331
327332 if (any_errors) {
......@@ -426,3 +431,7 @@ fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !
426431 },
427432 }
428433}
434
435const std = @import("../std.zig");
436const ConfigHeaderStep = @This();
437const Step = std.Build.Step;
lib/std/Build/EmulatableRunStep.zig deleted-213
......@@ -1,213 +0,0 @@
1//! Unlike `RunStep` this step will provide emulation, when enabled, to run foreign binaries.
2//! When a binary is foreign, but emulation for the target is disabled, the specified binary
3//! will not be run and therefore also not validated against its output.
4//! This step can be useful when wishing to run a built binary on multiple platforms,
5//! without having to verify if it's possible to be ran against.
6
7const std = @import("../std.zig");
8const Step = std.Build.Step;
9const CompileStep = std.Build.CompileStep;
10const RunStep = std.Build.RunStep;
11
12const fs = std.fs;
13const process = std.process;
14const EnvMap = process.EnvMap;
15
16const EmulatableRunStep = @This();
17
18pub const base_id = .emulatable_run;
19
20const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
21
22step: Step,
23builder: *std.Build,
24
25/// The artifact (executable) to be run by this step
26exe: *CompileStep,
27
28/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
29expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },
30
31/// Override this field to modify the environment
32env_map: ?*EnvMap,
33
34/// Set this to modify the current working directory
35cwd: ?[]const u8,
36
37stdout_action: RunStep.StdIoAction = .inherit,
38stderr_action: RunStep.StdIoAction = .inherit,
39
40/// When set to true, hides the warning of skipping a foreign binary which cannot be run on the host
41/// or through emulation.
42hide_foreign_binaries_warning: bool,
43
44/// Creates a step that will execute the given artifact. This step will allow running the
45/// binary through emulation when any of the emulation options such as `enable_rosetta` are set to true.
46/// When set to false, and the binary is foreign, running the executable is skipped.
47/// Asserts given artifact is an executable.
48pub fn create(builder: *std.Build, name: []const u8, artifact: *CompileStep) *EmulatableRunStep {
49 std.debug.assert(artifact.kind == .exe or artifact.kind == .test_exe);
50 const self = builder.allocator.create(EmulatableRunStep) catch @panic("OOM");
51
52 const option_name = "hide-foreign-warnings";
53 const hide_warnings = if (builder.available_options_map.get(option_name) == null) warn: {
54 break :warn builder.option(bool, option_name, "Hide the warning when a foreign binary which is incompatible is skipped") orelse false;
55 } else false;
56
57 self.* = .{
58 .builder = builder,
59 .step = Step.init(.emulatable_run, name, builder.allocator, make),
60 .exe = artifact,
61 .env_map = null,
62 .cwd = null,
63 .hide_foreign_binaries_warning = hide_warnings,
64 };
65 self.step.dependOn(&artifact.step);
66
67 return self;
68}
69
70fn make(step: *Step) !void {
71 const self = @fieldParentPtr(EmulatableRunStep, "step", step);
72 const host_info = self.builder.host;
73
74 var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
75 defer argv_list.deinit();
76
77 const need_cross_glibc = self.exe.target.isGnuLibC() and self.exe.is_linking_libc;
78 switch (host_info.getExternalExecutor(self.exe.target_info, .{
79 .qemu_fixes_dl = need_cross_glibc and self.builder.glibc_runtimes_dir != null,
80 .link_libc = self.exe.is_linking_libc,
81 })) {
82 .native => {},
83 .rosetta => if (!self.builder.enable_rosetta) return warnAboutForeignBinaries(self),
84 .wine => |bin_name| if (self.builder.enable_wine) {
85 try argv_list.append(bin_name);
86 } else return,
87 .qemu => |bin_name| if (self.builder.enable_qemu) {
88 const glibc_dir_arg = if (need_cross_glibc)
89 self.builder.glibc_runtimes_dir orelse return
90 else
91 null;
92 try argv_list.append(bin_name);
93 if (glibc_dir_arg) |dir| {
94 // TODO look into making this a call to `linuxTriple`. This
95 // needs the directory to be called "i686" rather than
96 // "x86" which is why we do it manually here.
97 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
98 const cpu_arch = self.exe.target.getCpuArch();
99 const os_tag = self.exe.target.getOsTag();
100 const abi = self.exe.target.getAbi();
101 const cpu_arch_name: []const u8 = if (cpu_arch == .x86)
102 "i686"
103 else
104 @tagName(cpu_arch);
105 const full_dir = try std.fmt.allocPrint(self.builder.allocator, fmt_str, .{
106 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
107 });
108
109 try argv_list.append("-L");
110 try argv_list.append(full_dir);
111 }
112 } else return warnAboutForeignBinaries(self),
113 .darling => |bin_name| if (self.builder.enable_darling) {
114 try argv_list.append(bin_name);
115 } else return warnAboutForeignBinaries(self),
116 .wasmtime => |bin_name| if (self.builder.enable_wasmtime) {
117 try argv_list.append(bin_name);
118 try argv_list.append("--dir=.");
119 } else return warnAboutForeignBinaries(self),
120 else => return warnAboutForeignBinaries(self),
121 }
122
123 if (self.exe.target.isWindows()) {
124 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
125 RunStep.addPathForDynLibsInternal(&self.step, self.builder, self.exe);
126 }
127
128 const executable_path = self.exe.installed_path orelse self.exe.getOutputSource().getPath(self.builder);
129 try argv_list.append(executable_path);
130
131 try RunStep.runCommand(
132 argv_list.items,
133 self.builder,
134 self.expected_term,
135 self.stdout_action,
136 self.stderr_action,
137 .Inherit,
138 self.env_map,
139 self.cwd,
140 false,
141 );
142}
143
144pub fn expectStdErrEqual(self: *EmulatableRunStep, bytes: []const u8) void {
145 self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) };
146}
147
148pub fn expectStdOutEqual(self: *EmulatableRunStep, bytes: []const u8) void {
149 self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) };
150}
151
152fn warnAboutForeignBinaries(step: *EmulatableRunStep) void {
153 if (step.hide_foreign_binaries_warning) return;
154 const builder = step.builder;
155 const artifact = step.exe;
156
157 const host_name = builder.host.target.zigTriple(builder.allocator) catch @panic("unhandled error");
158 const foreign_name = artifact.target.zigTriple(builder.allocator) catch @panic("unhandled error");
159 const target_info = std.zig.system.NativeTargetInfo.detect(artifact.target) catch @panic("unhandled error");
160 const need_cross_glibc = artifact.target.isGnuLibC() and artifact.is_linking_libc;
161 switch (builder.host.getExternalExecutor(target_info, .{
162 .qemu_fixes_dl = need_cross_glibc and builder.glibc_runtimes_dir != null,
163 .link_libc = artifact.is_linking_libc,
164 })) {
165 .native => unreachable,
166 .bad_dl => |foreign_dl| {
167 const host_dl = builder.host.dynamic_linker.get() orelse "(none)";
168 std.debug.print("the host system does not appear to be capable of executing binaries from the target because the host dynamic linker is '{s}', while the target dynamic linker is '{s}'. Consider setting the dynamic linker as '{s}'.\n", .{
169 host_dl, foreign_dl, host_dl,
170 });
171 },
172 .bad_os_or_cpu => {
173 std.debug.print("the host system ({s}) does not appear to be capable of executing binaries from the target ({s}).\n", .{
174 host_name, foreign_name,
175 });
176 },
177 .darling => if (!builder.enable_darling) {
178 std.debug.print(
179 "the host system ({s}) does not appear to be capable of executing binaries " ++
180 "from the target ({s}). Consider enabling darling.\n",
181 .{ host_name, foreign_name },
182 );
183 },
184 .rosetta => if (!builder.enable_rosetta) {
185 std.debug.print(
186 "the host system ({s}) does not appear to be capable of executing binaries " ++
187 "from the target ({s}). Consider enabling rosetta.\n",
188 .{ host_name, foreign_name },
189 );
190 },
191 .wine => if (!builder.enable_wine) {
192 std.debug.print(
193 "the host system ({s}) does not appear to be capable of executing binaries " ++
194 "from the target ({s}). Consider enabling wine.\n",
195 .{ host_name, foreign_name },
196 );
197 },
198 .qemu => if (!builder.enable_qemu) {
199 std.debug.print(
200 "the host system ({s}) does not appear to be capable of executing binaries " ++
201 "from the target ({s}). Consider enabling qemu.\n",
202 .{ host_name, foreign_name },
203 );
204 },
205 .wasmtime => {
206 std.debug.print(
207 "the host system ({s}) does not appear to be capable of executing binaries " ++
208 "from the target ({s}). Consider enabling wasmtime.\n",
209 .{ host_name, foreign_name },
210 );
211 },
212 }
213}
lib/std/Build/FmtStep.zig+63-22
......@@ -1,32 +1,73 @@
1const std = @import("../std.zig");
2const Step = std.Build.Step;
3const FmtStep = @This();
1//! This step has two modes:
2//! * Modify mode: directly modify source files, formatting them in place.
3//! * Check mode: fail the step if a non-conforming file is found.
4
5step: Step,
6paths: []const []const u8,
7exclude_paths: []const []const u8,
8check: bool,
49
510pub const base_id = .fmt;
611
7step: Step,
8builder: *std.Build,
9argv: [][]const u8,
10
11pub fn create(builder: *std.Build, paths: []const []const u8) *FmtStep {
12 const self = builder.allocator.create(FmtStep) catch @panic("OOM");
13 const name = "zig fmt";
14 self.* = FmtStep{
15 .step = Step.init(.fmt, name, builder.allocator, make),
16 .builder = builder,
17 .argv = builder.allocator.alloc([]u8, paths.len + 2) catch @panic("OOM"),
18 };
12pub const Options = struct {
13 paths: []const []const u8 = &.{},
14 exclude_paths: []const []const u8 = &.{},
15 /// If true, fails the build step when any non-conforming files are encountered.
16 check: bool = false,
17};
1918
20 self.argv[0] = builder.zig_exe;
21 self.argv[1] = "fmt";
22 for (paths, 0..) |path, i| {
23 self.argv[2 + i] = builder.pathFromRoot(path);
24 }
19pub fn create(owner: *std.Build, options: Options) *FmtStep {
20 const self = owner.allocator.create(FmtStep) catch @panic("OOM");
21 const name = if (options.check) "zig fmt --check" else "zig fmt";
22 self.* = .{
23 .step = Step.init(.{
24 .id = base_id,
25 .name = name,
26 .owner = owner,
27 .makeFn = make,
28 }),
29 .paths = options.paths,
30 .exclude_paths = options.exclude_paths,
31 .check = options.check,
32 };
2533 return self;
2634}
2735
28fn make(step: *Step) !void {
36fn make(step: *Step, prog_node: *std.Progress.Node) !void {
37 // zig fmt is fast enough that no progress is needed.
38 _ = prog_node;
39
40 // TODO: if check=false, this means we are modifying source files in place, which
41 // is an operation that could race against other operations also modifying source files
42 // in place. In this case, this step should obtain a write lock while making those
43 // modifications.
44
45 const b = step.owner;
46 const arena = b.allocator;
2947 const self = @fieldParentPtr(FmtStep, "step", step);
3048
31 return self.builder.spawnChild(self.argv);
49 var argv: std.ArrayListUnmanaged([]const u8) = .{};
50 try argv.ensureUnusedCapacity(arena, 2 + 1 + self.paths.len + 2 * self.exclude_paths.len);
51
52 argv.appendAssumeCapacity(b.zig_exe);
53 argv.appendAssumeCapacity("fmt");
54
55 if (self.check) {
56 argv.appendAssumeCapacity("--check");
57 }
58
59 for (self.paths) |p| {
60 argv.appendAssumeCapacity(b.pathFromRoot(p));
61 }
62
63 for (self.exclude_paths) |p| {
64 argv.appendAssumeCapacity("--exclude");
65 argv.appendAssumeCapacity(b.pathFromRoot(p));
66 }
67
68 return step.evalChildProcess(argv.items);
3269}
70
71const std = @import("../std.zig");
72const Step = std.Build.Step;
73const FmtStep = @This();
lib/std/Build/InstallArtifactStep.zig+77-27
......@@ -3,83 +3,133 @@ const Step = std.Build.Step;
33const CompileStep = std.Build.CompileStep;
44const InstallDir = std.Build.InstallDir;
55const InstallArtifactStep = @This();
6const fs = std.fs;
67
78pub const base_id = .install_artifact;
89
910step: Step,
10builder: *std.Build,
11dest_builder: *std.Build,
1112artifact: *CompileStep,
1213dest_dir: InstallDir,
1314pdb_dir: ?InstallDir,
1415h_dir: ?InstallDir,
16/// If non-null, adds additional path components relative to dest_dir, and
17/// overrides the basename of the CompileStep.
18dest_sub_path: ?[]const u8,
1519
16pub fn create(builder: *std.Build, artifact: *CompileStep) *InstallArtifactStep {
20pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep {
1721 if (artifact.install_step) |s| return s;
1822
19 const self = builder.allocator.create(InstallArtifactStep) catch @panic("OOM");
23 const self = owner.allocator.create(InstallArtifactStep) catch @panic("OOM");
2024 self.* = InstallArtifactStep{
21 .builder = builder,
22 .step = Step.init(.install_artifact, builder.fmt("install {s}", .{artifact.step.name}), builder.allocator, make),
25 .step = Step.init(.{
26 .id = base_id,
27 .name = owner.fmt("install {s}", .{artifact.name}),
28 .owner = owner,
29 .makeFn = make,
30 }),
31 .dest_builder = owner,
2332 .artifact = artifact,
2433 .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {
2534 .obj => @panic("Cannot install a .obj build artifact."),
26 .@"test" => @panic("Cannot install a .test build artifact, use .test_exe instead."),
27 .exe, .test_exe => InstallDir{ .bin = {} },
35 .exe, .@"test" => InstallDir{ .bin = {} },
2836 .lib => InstallDir{ .lib = {} },
2937 },
3038 .pdb_dir = if (artifact.producesPdbFile()) blk: {
31 if (artifact.kind == .exe or artifact.kind == .test_exe) {
39 if (artifact.kind == .exe or artifact.kind == .@"test") {
3240 break :blk InstallDir{ .bin = {} };
3341 } else {
3442 break :blk InstallDir{ .lib = {} };
3543 }
3644 } else null,
3745 .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,
46 .dest_sub_path = null,
3847 };
3948 self.step.dependOn(&artifact.step);
4049 artifact.install_step = self;
4150
42 builder.pushInstalledFile(self.dest_dir, artifact.out_filename);
51 owner.pushInstalledFile(self.dest_dir, artifact.out_filename);
4352 if (self.artifact.isDynamicLibrary()) {
4453 if (artifact.major_only_filename) |name| {
45 builder.pushInstalledFile(.lib, name);
54 owner.pushInstalledFile(.lib, name);
4655 }
4756 if (artifact.name_only_filename) |name| {
48 builder.pushInstalledFile(.lib, name);
57 owner.pushInstalledFile(.lib, name);
4958 }
5059 if (self.artifact.target.isWindows()) {
51 builder.pushInstalledFile(.lib, artifact.out_lib_filename);
60 owner.pushInstalledFile(.lib, artifact.out_lib_filename);
5261 }
5362 }
5463 if (self.pdb_dir) |pdb_dir| {
55 builder.pushInstalledFile(pdb_dir, artifact.out_pdb_filename);
64 owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename);
5665 }
5766 if (self.h_dir) |h_dir| {
58 builder.pushInstalledFile(h_dir, artifact.out_h_filename);
67 owner.pushInstalledFile(h_dir, artifact.out_h_filename);
5968 }
6069 return self;
6170}
6271
63fn make(step: *Step) !void {
72fn make(step: *Step, prog_node: *std.Progress.Node) !void {
73 _ = prog_node;
74 const src_builder = step.owner;
6475 const self = @fieldParentPtr(InstallArtifactStep, "step", step);
65 const builder = self.builder;
76 const dest_builder = self.dest_builder;
6677
67 const full_dest_path = builder.getInstallPath(self.dest_dir, self.artifact.out_filename);
68 try builder.updateFile(self.artifact.getOutputSource().getPath(builder), full_dest_path);
69 if (self.artifact.isDynamicLibrary() and self.artifact.version != null and self.artifact.target.wantSharedLibSymLinks()) {
70 try CompileStep.doAtomicSymLinks(builder.allocator, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?);
78 const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename;
79 const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path);
80 const cwd = fs.cwd();
81
82 var all_cached = true;
83
84 {
85 const full_src_path = self.artifact.getOutputSource().getPath(src_builder);
86 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| {
87 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
88 full_src_path, full_dest_path, @errorName(err),
89 });
90 };
91 all_cached = all_cached and p == .fresh;
92 }
93
94 if (self.artifact.isDynamicLibrary() and
95 self.artifact.version != null and
96 self.artifact.target.wantSharedLibSymLinks())
97 {
98 try CompileStep.doAtomicSymLinks(step, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?);
7199 }
72 if (self.artifact.isDynamicLibrary() and self.artifact.target.isWindows() and self.artifact.emit_implib != .no_emit) {
73 const full_implib_path = builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename);
74 try builder.updateFile(self.artifact.getOutputLibSource().getPath(builder), full_implib_path);
100 if (self.artifact.isDynamicLibrary() and
101 self.artifact.target.isWindows() and
102 self.artifact.emit_implib != .no_emit)
103 {
104 const full_src_path = self.artifact.getOutputLibSource().getPath(src_builder);
105 const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename);
106 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| {
107 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
108 full_src_path, full_implib_path, @errorName(err),
109 });
110 };
111 all_cached = all_cached and p == .fresh;
75112 }
76113 if (self.pdb_dir) |pdb_dir| {
77 const full_pdb_path = builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename);
78 try builder.updateFile(self.artifact.getOutputPdbSource().getPath(builder), full_pdb_path);
114 const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder);
115 const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename);
116 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| {
117 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
118 full_src_path, full_pdb_path, @errorName(err),
119 });
120 };
121 all_cached = all_cached and p == .fresh;
79122 }
80123 if (self.h_dir) |h_dir| {
81 const full_h_path = builder.getInstallPath(h_dir, self.artifact.out_h_filename);
82 try builder.updateFile(self.artifact.getOutputHSource().getPath(builder), full_h_path);
124 const full_src_path = self.artifact.getOutputHSource().getPath(src_builder);
125 const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename);
126 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| {
127 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
128 full_src_path, full_h_path, @errorName(err),
129 });
130 };
131 all_cached = all_cached and p == .fresh;
83132 }
84133 self.artifact.installed_path = full_dest_path;
134 step.result_cached = all_cached;
85135}
lib/std/Build/InstallDirStep.zig+43-26
......@@ -4,14 +4,12 @@ const fs = std.fs;
44const Step = std.Build.Step;
55const InstallDir = std.Build.InstallDir;
66const InstallDirStep = @This();
7const log = std.log;
87
98step: Step,
10builder: *std.Build,
119options: Options,
1210/// This is used by the build system when a file being installed comes from one
1311/// package but is being installed by another.
14override_source_builder: ?*std.Build = null,
12dest_builder: *std.Build,
1513
1614pub const base_id = .install_dir;
1715
......@@ -40,31 +38,35 @@ pub const Options = struct {
4038 }
4139};
4240
43pub fn init(
44 builder: *std.Build,
45 options: Options,
46) InstallDirStep {
47 builder.pushInstalledFile(options.install_dir, options.install_subdir);
48 return InstallDirStep{
49 .builder = builder,
50 .step = Step.init(.install_dir, builder.fmt("install {s}/", .{options.source_dir}), builder.allocator, make),
51 .options = options.dupe(builder),
41pub fn init(owner: *std.Build, options: Options) InstallDirStep {
42 owner.pushInstalledFile(options.install_dir, options.install_subdir);
43 return .{
44 .step = Step.init(.{
45 .id = .install_dir,
46 .name = owner.fmt("install {s}/", .{options.source_dir}),
47 .owner = owner,
48 .makeFn = make,
49 }),
50 .options = options.dupe(owner),
51 .dest_builder = owner,
5252 };
5353}
5454
55fn make(step: *Step) !void {
55fn make(step: *Step, prog_node: *std.Progress.Node) !void {
56 _ = prog_node;
5657 const self = @fieldParentPtr(InstallDirStep, "step", step);
57 const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
58 const src_builder = self.override_source_builder orelse self.builder;
59 const full_src_dir = src_builder.pathFromRoot(self.options.source_dir);
60 var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| {
61 log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{
62 full_src_dir, @errorName(err),
58 const dest_builder = self.dest_builder;
59 const arena = dest_builder.allocator;
60 const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
61 const src_builder = self.step.owner;
62 var src_dir = src_builder.build_root.handle.openIterableDir(self.options.source_dir, .{}) catch |err| {
63 return step.fail("unable to open source directory '{}{s}': {s}", .{
64 src_builder.build_root, self.options.source_dir, @errorName(err),
6365 });
64 return error.StepFailed;
6566 };
6667 defer src_dir.close();
67 var it = try src_dir.walk(self.builder.allocator);
68 var it = try src_dir.walk(arena);
69 var all_cached = true;
6870 next_entry: while (try it.next()) |entry| {
6971 for (self.options.exclude_extensions) |ext| {
7072 if (mem.endsWith(u8, entry.path, ext)) {
......@@ -72,22 +74,37 @@ fn make(step: *Step) !void {
7274 }
7375 }
7476
75 const full_path = self.builder.pathJoin(&.{ full_src_dir, entry.path });
76 const dest_path = self.builder.pathJoin(&.{ dest_prefix, entry.path });
77 // relative to src build root
78 const src_sub_path = try fs.path.join(arena, &.{ self.options.source_dir, entry.path });
79 const dest_path = try fs.path.join(arena, &.{ dest_prefix, entry.path });
80 const cwd = fs.cwd();
7781
7882 switch (entry.kind) {
79 .Directory => try fs.cwd().makePath(dest_path),
83 .Directory => try cwd.makePath(dest_path),
8084 .File => {
8185 for (self.options.blank_extensions) |ext| {
8286 if (mem.endsWith(u8, entry.path, ext)) {
83 try self.builder.truncateFile(dest_path);
87 try dest_builder.truncateFile(dest_path);
8488 continue :next_entry;
8589 }
8690 }
8791
88 try self.builder.updateFile(full_path, dest_path);
92 const prev_status = fs.Dir.updateFile(
93 src_builder.build_root.handle,
94 src_sub_path,
95 cwd,
96 dest_path,
97 .{},
98 ) catch |err| {
99 return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{
100 src_builder.build_root, src_sub_path, dest_path, @errorName(err),
101 });
102 };
103 all_cached = all_cached and prev_status == .fresh;
89104 },
90105 else => continue,
91106 }
92107 }
108
109 step.result_cached = all_cached;
93110}
lib/std/Build/InstallFileStep.zig+34-17
......@@ -3,38 +3,55 @@ const Step = std.Build.Step;
33const FileSource = std.Build.FileSource;
44const InstallDir = std.Build.InstallDir;
55const InstallFileStep = @This();
6const assert = std.debug.assert;
67
78pub const base_id = .install_file;
89
910step: Step,
10builder: *std.Build,
1111source: FileSource,
1212dir: InstallDir,
1313dest_rel_path: []const u8,
1414/// This is used by the build system when a file being installed comes from one
1515/// package but is being installed by another.
16override_source_builder: ?*std.Build = null,
16dest_builder: *std.Build,
1717
18pub fn init(
19 builder: *std.Build,
18pub fn create(
19 owner: *std.Build,
2020 source: FileSource,
2121 dir: InstallDir,
2222 dest_rel_path: []const u8,
23) InstallFileStep {
24 builder.pushInstalledFile(dir, dest_rel_path);
25 return InstallFileStep{
26 .builder = builder,
27 .step = Step.init(.install_file, builder.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), builder.allocator, make),
28 .source = source.dupe(builder),
29 .dir = dir.dupe(builder),
30 .dest_rel_path = builder.dupePath(dest_rel_path),
23) *InstallFileStep {
24 assert(dest_rel_path.len != 0);
25 owner.pushInstalledFile(dir, dest_rel_path);
26 const self = owner.allocator.create(InstallFileStep) catch @panic("OOM");
27 self.* = .{
28 .step = Step.init(.{
29 .id = base_id,
30 .name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }),
31 .owner = owner,
32 .makeFn = make,
33 }),
34 .source = source.dupe(owner),
35 .dir = dir.dupe(owner),
36 .dest_rel_path = owner.dupePath(dest_rel_path),
37 .dest_builder = owner,
3138 };
39 source.addStepDependencies(&self.step);
40 return self;
3241}
3342
34fn make(step: *Step) !void {
43fn make(step: *Step, prog_node: *std.Progress.Node) !void {
44 _ = prog_node;
45 const src_builder = step.owner;
3546 const self = @fieldParentPtr(InstallFileStep, "step", step);
36 const src_builder = self.override_source_builder orelse self.builder;
37 const full_src_path = self.source.getPath(src_builder);
38 const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path);
39 try self.builder.updateFile(full_src_path, full_dest_path);
47 const dest_builder = self.dest_builder;
48 const full_src_path = self.source.getPath2(src_builder, step);
49 const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path);
50 const cwd = std.fs.cwd();
51 const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| {
52 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
53 full_src_path, full_dest_path, @errorName(err),
54 });
55 };
56 step.result_cached = prev == .fresh;
4057}
lib/std/Build/LogStep.zig deleted-23
......@@ -1,23 +0,0 @@
1const std = @import("../std.zig");
2const log = std.log;
3const Step = std.Build.Step;
4const LogStep = @This();
5
6pub const base_id = .log;
7
8step: Step,
9builder: *std.Build,
10data: []const u8,
11
12pub fn init(builder: *std.Build, data: []const u8) LogStep {
13 return LogStep{
14 .builder = builder,
15 .step = Step.init(.log, builder.fmt("log {s}", .{data}), builder.allocator, make),
16 .data = builder.dupe(data),
17 };
18}
19
20fn make(step: *Step) anyerror!void {
21 const self = @fieldParentPtr(LogStep, "step", step);
22 log.info("{s}", .{self.data});
23}
lib/std/Build/ObjCopyStep.zig+15-31
......@@ -21,7 +21,6 @@ pub const RawFormat = enum {
2121};
2222
2323step: Step,
24builder: *std.Build,
2524file_source: std.Build.FileSource,
2625basename: []const u8,
2726output_file: std.Build.GeneratedFile,
......@@ -38,19 +37,18 @@ pub const Options = struct {
3837};
3938
4039pub fn create(
41 builder: *std.Build,
40 owner: *std.Build,
4241 file_source: std.Build.FileSource,
4342 options: Options,
4443) *ObjCopyStep {
45 const self = builder.allocator.create(ObjCopyStep) catch @panic("OOM");
44 const self = owner.allocator.create(ObjCopyStep) catch @panic("OOM");
4645 self.* = ObjCopyStep{
47 .step = Step.init(
48 base_id,
49 builder.fmt("objcopy {s}", .{file_source.getDisplayName()}),
50 builder.allocator,
51 make,
52 ),
53 .builder = builder,
46 .step = Step.init(.{
47 .id = base_id,
48 .name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}),
49 .owner = owner,
50 .makeFn = make,
51 }),
5452 .file_source = file_source,
5553 .basename = options.basename orelse file_source.getDisplayName(),
5654 .output_file = std.Build.GeneratedFile{ .step = &self.step },
......@@ -67,9 +65,9 @@ pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource {
6765 return .{ .generated = &self.output_file };
6866}
6967
70fn make(step: *Step) !void {
68fn make(step: *Step, prog_node: *std.Progress.Node) !void {
69 const b = step.owner;
7170 const self = @fieldParentPtr(ObjCopyStep, "step", step);
72 const b = self.builder;
7371
7472 var man = b.cache.obtain();
7573 defer man.deinit();
......@@ -84,7 +82,7 @@ fn make(step: *Step) !void {
8482 man.hash.addOptional(self.pad_to);
8583 man.hash.addOptional(self.format);
8684
87 if (man.hit() catch |err| failWithCacheError(man, err)) {
85 if (try step.cacheHit(&man)) {
8886 // Cache hit, skip subprocess execution.
8987 const digest = man.final();
9088 self.output_file.path = try b.cache_root.join(b.allocator, &.{
......@@ -97,8 +95,7 @@ fn make(step: *Step) !void {
9795 const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename });
9896 const cache_path = "o" ++ fs.path.sep_str ++ digest;
9997 b.cache_root.handle.makePath(cache_path) catch |err| {
100 std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });
101 return err;
98 return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
10299 };
103100
104101 var argv = std.ArrayList([]const u8).init(b.allocator);
......@@ -116,23 +113,10 @@ fn make(step: *Step) !void {
116113 };
117114
118115 try argv.appendSlice(&.{ full_src_path, full_dest_path });
119 _ = try self.builder.execFromStep(argv.items, &self.step);
116
117 try argv.append("--listen=-");
118 _ = try step.evalZigProcess(argv.items, prog_node);
120119
121120 self.output_file.path = full_dest_path;
122121 try man.writeManifest();
123122}
124
125/// TODO consolidate this with the same function in RunStep?
126/// Also properly deal with concurrency (see open PR)
127fn failWithCacheError(man: std.Build.Cache.Manifest, err: anyerror) noreturn {
128 const i = man.failed_file_index orelse failWithSimpleError(err);
129 const pp = man.files.items[i].prefixed_path orelse failWithSimpleError(err);
130 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
131 std.debug.print("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path });
132 std.process.exit(1);
133}
134
135fn failWithSimpleError(err: anyerror) noreturn {
136 std.debug.print("{s}\n", .{@errorName(err)});
137 std.process.exit(1);
138}
lib/std/Build/OptionsStep.zig+23-18
......@@ -12,21 +12,24 @@ pub const base_id = .options;
1212
1313step: Step,
1414generated_file: GeneratedFile,
15builder: *std.Build,
1615
1716contents: std.ArrayList(u8),
1817artifact_args: std.ArrayList(OptionArtifactArg),
1918file_source_args: std.ArrayList(OptionFileSourceArg),
2019
21pub fn create(builder: *std.Build) *OptionsStep {
22 const self = builder.allocator.create(OptionsStep) catch @panic("OOM");
20pub fn create(owner: *std.Build) *OptionsStep {
21 const self = owner.allocator.create(OptionsStep) catch @panic("OOM");
2322 self.* = .{
24 .builder = builder,
25 .step = Step.init(.options, "options", builder.allocator, make),
23 .step = Step.init(.{
24 .id = base_id,
25 .name = "options",
26 .owner = owner,
27 .makeFn = make,
28 }),
2629 .generated_file = undefined,
27 .contents = std.ArrayList(u8).init(builder.allocator),
28 .artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator),
29 .file_source_args = std.ArrayList(OptionFileSourceArg).init(builder.allocator),
30 .contents = std.ArrayList(u8).init(owner.allocator),
31 .artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator),
32 .file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.allocator),
3033 };
3134 self.generated_file = .{ .step = &self.step };
3235
......@@ -192,7 +195,7 @@ pub fn addOptionFileSource(
192195) void {
193196 self.file_source_args.append(.{
194197 .name = name,
195 .source = source.dupe(self.builder),
198 .source = source.dupe(self.step.owner),
196199 }) catch @panic("OOM");
197200 source.addStepDependencies(&self.step);
198201}
......@@ -200,12 +203,12 @@ pub fn addOptionFileSource(
200203/// The value is the path in the cache dir.
201204/// Adds a dependency automatically.
202205pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *CompileStep) void {
203 self.artifact_args.append(.{ .name = self.builder.dupe(name), .artifact = artifact }) catch @panic("OOM");
206 self.artifact_args.append(.{ .name = self.step.owner.dupe(name), .artifact = artifact }) catch @panic("OOM");
204207 self.step.dependOn(&artifact.step);
205208}
206209
207210pub fn createModule(self: *OptionsStep) *std.Build.Module {
208 return self.builder.createModule(.{
211 return self.step.owner.createModule(.{
209212 .source_file = self.getSource(),
210213 .dependencies = &.{},
211214 });
......@@ -215,14 +218,18 @@ pub fn getSource(self: *OptionsStep) FileSource {
215218 return .{ .generated = &self.generated_file };
216219}
217220
218fn make(step: *Step) !void {
221fn make(step: *Step, prog_node: *std.Progress.Node) !void {
222 // This step completes so quickly that no progress is necessary.
223 _ = prog_node;
224
225 const b = step.owner;
219226 const self = @fieldParentPtr(OptionsStep, "step", step);
220227
221228 for (self.artifact_args.items) |item| {
222229 self.addOption(
223230 []const u8,
224231 item.name,
225 self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)),
232 b.pathFromRoot(item.artifact.getOutputSource().getPath(b)),
226233 );
227234 }
228235
......@@ -230,20 +237,18 @@ fn make(step: *Step) !void {
230237 self.addOption(
231238 []const u8,
232239 item.name,
233 item.source.getPath(self.builder),
240 item.source.getPath(b),
234241 );
235242 }
236243
237 var options_dir = try self.builder.cache_root.handle.makeOpenPath("options", .{});
244 var options_dir = try b.cache_root.handle.makeOpenPath("options", .{});
238245 defer options_dir.close();
239246
240247 const basename = self.hashContentsToFileName();
241248
242249 try options_dir.writeFile(&basename, self.contents.items);
243250
244 self.generated_file.path = try self.builder.cache_root.join(self.builder.allocator, &.{
245 "options", &basename,
246 });
251 self.generated_file.path = try b.cache_root.join(b.allocator, &.{ "options", &basename });
247252}
248253
249254fn hashContentsToFileName(self: *OptionsStep) [64]u8 {
lib/std/Build/RemoveDirStep.zig+24-11
......@@ -1,5 +1,4 @@
11const std = @import("../std.zig");
2const log = std.log;
32const fs = std.fs;
43const Step = std.Build.Step;
54const RemoveDirStep = @This();
......@@ -7,23 +6,37 @@ const RemoveDirStep = @This();
76pub const base_id = .remove_dir;
87
98step: Step,
10builder: *std.Build,
119dir_path: []const u8,
1210
13pub fn init(builder: *std.Build, dir_path: []const u8) RemoveDirStep {
11pub fn init(owner: *std.Build, dir_path: []const u8) RemoveDirStep {
1412 return RemoveDirStep{
15 .builder = builder,
16 .step = Step.init(.remove_dir, builder.fmt("RemoveDir {s}", .{dir_path}), builder.allocator, make),
17 .dir_path = builder.dupePath(dir_path),
13 .step = Step.init(.{
14 .id = .remove_dir,
15 .name = owner.fmt("RemoveDir {s}", .{dir_path}),
16 .owner = owner,
17 .makeFn = make,
18 }),
19 .dir_path = owner.dupePath(dir_path),
1820 };
1921}
2022
21fn make(step: *Step) !void {
23fn make(step: *Step, prog_node: *std.Progress.Node) !void {
24 // TODO update progress node while walking file system.
25 // Should the standard library support this use case??
26 _ = prog_node;
27
28 const b = step.owner;
2229 const self = @fieldParentPtr(RemoveDirStep, "step", step);
2330
24 const full_path = self.builder.pathFromRoot(self.dir_path);
25 fs.cwd().deleteTree(full_path) catch |err| {
26 log.err("Unable to remove {s}: {s}", .{ full_path, @errorName(err) });
27 return err;
31 b.build_root.handle.deleteTree(self.dir_path) catch |err| {
32 if (b.build_root.path) |base| {
33 return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{
34 base, self.dir_path, @errorName(err),
35 });
36 } else {
37 return step.fail("unable to recursively delete path '{s}': {s}", .{
38 self.dir_path, @errorName(err),
39 });
40 }
2841 };
2942}
lib/std/Build/RunStep.zig+975-271
......@@ -10,76 +10,136 @@ const ArrayList = std.ArrayList;
1010const EnvMap = process.EnvMap;
1111const Allocator = mem.Allocator;
1212const ExecError = std.Build.ExecError;
13
14const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
13const assert = std.debug.assert;
1514
1615const RunStep = @This();
1716
1817pub const base_id: Step.Id = .run;
1918
2019step: Step,
21builder: *std.Build,
2220
2321/// See also addArg and addArgs to modifying this directly
2422argv: ArrayList(Arg),
2523
2624/// Set this to modify the current working directory
25/// TODO change this to a Build.Cache.Directory to better integrate with
26/// future child process cwd API.
2727cwd: ?[]const u8,
2828
2929/// Override this field to modify the environment, or use setEnvironmentVariable
3030env_map: ?*EnvMap,
3131
32stdout_action: StdIoAction = .inherit,
33stderr_action: StdIoAction = .inherit,
34
35stdin_behavior: std.ChildProcess.StdIo = .Inherit,
36
37/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution
38expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },
39
40/// Print the command before running it
41print: bool,
42/// Controls whether execution is skipped if the output file is up-to-date.
43/// The default is to always run if there is no output file, and to skip
44/// running if all output files are up-to-date.
45condition: enum { output_outdated, always } = .output_outdated,
32/// Configures whether the RunStep is considered to have side-effects, and also
33/// whether the RunStep will inherit stdio streams, forwarding them to the
34/// parent process, in which case will require a global lock to prevent other
35/// steps from interfering with stdio while the subprocess associated with this
36/// RunStep is running.
37/// If the RunStep is determined to not have side-effects, then execution will
38/// be skipped if all output files are up-to-date and input files are
39/// unchanged.
40stdio: StdIo = .infer_from_args,
41/// This field must be `null` if stdio is `inherit`.
42stdin: ?[]const u8 = null,
4643
4744/// Additional file paths relative to build.zig that, when modified, indicate
4845/// that the RunStep should be re-executed.
46/// If the RunStep is determined to have side-effects, this field is ignored
47/// and the RunStep is always executed when it appears in the build graph.
4948extra_file_dependencies: []const []const u8 = &.{},
5049
51pub const StdIoAction = union(enum) {
50/// After adding an output argument, this step will by default rename itself
51/// for a better display name in the build summary.
52/// This can be disabled by setting this to false.
53rename_step_with_output_arg: bool = true,
54
55/// If this is true, a RunStep which is configured to check the output of the
56/// executed binary will not fail the build if the binary cannot be executed
57/// due to being for a foreign binary to the host system which is running the
58/// build graph.
59/// Command-line arguments such as -fqemu and -fwasmtime may affect whether a
60/// binary is detected as foreign, as well as system configuration such as
61/// Rosetta (macOS) and binfmt_misc (Linux).
62/// If this RunStep is considered to have side-effects, then this flag does
63/// nothing.
64skip_foreign_checks: bool = false,
65
66/// If stderr or stdout exceeds this amount, the child process is killed and
67/// the step fails.
68max_stdio_size: usize = 10 * 1024 * 1024,
69
70captured_stdout: ?*Output = null,
71captured_stderr: ?*Output = null,
72
73has_side_effects: bool = false,
74
75pub const StdIo = union(enum) {
76 /// Whether the RunStep has side-effects will be determined by whether or not one
77 /// of the args is an output file (added with `addOutputFileArg`).
78 /// If the RunStep is determined to have side-effects, this is the same as `inherit`.
79 /// The step will fail if the subprocess crashes or returns a non-zero exit code.
80 infer_from_args,
81 /// Causes the RunStep to be considered to have side-effects, and therefore
82 /// always execute when it appears in the build graph.
83 /// It also means that this step will obtain a global lock to prevent other
84 /// steps from running in the meantime.
85 /// The step will fail if the subprocess crashes or returns a non-zero exit code.
5286 inherit,
53 ignore,
54 expect_exact: []const u8,
55 expect_matches: []const []const u8,
87 /// Causes the RunStep to be considered to *not* have side-effects. The
88 /// process will be re-executed if any of the input dependencies are
89 /// modified. The exit code and standard I/O streams will be checked for
90 /// certain conditions, and the step will succeed or fail based on these
91 /// conditions.
92 /// Note that an explicit check for exit code 0 needs to be added to this
93 /// list if such a check is desireable.
94 check: std.ArrayList(Check),
95 /// This RunStep is running a zig unit test binary and will communicate
96 /// extra metadata over the IPC protocol.
97 zig_test,
98
99 pub const Check = union(enum) {
100 expect_stderr_exact: []const u8,
101 expect_stderr_match: []const u8,
102 expect_stdout_exact: []const u8,
103 expect_stdout_match: []const u8,
104 expect_term: std.process.Child.Term,
105 };
56106};
57107
58108pub const Arg = union(enum) {
59109 artifact: *CompileStep,
60110 file_source: std.Build.FileSource,
111 directory_source: std.Build.FileSource,
61112 bytes: []u8,
62 output: Output,
113 output: *Output,
114};
63115
64 pub const Output = struct {
65 generated_file: *std.Build.GeneratedFile,
66 basename: []const u8,
67 };
116pub const Output = struct {
117 generated_file: std.Build.GeneratedFile,
118 prefix: []const u8,
119 basename: []const u8,
68120};
69121
70pub fn create(builder: *std.Build, name: []const u8) *RunStep {
71 const self = builder.allocator.create(RunStep) catch @panic("OOM");
72 self.* = RunStep{
73 .builder = builder,
74 .step = Step.init(base_id, name, builder.allocator, make),
75 .argv = ArrayList(Arg).init(builder.allocator),
122pub fn create(owner: *std.Build, name: []const u8) *RunStep {
123 const self = owner.allocator.create(RunStep) catch @panic("OOM");
124 self.* = .{
125 .step = Step.init(.{
126 .id = base_id,
127 .name = name,
128 .owner = owner,
129 .makeFn = make,
130 }),
131 .argv = ArrayList(Arg).init(owner.allocator),
76132 .cwd = null,
77133 .env_map = null,
78 .print = builder.verbose,
79134 };
80135 return self;
81136}
82137
138pub fn setName(self: *RunStep, name: []const u8) void {
139 self.step.name = name;
140 self.rename_step_with_output_arg = false;
141}
142
83143pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {
84144 self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM");
85145 self.step.dependOn(&artifact.step);
......@@ -89,25 +149,47 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {
89149/// run, and returns a FileSource which can be used as inputs to other APIs
90150/// throughout the build system.
91151pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource {
92 const generated_file = rs.builder.allocator.create(std.Build.GeneratedFile) catch @panic("OOM");
93 generated_file.* = .{ .step = &rs.step };
94 rs.argv.append(.{ .output = .{
95 .generated_file = generated_file,
96 .basename = rs.builder.dupe(basename),
97 } }) catch @panic("OOM");
152 return addPrefixedOutputFileArg(rs, "", basename);
153}
98154
99 return .{ .generated = generated_file };
155pub fn addPrefixedOutputFileArg(
156 rs: *RunStep,
157 prefix: []const u8,
158 basename: []const u8,
159) std.Build.FileSource {
160 const b = rs.step.owner;
161
162 const output = b.allocator.create(Output) catch @panic("OOM");
163 output.* = .{
164 .prefix = prefix,
165 .basename = basename,
166 .generated_file = .{ .step = &rs.step },
167 };
168 rs.argv.append(.{ .output = output }) catch @panic("OOM");
169
170 if (rs.rename_step_with_output_arg) {
171 rs.setName(b.fmt("{s} ({s})", .{ rs.step.name, basename }));
172 }
173
174 return .{ .generated = &output.generated_file };
100175}
101176
102177pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void {
103 self.argv.append(Arg{
104 .file_source = file_source.dupe(self.builder),
178 self.argv.append(.{
179 .file_source = file_source.dupe(self.step.owner),
105180 }) catch @panic("OOM");
106181 file_source.addStepDependencies(&self.step);
107182}
108183
184pub fn addDirectorySourceArg(self: *RunStep, directory_source: std.Build.FileSource) void {
185 self.argv.append(.{
186 .directory_source = directory_source.dupe(self.step.owner),
187 }) catch @panic("OOM");
188 directory_source.addStepDependencies(&self.step);
189}
190
109191pub fn addArg(self: *RunStep, arg: []const u8) void {
110 self.argv.append(Arg{ .bytes = self.builder.dupe(arg) }) catch @panic("OOM");
192 self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM");
111193}
112194
113195pub fn addArgs(self: *RunStep, args: []const []const u8) void {
......@@ -117,102 +199,183 @@ pub fn addArgs(self: *RunStep, args: []const []const u8) void {
117199}
118200
119201pub fn clearEnvironment(self: *RunStep) void {
120 const new_env_map = self.builder.allocator.create(EnvMap) catch @panic("OOM");
121 new_env_map.* = EnvMap.init(self.builder.allocator);
202 const b = self.step.owner;
203 const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM");
204 new_env_map.* = EnvMap.init(b.allocator);
122205 self.env_map = new_env_map;
123206}
124207
125208pub fn addPathDir(self: *RunStep, search_path: []const u8) void {
126 addPathDirInternal(&self.step, self.builder, search_path);
127}
128
129/// For internal use only, users of `RunStep` should use `addPathDir` directly.
130pub fn addPathDirInternal(step: *Step, builder: *std.Build, search_path: []const u8) void {
131 const env_map = getEnvMapInternal(step, builder.allocator);
209 const b = self.step.owner;
210 const env_map = getEnvMapInternal(self);
132211
133212 const key = "PATH";
134213 var prev_path = env_map.get(key);
135214
136215 if (prev_path) |pp| {
137 const new_path = builder.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path });
216 const new_path = b.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path });
138217 env_map.put(key, new_path) catch @panic("OOM");
139218 } else {
140 env_map.put(key, builder.dupePath(search_path)) catch @panic("OOM");
219 env_map.put(key, b.dupePath(search_path)) catch @panic("OOM");
141220 }
142221}
143222
144223pub fn getEnvMap(self: *RunStep) *EnvMap {
145 return getEnvMapInternal(&self.step, self.builder.allocator);
224 return getEnvMapInternal(self);
146225}
147226
148fn getEnvMapInternal(step: *Step, allocator: Allocator) *EnvMap {
149 const maybe_env_map = switch (step.id) {
150 .run => step.cast(RunStep).?.env_map,
151 .emulatable_run => step.cast(std.Build.EmulatableRunStep).?.env_map,
152 else => unreachable,
153 };
154 return maybe_env_map orelse {
155 const env_map = allocator.create(EnvMap) catch @panic("OOM");
156 env_map.* = process.getEnvMap(allocator) catch @panic("unhandled error");
157 switch (step.id) {
158 .run => step.cast(RunStep).?.env_map = env_map,
159 .emulatable_run => step.cast(RunStep).?.env_map = env_map,
160 else => unreachable,
161 }
227fn getEnvMapInternal(self: *RunStep) *EnvMap {
228 const arena = self.step.owner.allocator;
229 return self.env_map orelse {
230 const env_map = arena.create(EnvMap) catch @panic("OOM");
231 env_map.* = process.getEnvMap(arena) catch @panic("unhandled error");
232 self.env_map = env_map;
162233 return env_map;
163234 };
164235}
165236
166237pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void {
238 const b = self.step.owner;
167239 const env_map = self.getEnvMap();
168 env_map.put(
169 self.builder.dupe(key),
170 self.builder.dupe(value),
171 ) catch @panic("unhandled error");
240 env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error");
241}
242
243pub fn removeEnvironmentVariable(self: *RunStep, key: []const u8) void {
244 self.getEnvMap().remove(key);
172245}
173246
247/// Adds a check for exact stderr match. Does not add any other checks.
174248pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void {
175 self.stderr_action = .{ .expect_exact = self.builder.dupe(bytes) };
249 const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) };
250 self.addCheck(new_check);
176251}
177252
253/// Adds a check for exact stdout match as well as a check for exit code 0, if
254/// there is not already an expected termination check.
178255pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void {
179 self.stdout_action = .{ .expect_exact = self.builder.dupe(bytes) };
256 const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) };
257 self.addCheck(new_check);
258 if (!self.hasTermCheck()) {
259 self.expectExitCode(0);
260 }
180261}
181262
182fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo {
183 return switch (action) {
184 .ignore => .Ignore,
185 .inherit => .Inherit,
186 .expect_exact, .expect_matches => .Pipe,
263pub fn expectExitCode(self: *RunStep, code: u8) void {
264 const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } };
265 self.addCheck(new_check);
266}
267
268pub fn hasTermCheck(self: RunStep) bool {
269 for (self.stdio.check.items) |check| switch (check) {
270 .expect_term => return true,
271 else => continue,
187272 };
273 return false;
188274}
189275
190fn needOutputCheck(self: RunStep) bool {
191 switch (self.condition) {
192 .always => return false,
193 .output_outdated => {},
276pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void {
277 switch (self.stdio) {
278 .infer_from_args => {
279 self.stdio = .{ .check = std.ArrayList(StdIo.Check).init(self.step.owner.allocator) };
280 self.stdio.check.append(new_check) catch @panic("OOM");
281 },
282 .check => |*checks| checks.append(new_check) catch @panic("OOM"),
283 else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"),
194284 }
195 if (self.extra_file_dependencies.len > 0) return true;
285}
286
287pub fn captureStdErr(self: *RunStep) std.Build.FileSource {
288 assert(self.stdio != .inherit);
289
290 if (self.captured_stderr) |output| return .{ .generated = &output.generated_file };
291
292 const output = self.step.owner.allocator.create(Output) catch @panic("OOM");
293 output.* = .{
294 .prefix = "",
295 .basename = "stderr",
296 .generated_file = .{ .step = &self.step },
297 };
298 self.captured_stderr = output;
299 return .{ .generated = &output.generated_file };
300}
301
302pub fn captureStdOut(self: *RunStep) *std.Build.GeneratedFile {
303 assert(self.stdio != .inherit);
196304
305 if (self.captured_stdout) |output| return .{ .generated = &output.generated_file };
306
307 const output = self.step.owner.allocator.create(Output) catch @panic("OOM");
308 output.* = .{
309 .prefix = "",
310 .basename = "stdout",
311 .generated_file = .{ .step = &self.step },
312 };
313 self.captured_stdout = output;
314 return .{ .generated = &output.generated_file };
315}
316
317/// Returns whether the RunStep has side effects *other than* updating the output arguments.
318fn hasSideEffects(self: RunStep) bool {
319 if (self.has_side_effects) return true;
320 return switch (self.stdio) {
321 .infer_from_args => !self.hasAnyOutputArgs(),
322 .inherit => true,
323 .check => false,
324 .zig_test => false,
325 };
326}
327
328fn hasAnyOutputArgs(self: RunStep) bool {
329 if (self.captured_stdout != null) return true;
330 if (self.captured_stderr != null) return true;
197331 for (self.argv.items) |arg| switch (arg) {
198332 .output => return true,
199333 else => continue,
200334 };
335 return false;
336}
337
338fn checksContainStdout(checks: []const StdIo.Check) bool {
339 for (checks) |check| switch (check) {
340 .expect_stderr_exact,
341 .expect_stderr_match,
342 .expect_term,
343 => continue,
344
345 .expect_stdout_exact,
346 .expect_stdout_match,
347 => return true,
348 };
349 return false;
350}
201351
352fn checksContainStderr(checks: []const StdIo.Check) bool {
353 for (checks) |check| switch (check) {
354 .expect_stdout_exact,
355 .expect_stdout_match,
356 .expect_term,
357 => continue,
358
359 .expect_stderr_exact,
360 .expect_stderr_match,
361 => return true,
362 };
202363 return false;
203364}
204365
205fn make(step: *Step) !void {
366fn make(step: *Step, prog_node: *std.Progress.Node) !void {
367 const b = step.owner;
368 const arena = b.allocator;
206369 const self = @fieldParentPtr(RunStep, "step", step);
207 const need_output_check = self.needOutputCheck();
370 const has_side_effects = self.hasSideEffects();
208371
209 var argv_list = ArrayList([]const u8).init(self.builder.allocator);
372 var argv_list = ArrayList([]const u8).init(arena);
210373 var output_placeholders = ArrayList(struct {
211374 index: usize,
212 output: Arg.Output,
213 }).init(self.builder.allocator);
375 output: *Output,
376 }).init(arena);
214377
215 var man = self.builder.cache.obtain();
378 var man = b.cache.obtain();
216379 defer man.deinit();
217380
218381 for (self.argv.items) |arg| {
......@@ -222,23 +385,29 @@ fn make(step: *Step) !void {
222385 man.hash.addBytes(bytes);
223386 },
224387 .file_source => |file| {
225 const file_path = file.getPath(self.builder);
388 const file_path = file.getPath(b);
226389 try argv_list.append(file_path);
227390 _ = try man.addFile(file_path, null);
228391 },
392 .directory_source => |file| {
393 const file_path = file.getPath(b);
394 try argv_list.append(file_path);
395 man.hash.addBytes(file_path);
396 },
229397 .artifact => |artifact| {
230398 if (artifact.target.isWindows()) {
231399 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
232400 self.addPathForDynLibs(artifact);
233401 }
234402 const file_path = artifact.installed_path orelse
235 artifact.getOutputSource().getPath(self.builder);
403 artifact.getOutputSource().getPath(b);
236404
237405 try argv_list.append(file_path);
238406
239407 _ = try man.addFile(file_path, null);
240408 },
241409 .output => |output| {
410 man.hash.addBytes(output.prefix);
242411 man.hash.addBytes(output.basename);
243412 // Add a placeholder into the argument list because we need the
244413 // manifest hash to be updated with all arguments before the
......@@ -252,60 +421,77 @@ fn make(step: *Step) !void {
252421 }
253422 }
254423
255 if (need_output_check) {
256 for (self.extra_file_dependencies) |file_path| {
257 _ = try man.addFile(self.builder.pathFromRoot(file_path), null);
258 }
424 if (self.captured_stdout) |output| {
425 man.hash.addBytes(output.basename);
426 }
259427
260 if (man.hit() catch |err| failWithCacheError(man, err)) {
261 // cache hit, skip running command
262 const digest = man.final();
263 for (output_placeholders.items) |placeholder| {
264 placeholder.output.generated_file.path = try self.builder.cache_root.join(
265 self.builder.allocator,
266 &.{ "o", &digest, placeholder.output.basename },
267 );
268 }
269 return;
270 }
428 if (self.captured_stderr) |output| {
429 man.hash.addBytes(output.basename);
430 }
271431
272 const digest = man.final();
432 hashStdIo(&man.hash, self.stdio);
273433
434 if (has_side_effects) {
435 try runCommand(self, argv_list.items, has_side_effects, null, prog_node);
436 return;
437 }
438
439 for (self.extra_file_dependencies) |file_path| {
440 _ = try man.addFile(b.pathFromRoot(file_path), null);
441 }
442
443 if (try step.cacheHit(&man)) {
444 // cache hit, skip running command
445 const digest = man.final();
274446 for (output_placeholders.items) |placeholder| {
275 const output_path = try self.builder.cache_root.join(
276 self.builder.allocator,
277 &.{ "o", &digest, placeholder.output.basename },
278 );
279 const output_dir = fs.path.dirname(output_path).?;
280 fs.cwd().makePath(output_dir) catch |err| {
281 std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });
282 return err;
283 };
447 placeholder.output.generated_file.path = try b.cache_root.join(arena, &.{
448 "o", &digest, placeholder.output.basename,
449 });
450 }
451
452 if (self.captured_stdout) |output| {
453 output.generated_file.path = try b.cache_root.join(arena, &.{
454 "o", &digest, output.basename,
455 });
456 }
284457
285 placeholder.output.generated_file.path = output_path;
286 argv_list.items[placeholder.index] = output_path;
458 if (self.captured_stderr) |output| {
459 output.generated_file.path = try b.cache_root.join(arena, &.{
460 "o", &digest, output.basename,
461 });
287462 }
463
464 step.result_cached = true;
465 return;
288466 }
289467
290 try runCommand(
291 argv_list.items,
292 self.builder,
293 self.expected_term,
294 self.stdout_action,
295 self.stderr_action,
296 self.stdin_behavior,
297 self.env_map,
298 self.cwd,
299 self.print,
300 );
301
302 if (need_output_check) {
303 try man.writeManifest();
468 const digest = man.final();
469
470 for (output_placeholders.items) |placeholder| {
471 const output_components = .{ "o", &digest, placeholder.output.basename };
472 const output_sub_path = try fs.path.join(arena, &output_components);
473 const output_sub_dir_path = fs.path.dirname(output_sub_path).?;
474 b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
475 return step.fail("unable to make path '{}{s}': {s}", .{
476 b.cache_root, output_sub_dir_path, @errorName(err),
477 });
478 };
479 const output_path = try b.cache_root.join(arena, &output_components);
480 placeholder.output.generated_file.path = output_path;
481 const cli_arg = if (placeholder.output.prefix.len == 0)
482 output_path
483 else
484 b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });
485 argv_list.items[placeholder.index] = cli_arg;
304486 }
487
488 try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node);
489
490 try step.writeManifest(&man);
305491}
306492
307493fn formatTerm(
308 term: ?std.ChildProcess.Term,
494 term: ?std.process.Child.Term,
309495 comptime fmt: []const u8,
310496 options: std.fmt.FormatOptions,
311497 writer: anytype,
......@@ -321,11 +507,11 @@ fn formatTerm(
321507 try writer.writeAll("exited with any code");
322508 }
323509}
324fn fmtTerm(term: ?std.ChildProcess.Term) std.fmt.Formatter(formatTerm) {
510fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) {
325511 return .{ .data = term };
326512}
327513
328fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term) bool {
514fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool {
329515 return if (expected) |e| switch (e) {
330516 .Exited => |expected_code| switch (actual) {
331517 .Exited => |actual_code| expected_code == actual_code,
......@@ -349,183 +535,701 @@ fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term)
349535 };
350536}
351537
352pub fn runCommand(
538fn runCommand(
539 self: *RunStep,
353540 argv: []const []const u8,
354 builder: *std.Build,
355 expected_term: ?std.ChildProcess.Term,
356 stdout_action: StdIoAction,
357 stderr_action: StdIoAction,
358 stdin_behavior: std.ChildProcess.StdIo,
359 env_map: ?*EnvMap,
360 maybe_cwd: ?[]const u8,
361 print: bool,
541 has_side_effects: bool,
542 digest: ?*const [std.Build.Cache.hex_digest_len]u8,
543 prog_node: *std.Progress.Node,
362544) !void {
363 const cwd = if (maybe_cwd) |cwd| builder.pathFromRoot(cwd) else builder.build_root.path;
364
365 if (!std.process.can_spawn) {
366 const cmd = try std.mem.join(builder.allocator, " ", argv);
367 std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{
368 @tagName(builtin.os.tag), cmd,
369 });
370 builder.allocator.free(cmd);
371 return ExecError.ExecNotSupported;
372 }
545 const step = &self.step;
546 const b = step.owner;
547 const arena = b.allocator;
548
549 try step.handleChildProcUnsupported(self.cwd, argv);
550 try Step.handleVerbose2(step.owner, self.cwd, self.env_map, argv);
551
552 const allow_skip = switch (self.stdio) {
553 .check, .zig_test => self.skip_foreign_checks,
554 else => false,
555 };
556
557 var interp_argv = std.ArrayList([]const u8).init(b.allocator);
558 defer interp_argv.deinit();
559
560 const result = spawnChildAndCollect(self, argv, has_side_effects, prog_node) catch |err| term: {
561 // InvalidExe: cpu arch mismatch
562 // FileNotFound: can happen with a wrong dynamic linker path
563 if (err == error.InvalidExe or err == error.FileNotFound) interpret: {
564 // TODO: learn the target from the binary directly rather than from
565 // relying on it being a CompileStep. This will make this logic
566 // work even for the edge case that the binary was produced by a
567 // third party.
568 const exe = switch (self.argv.items[0]) {
569 .artifact => |exe| exe,
570 else => break :interpret,
571 };
572 switch (exe.kind) {
573 .exe, .@"test" => {},
574 else => break :interpret,
575 }
576
577 const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc;
578 switch (b.host.getExternalExecutor(exe.target_info, .{
579 .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null,
580 .link_libc = exe.is_linking_libc,
581 })) {
582 .native, .rosetta => {
583 if (allow_skip) return error.MakeSkipped;
584 break :interpret;
585 },
586 .wine => |bin_name| {
587 if (b.enable_wine) {
588 try interp_argv.append(bin_name);
589 try interp_argv.appendSlice(argv);
590 } else {
591 return failForeign(self, "-fwine", argv[0], exe);
592 }
593 },
594 .qemu => |bin_name| {
595 if (b.enable_qemu) {
596 const glibc_dir_arg = if (need_cross_glibc)
597 b.glibc_runtimes_dir orelse return
598 else
599 null;
600
601 try interp_argv.append(bin_name);
602
603 if (glibc_dir_arg) |dir| {
604 // TODO look into making this a call to `linuxTriple`. This
605 // needs the directory to be called "i686" rather than
606 // "x86" which is why we do it manually here.
607 const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}";
608 const cpu_arch = exe.target.getCpuArch();
609 const os_tag = exe.target.getOsTag();
610 const abi = exe.target.getAbi();
611 const cpu_arch_name: []const u8 = if (cpu_arch == .x86)
612 "i686"
613 else
614 @tagName(cpu_arch);
615 const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{
616 dir, cpu_arch_name, @tagName(os_tag), @tagName(abi),
617 });
618
619 try interp_argv.append("-L");
620 try interp_argv.append(full_dir);
621 }
622
623 try interp_argv.appendSlice(argv);
624 } else {
625 return failForeign(self, "-fqemu", argv[0], exe);
626 }
627 },
628 .darling => |bin_name| {
629 if (b.enable_darling) {
630 try interp_argv.append(bin_name);
631 try interp_argv.appendSlice(argv);
632 } else {
633 return failForeign(self, "-fdarling", argv[0], exe);
634 }
635 },
636 .wasmtime => |bin_name| {
637 if (b.enable_wasmtime) {
638 try interp_argv.append(bin_name);
639 try interp_argv.append("--dir=.");
640 try interp_argv.append(argv[0]);
641 try interp_argv.append("--");
642 try interp_argv.appendSlice(argv[1..]);
643 } else {
644 return failForeign(self, "-fwasmtime", argv[0], exe);
645 }
646 },
647 .bad_dl => |foreign_dl| {
648 if (allow_skip) return error.MakeSkipped;
649
650 const host_dl = b.host.dynamic_linker.get() orelse "(none)";
651
652 return step.fail(
653 \\the host system is unable to execute binaries from the target
654 \\ because the host dynamic linker is '{s}',
655 \\ while the target dynamic linker is '{s}'.
656 \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step
657 , .{ host_dl, foreign_dl });
658 },
659 .bad_os_or_cpu => {
660 if (allow_skip) return error.MakeSkipped;
661
662 const host_name = try b.host.target.zigTriple(b.allocator);
663 const foreign_name = try exe.target.zigTriple(b.allocator);
664
665 return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{
666 host_name, foreign_name,
667 });
668 },
669 }
373670
374 var child = std.ChildProcess.init(argv, builder.allocator);
375 child.cwd = cwd;
376 child.env_map = env_map orelse builder.env_map;
671 if (exe.target.isWindows()) {
672 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
673 self.addPathForDynLibs(exe);
674 }
377675
378 child.stdin_behavior = stdin_behavior;
379 child.stdout_behavior = stdIoActionToBehavior(stdout_action);
380 child.stderr_behavior = stdIoActionToBehavior(stderr_action);
676 try Step.handleVerbose2(step.owner, self.cwd, self.env_map, interp_argv.items);
381677
382 if (print)
383 printCmd(cwd, argv);
678 break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| {
679 return step.fail("unable to spawn interpreter {s}: {s}", .{
680 interp_argv.items[0], @errorName(e),
681 });
682 };
683 }
384684
385 child.spawn() catch |err| {
386 std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) });
387 return err;
685 return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
388686 };
389687
390 // TODO need to poll to read these streams to prevent a deadlock (or rely on evented I/O).
688 step.result_duration_ns = result.elapsed_ns;
689 step.result_peak_rss = result.peak_rss;
690 step.test_results = result.stdio.test_results;
391691
392 var stdout: ?[]const u8 = null;
393 defer if (stdout) |s| builder.allocator.free(s);
692 // Capture stdout and stderr to GeneratedFile objects.
693 const Stream = struct {
694 captured: ?*Output,
695 is_null: bool,
696 bytes: []const u8,
697 };
698 for ([_]Stream{
699 .{
700 .captured = self.captured_stdout,
701 .is_null = result.stdio.stdout_null,
702 .bytes = result.stdio.stdout,
703 },
704 .{
705 .captured = self.captured_stderr,
706 .is_null = result.stdio.stderr_null,
707 .bytes = result.stdio.stderr,
708 },
709 }) |stream| {
710 if (stream.captured) |output| {
711 assert(!stream.is_null);
712
713 const output_components = .{ "o", digest.?, output.basename };
714 const output_path = try b.cache_root.join(arena, &output_components);
715 output.generated_file.path = output_path;
716
717 const sub_path = try fs.path.join(arena, &output_components);
718 const sub_path_dirname = fs.path.dirname(sub_path).?;
719 b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
720 return step.fail("unable to make path '{}{s}': {s}", .{
721 b.cache_root, sub_path_dirname, @errorName(err),
722 });
723 };
724 b.cache_root.handle.writeFile(sub_path, stream.bytes) catch |err| {
725 return step.fail("unable to write file '{}{s}': {s}", .{
726 b.cache_root, sub_path, @errorName(err),
727 });
728 };
729 }
730 }
394731
395 switch (stdout_action) {
396 .expect_exact, .expect_matches => {
397 stdout = try child.stdout.?.reader().readAllAlloc(builder.allocator, max_stdout_size);
732 const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
733
734 switch (self.stdio) {
735 .check => |checks| for (checks.items) |check| switch (check) {
736 .expect_stderr_exact => |expected_bytes| {
737 assert(!result.stdio.stderr_null);
738 if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) {
739 return step.fail(
740 \\
741 \\========= expected this stderr: =========
742 \\{s}
743 \\========= but found: ====================
744 \\{s}
745 \\========= from the following command: ===
746 \\{s}
747 , .{
748 expected_bytes,
749 result.stdio.stderr,
750 try Step.allocPrintCmd(arena, self.cwd, final_argv),
751 });
752 }
753 },
754 .expect_stderr_match => |match| {
755 assert(!result.stdio.stderr_null);
756 if (mem.indexOf(u8, result.stdio.stderr, match) == null) {
757 return step.fail(
758 \\
759 \\========= expected to find in stderr: =========
760 \\{s}
761 \\========= but stderr does not contain it: =====
762 \\{s}
763 \\========= from the following command: =========
764 \\{s}
765 , .{
766 match,
767 result.stdio.stderr,
768 try Step.allocPrintCmd(arena, self.cwd, final_argv),
769 });
770 }
771 },
772 .expect_stdout_exact => |expected_bytes| {
773 assert(!result.stdio.stdout_null);
774 if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) {
775 return step.fail(
776 \\
777 \\========= expected this stdout: =========
778 \\{s}
779 \\========= but found: ====================
780 \\{s}
781 \\========= from the following command: ===
782 \\{s}
783 , .{
784 expected_bytes,
785 result.stdio.stdout,
786 try Step.allocPrintCmd(arena, self.cwd, final_argv),
787 });
788 }
789 },
790 .expect_stdout_match => |match| {
791 assert(!result.stdio.stdout_null);
792 if (mem.indexOf(u8, result.stdio.stdout, match) == null) {
793 return step.fail(
794 \\
795 \\========= expected to find in stdout: =========
796 \\{s}
797 \\========= but stdout does not contain it: =====
798 \\{s}
799 \\========= from the following command: =========
800 \\{s}
801 , .{
802 match,
803 result.stdio.stdout,
804 try Step.allocPrintCmd(arena, self.cwd, final_argv),
805 });
806 }
807 },
808 .expect_term => |expected_term| {
809 if (!termMatches(expected_term, result.term)) {
810 return step.fail("the following command {} (expected {}):\n{s}", .{
811 fmtTerm(result.term),
812 fmtTerm(expected_term),
813 try Step.allocPrintCmd(arena, self.cwd, final_argv),
814 });
815 }
816 },
817 },
818 .zig_test => {
819 const expected_term: std.process.Child.Term = .{ .Exited = 0 };
820 if (!termMatches(expected_term, result.term)) {
821 return step.fail("the following command {} (expected {}):\n{s}", .{
822 fmtTerm(result.term),
823 fmtTerm(expected_term),
824 try Step.allocPrintCmd(arena, self.cwd, final_argv),
825 });
826 }
827 if (!result.stdio.test_results.isSuccess()) {
828 return step.fail(
829 "the following test command failed:\n{s}",
830 .{try Step.allocPrintCmd(arena, self.cwd, final_argv)},
831 );
832 }
833 },
834 else => {
835 try step.handleChildProcessTerm(result.term, self.cwd, final_argv);
398836 },
399 .inherit, .ignore => {},
400837 }
838}
401839
402 var stderr: ?[]const u8 = null;
403 defer if (stderr) |s| builder.allocator.free(s);
840const ChildProcResult = struct {
841 term: std.process.Child.Term,
842 elapsed_ns: u64,
843 peak_rss: usize,
404844
405 switch (stderr_action) {
406 .expect_exact, .expect_matches => {
407 stderr = try child.stderr.?.reader().readAllAlloc(builder.allocator, max_stdout_size);
408 },
409 .inherit, .ignore => {},
845 stdio: StdIoResult,
846};
847
848fn spawnChildAndCollect(
849 self: *RunStep,
850 argv: []const []const u8,
851 has_side_effects: bool,
852 prog_node: *std.Progress.Node,
853) !ChildProcResult {
854 const b = self.step.owner;
855 const arena = b.allocator;
856
857 var child = std.process.Child.init(argv, arena);
858 if (self.cwd) |cwd| {
859 child.cwd = b.pathFromRoot(cwd);
860 } else {
861 child.cwd = b.build_root.path;
862 child.cwd_dir = b.build_root.handle;
410863 }
864 child.env_map = self.env_map orelse b.env_map;
865 child.request_resource_usage_statistics = true;
411866
412 const term = child.wait() catch |err| {
413 std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) });
414 return err;
867 child.stdin_behavior = switch (self.stdio) {
868 .infer_from_args => if (has_side_effects) .Inherit else .Close,
869 .inherit => .Inherit,
870 .check => .Close,
871 .zig_test => .Pipe,
415872 };
873 child.stdout_behavior = switch (self.stdio) {
874 .infer_from_args => if (has_side_effects) .Inherit else .Ignore,
875 .inherit => .Inherit,
876 .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore,
877 .zig_test => .Pipe,
878 };
879 child.stderr_behavior = switch (self.stdio) {
880 .infer_from_args => if (has_side_effects) .Inherit else .Pipe,
881 .inherit => .Inherit,
882 .check => .Pipe,
883 .zig_test => .Pipe,
884 };
885 if (self.captured_stdout != null) child.stdout_behavior = .Pipe;
886 if (self.captured_stderr != null) child.stderr_behavior = .Pipe;
887 if (self.stdin != null) {
888 assert(child.stdin_behavior != .Inherit);
889 child.stdin_behavior = .Pipe;
890 }
416891
417 if (!termMatches(expected_term, term)) {
418 if (builder.prominent_compile_errors) {
419 std.debug.print("Run step {} (expected {})\n", .{ fmtTerm(term), fmtTerm(expected_term) });
420 } else {
421 std.debug.print("The following command {} (expected {}):\n", .{ fmtTerm(term), fmtTerm(expected_term) });
422 printCmd(cwd, argv);
892 try child.spawn();
893 var timer = try std.time.Timer.start();
894
895 const result = if (self.stdio == .zig_test)
896 evalZigTest(self, &child, prog_node)
897 else
898 evalGeneric(self, &child);
899
900 const term = try child.wait();
901 const elapsed_ns = timer.read();
902
903 return .{
904 .stdio = try result,
905 .term = term,
906 .elapsed_ns = elapsed_ns,
907 .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0,
908 };
909}
910
911const StdIoResult = struct {
912 // These use boolean flags instead of optionals as a workaround for
913 // https://github.com/ziglang/zig/issues/14783
914 stdout: []const u8,
915 stderr: []const u8,
916 stdout_null: bool,
917 stderr_null: bool,
918 test_results: Step.TestResults,
919};
920
921fn evalZigTest(
922 self: *RunStep,
923 child: *std.process.Child,
924 prog_node: *std.Progress.Node,
925) !StdIoResult {
926 const gpa = self.step.owner.allocator;
927 const arena = self.step.owner.allocator;
928
929 var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
930 .stdout = child.stdout.?,
931 .stderr = child.stderr.?,
932 });
933 defer poller.deinit();
934
935 try sendMessage(child.stdin.?, .query_test_metadata);
936
937 const Header = std.zig.Server.Message.Header;
938
939 const stdout = poller.fifo(.stdout);
940 const stderr = poller.fifo(.stderr);
941
942 var fail_count: u32 = 0;
943 var skip_count: u32 = 0;
944 var leak_count: u32 = 0;
945 var test_count: u32 = 0;
946
947 var metadata: ?TestMetadata = null;
948
949 var sub_prog_node: ?std.Progress.Node = null;
950 defer if (sub_prog_node) |*n| n.end();
951
952 poll: while (true) {
953 while (stdout.readableLength() < @sizeOf(Header)) {
954 if (!(try poller.poll())) break :poll;
955 }
956 const header = stdout.reader().readStruct(Header) catch unreachable;
957 while (stdout.readableLength() < header.bytes_len) {
958 if (!(try poller.poll())) break :poll;
423959 }
424 return error.UnexpectedExit;
960 const body = stdout.readableSliceOfLen(header.bytes_len);
961
962 switch (header.tag) {
963 .zig_version => {
964 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
965 return self.step.fail(
966 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
967 .{ builtin.zig_version_string, body },
968 );
969 }
970 },
971 .test_metadata => {
972 const TmHdr = std.zig.Server.Message.TestMetadata;
973 const tm_hdr = @ptrCast(*align(1) const TmHdr, body);
974 test_count = tm_hdr.tests_len;
975
976 const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)];
977 const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)];
978 const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)];
979 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];
980
981 const names = std.mem.bytesAsSlice(u32, names_bytes);
982 const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes);
983 const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes);
984 const names_aligned = try arena.alloc(u32, names.len);
985 for (names_aligned, names) |*dest, src| dest.* = src;
986
987 const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len);
988 for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src;
989
990 const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len);
991 for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src;
992
993 prog_node.setEstimatedTotalItems(names.len);
994 metadata = .{
995 .string_bytes = try arena.dupe(u8, string_bytes),
996 .names = names_aligned,
997 .async_frame_lens = async_frame_lens_aligned,
998 .expected_panic_msgs = expected_panic_msgs_aligned,
999 .next_index = 0,
1000 .prog_node = prog_node,
1001 };
1002
1003 try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node);
1004 },
1005 .test_results => {
1006 const md = metadata.?;
1007
1008 const TrHdr = std.zig.Server.Message.TestResults;
1009 const tr_hdr = @ptrCast(*align(1) const TrHdr, body);
1010 fail_count += @boolToInt(tr_hdr.flags.fail);
1011 skip_count += @boolToInt(tr_hdr.flags.skip);
1012 leak_count += @boolToInt(tr_hdr.flags.leak);
1013
1014 if (tr_hdr.flags.fail or tr_hdr.flags.leak) {
1015 const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0);
1016 const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n");
1017 const label = if (tr_hdr.flags.fail) "failed" else "leaked";
1018 if (msg.len > 0) {
1019 try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg });
1020 } else {
1021 try self.step.addError("'{s}' {s}", .{ name, label });
1022 }
1023 stderr.discard(msg.len);
1024 }
1025
1026 try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node);
1027 },
1028 else => {}, // ignore other messages
1029 }
1030
1031 stdout.discard(body.len);
4251032 }
4261033
427 switch (stderr_action) {
428 .inherit, .ignore => {},
429 .expect_exact => |expected_bytes| {
430 if (!mem.eql(u8, expected_bytes, stderr.?)) {
431 std.debug.print(
432 \\
433 \\========= Expected this stderr: =========
434 \\{s}
435 \\========= But found: ====================
436 \\{s}
437 \\
438 , .{ expected_bytes, stderr.? });
439 printCmd(cwd, argv);
440 return error.TestFailed;
441 }
442 },
443 .expect_matches => |matches| for (matches) |match| {
444 if (mem.indexOf(u8, stderr.?, match) == null) {
445 std.debug.print(
446 \\
447 \\========= Expected to find in stderr: =========
448 \\{s}
449 \\========= But stderr does not contain it: =====
450 \\{s}
451 \\
452 , .{ match, stderr.? });
453 printCmd(cwd, argv);
454 return error.TestFailed;
455 }
456 },
1034 if (stderr.readableLength() > 0) {
1035 const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n");
1036 if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg);
4571037 }
4581038
459 switch (stdout_action) {
460 .inherit, .ignore => {},
461 .expect_exact => |expected_bytes| {
462 if (!mem.eql(u8, expected_bytes, stdout.?)) {
463 std.debug.print(
464 \\
465 \\========= Expected this stdout: =========
466 \\{s}
467 \\========= But found: ====================
468 \\{s}
469 \\
470 , .{ expected_bytes, stdout.? });
471 printCmd(cwd, argv);
472 return error.TestFailed;
473 }
474 },
475 .expect_matches => |matches| for (matches) |match| {
476 if (mem.indexOf(u8, stdout.?, match) == null) {
477 std.debug.print(
478 \\
479 \\========= Expected to find in stdout: =========
480 \\{s}
481 \\========= But stdout does not contain it: =====
482 \\{s}
483 \\
484 , .{ match, stdout.? });
485 printCmd(cwd, argv);
486 return error.TestFailed;
487 }
1039 // Send EOF to stdin.
1040 child.stdin.?.close();
1041 child.stdin = null;
1042
1043 return .{
1044 .stdout = &.{},
1045 .stderr = &.{},
1046 .stdout_null = true,
1047 .stderr_null = true,
1048 .test_results = .{
1049 .test_count = test_count,
1050 .fail_count = fail_count,
1051 .skip_count = skip_count,
1052 .leak_count = leak_count,
4881053 },
1054 };
1055}
1056
1057const TestMetadata = struct {
1058 names: []const u32,
1059 async_frame_lens: []const u32,
1060 expected_panic_msgs: []const u32,
1061 string_bytes: []const u8,
1062 next_index: u32,
1063 prog_node: *std.Progress.Node,
1064
1065 fn testName(tm: TestMetadata, index: u32) []const u8 {
1066 return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);
1067 }
1068};
1069
1070fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void {
1071 while (metadata.next_index < metadata.names.len) {
1072 const i = metadata.next_index;
1073 metadata.next_index += 1;
1074
1075 if (metadata.async_frame_lens[i] != 0) continue;
1076 if (metadata.expected_panic_msgs[i] != 0) continue;
1077
1078 const name = metadata.testName(i);
1079 if (sub_prog_node.*) |*n| n.end();
1080 sub_prog_node.* = metadata.prog_node.start(name, 0);
1081
1082 try sendRunTestMessage(in, i);
1083 return;
1084 } else {
1085 try sendMessage(in, .exit);
4891086 }
4901087}
4911088
492fn failWithCacheError(man: std.Build.Cache.Manifest, err: anyerror) noreturn {
493 const i = man.failed_file_index orelse failWithSimpleError(err);
494 const pp = man.files.items[i].prefixed_path orelse failWithSimpleError(err);
495 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
496 std.debug.print("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path });
497 std.process.exit(1);
1089fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
1090 const header: std.zig.Client.Message.Header = .{
1091 .tag = tag,
1092 .bytes_len = 0,
1093 };
1094 try file.writeAll(std.mem.asBytes(&header));
4981095}
4991096
500fn failWithSimpleError(err: anyerror) noreturn {
501 std.debug.print("{s}\n", .{@errorName(err)});
502 std.process.exit(1);
1097fn sendRunTestMessage(file: std.fs.File, index: u32) !void {
1098 const header: std.zig.Client.Message.Header = .{
1099 .tag = .run_test,
1100 .bytes_len = 4,
1101 };
1102 const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index);
1103 try file.writeAll(full_msg);
5031104}
5041105
505fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void {
506 if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd});
507 for (argv) |arg| {
508 std.debug.print("{s} ", .{arg});
1106fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult {
1107 const arena = self.step.owner.allocator;
1108
1109 if (self.stdin) |stdin| {
1110 child.stdin.?.writeAll(stdin) catch |err| {
1111 return self.step.fail("unable to write stdin: {s}", .{@errorName(err)});
1112 };
1113 child.stdin.?.close();
1114 child.stdin = null;
5091115 }
510 std.debug.print("\n", .{});
511}
5121116
513fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void {
514 addPathForDynLibsInternal(&self.step, self.builder, artifact);
1117 // These are not optionals, as a workaround for
1118 // https://github.com/ziglang/zig/issues/14783
1119 var stdout_bytes: []const u8 = undefined;
1120 var stderr_bytes: []const u8 = undefined;
1121 var stdout_null = true;
1122 var stderr_null = true;
1123
1124 if (child.stdout) |stdout| {
1125 if (child.stderr) |stderr| {
1126 var poller = std.io.poll(arena, enum { stdout, stderr }, .{
1127 .stdout = stdout,
1128 .stderr = stderr,
1129 });
1130 defer poller.deinit();
1131
1132 while (try poller.poll()) {
1133 if (poller.fifo(.stdout).count > self.max_stdio_size)
1134 return error.StdoutStreamTooLong;
1135 if (poller.fifo(.stderr).count > self.max_stdio_size)
1136 return error.StderrStreamTooLong;
1137 }
1138
1139 stdout_bytes = try poller.fifo(.stdout).toOwnedSlice();
1140 stderr_bytes = try poller.fifo(.stderr).toOwnedSlice();
1141 stdout_null = false;
1142 stderr_null = false;
1143 } else {
1144 stdout_bytes = try stdout.reader().readAllAlloc(arena, self.max_stdio_size);
1145 stdout_null = false;
1146 }
1147 } else if (child.stderr) |stderr| {
1148 stderr_bytes = try stderr.reader().readAllAlloc(arena, self.max_stdio_size);
1149 stderr_null = false;
1150 }
1151
1152 if (!stderr_null and stderr_bytes.len > 0) {
1153 // Treat stderr as an error message.
1154 const stderr_is_diagnostic = self.captured_stderr == null and switch (self.stdio) {
1155 .check => |checks| !checksContainStderr(checks.items),
1156 else => true,
1157 };
1158 if (stderr_is_diagnostic) {
1159 try self.step.result_error_msgs.append(arena, stderr_bytes);
1160 }
1161 }
1162
1163 return .{
1164 .stdout = stdout_bytes,
1165 .stderr = stderr_bytes,
1166 .stdout_null = stdout_null,
1167 .stderr_null = stderr_null,
1168 .test_results = .{},
1169 };
5151170}
5161171
517/// This should only be used for internal usage, this is called automatically
518/// for the user.
519pub fn addPathForDynLibsInternal(step: *Step, builder: *std.Build, artifact: *CompileStep) void {
1172fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void {
1173 const b = self.step.owner;
5201174 for (artifact.link_objects.items) |link_object| {
5211175 switch (link_object) {
5221176 .other_step => |other| {
5231177 if (other.target.isWindows() and other.isDynamicLibrary()) {
524 addPathDirInternal(step, builder, fs.path.dirname(other.getOutputSource().getPath(builder)).?);
525 addPathForDynLibsInternal(step, builder, other);
1178 addPathDir(self, fs.path.dirname(other.getOutputSource().getPath(b)).?);
1179 addPathForDynLibs(self, other);
5261180 }
5271181 },
5281182 else => {},
5291183 }
5301184 }
5311185}
1186
1187fn failForeign(
1188 self: *RunStep,
1189 suggested_flag: []const u8,
1190 argv0: []const u8,
1191 exe: *CompileStep,
1192) error{ MakeFailed, MakeSkipped, OutOfMemory } {
1193 switch (self.stdio) {
1194 .check, .zig_test => {
1195 if (self.skip_foreign_checks)
1196 return error.MakeSkipped;
1197
1198 const b = self.step.owner;
1199 const host_name = try b.host.target.zigTriple(b.allocator);
1200 const foreign_name = try exe.target.zigTriple(b.allocator);
1201
1202 return self.step.fail(
1203 \\unable to spawn foreign binary '{s}' ({s}) on host system ({s})
1204 \\ consider using {s} or enabling skip_foreign_checks in the Run step
1205 , .{ argv0, foreign_name, host_name, suggested_flag });
1206 },
1207 else => {
1208 return self.step.fail("unable to spawn foreign binary '{s}'", .{argv0});
1209 },
1210 }
1211}
1212
1213fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void {
1214 switch (stdio) {
1215 .infer_from_args, .inherit, .zig_test => {},
1216 .check => |checks| for (checks.items) |check| {
1217 hh.add(@as(std.meta.Tag(StdIo.Check), check));
1218 switch (check) {
1219 .expect_stderr_exact,
1220 .expect_stderr_match,
1221 .expect_stdout_exact,
1222 .expect_stdout_match,
1223 => |s| hh.addBytes(s),
1224
1225 .expect_term => |term| {
1226 hh.add(@as(std.meta.Tag(std.process.Child.Term), term));
1227 switch (term) {
1228 .Exited => |x| hh.add(x),
1229 .Signal, .Stopped, .Unknown => |x| hh.add(x),
1230 }
1231 },
1232 }
1233 },
1234 }
1235}
lib/std/Build/Step.zig+467-25
......@@ -1,9 +1,77 @@
11id: Id,
22name: []const u8,
3makeFn: *const fn (self: *Step) anyerror!void,
3owner: *Build,
4makeFn: MakeFn,
5
46dependencies: std.ArrayList(*Step),
5loop_flag: bool,
6done_flag: bool,
7/// This field is empty during execution of the user's build script, and
8/// then populated during dependency loop checking in the build runner.
9dependants: std.ArrayListUnmanaged(*Step),
10state: State,
11/// Set this field to declare an upper bound on the amount of bytes of memory it will
12/// take to run the step. Zero means no limit.
13///
14/// The idea to annotate steps that might use a high amount of RAM with an
15/// upper bound. For example, perhaps a particular set of unit tests require 4
16/// GiB of RAM, and those tests will be run under 4 different build
17/// configurations at once. This would potentially require 16 GiB of memory on
18/// the system if all 4 steps executed simultaneously, which could easily be
19/// greater than what is actually available, potentially causing the system to
20/// crash when using `zig build` at the default concurrency level.
21///
22/// This field causes the build runner to do two things:
23/// 1. ulimit child processes, so that they will fail if it would exceed this
24/// memory limit. This serves to enforce that this upper bound value is
25/// correct.
26/// 2. Ensure that the set of concurrent steps at any given time have a total
27/// max_rss value that does not exceed the `max_total_rss` value of the build
28/// runner. This value is configurable on the command line, and defaults to the
29/// total system memory available.
30max_rss: usize,
31
32result_error_msgs: std.ArrayListUnmanaged([]const u8),
33result_error_bundle: std.zig.ErrorBundle,
34result_cached: bool,
35result_duration_ns: ?u64,
36/// 0 means unavailable or not reported.
37result_peak_rss: usize,
38test_results: TestResults,
39
40/// The return addresss associated with creation of this step that can be useful
41/// to print along with debugging messages.
42debug_stack_trace: [n_debug_stack_frames]usize,
43
44pub const TestResults = struct {
45 fail_count: u32 = 0,
46 skip_count: u32 = 0,
47 leak_count: u32 = 0,
48 test_count: u32 = 0,
49
50 pub fn isSuccess(tr: TestResults) bool {
51 return tr.fail_count == 0 and tr.leak_count == 0;
52 }
53
54 pub fn passCount(tr: TestResults) u32 {
55 return tr.test_count - tr.fail_count - tr.skip_count;
56 }
57};
58
59pub const MakeFn = *const fn (self: *Step, prog_node: *std.Progress.Node) anyerror!void;
60
61const n_debug_stack_frames = 4;
62
63pub const State = enum {
64 precheck_unstarted,
65 precheck_started,
66 precheck_done,
67 running,
68 dependency_failure,
69 success,
70 failure,
71 /// This state indicates that the step did not complete, however, it also did not fail,
72 /// and it is safe to continue executing its dependencies.
73 skipped,
74};
775
876pub const Id = enum {
977 top_level,
......@@ -17,7 +85,6 @@ pub const Id = enum {
1785 translate_c,
1886 write_file,
1987 run,
20 emulatable_run,
2188 check_file,
2289 check_object,
2390 config_header,
......@@ -38,7 +105,6 @@ pub const Id = enum {
38105 .translate_c => Build.TranslateCStep,
39106 .write_file => Build.WriteFileStep,
40107 .run => Build.RunStep,
41 .emulatable_run => Build.EmulatableRunStep,
42108 .check_file => Build.CheckFileStep,
43109 .check_object => Build.CheckObjectStep,
44110 .config_header => Build.ConfigHeaderStep,
......@@ -49,39 +115,99 @@ pub const Id = enum {
49115 }
50116};
51117
52pub fn init(
118pub const Options = struct {
53119 id: Id,
54120 name: []const u8,
55 allocator: Allocator,
56 makeFn: *const fn (self: *Step) anyerror!void,
57) Step {
58 return Step{
59 .id = id,
60 .name = allocator.dupe(u8, name) catch @panic("OOM"),
61 .makeFn = makeFn,
62 .dependencies = std.ArrayList(*Step).init(allocator),
63 .loop_flag = false,
64 .done_flag = false,
121 owner: *Build,
122 makeFn: MakeFn = makeNoOp,
123 first_ret_addr: ?usize = null,
124 max_rss: usize = 0,
125};
126
127pub fn init(options: Options) Step {
128 const arena = options.owner.allocator;
129
130 var addresses = [1]usize{0} ** n_debug_stack_frames;
131 const first_ret_addr = options.first_ret_addr orelse @returnAddress();
132 var stack_trace = std.builtin.StackTrace{
133 .instruction_addresses = &addresses,
134 .index = 0,
65135 };
66}
136 std.debug.captureStackTrace(first_ret_addr, &stack_trace);
67137
68pub fn initNoOp(id: Id, name: []const u8, allocator: Allocator) Step {
69 return init(id, name, allocator, makeNoOp);
138 return .{
139 .id = options.id,
140 .name = arena.dupe(u8, options.name) catch @panic("OOM"),
141 .owner = options.owner,
142 .makeFn = options.makeFn,
143 .dependencies = std.ArrayList(*Step).init(arena),
144 .dependants = .{},
145 .state = .precheck_unstarted,
146 .max_rss = options.max_rss,
147 .debug_stack_trace = addresses,
148 .result_error_msgs = .{},
149 .result_error_bundle = std.zig.ErrorBundle.empty,
150 .result_cached = false,
151 .result_duration_ns = null,
152 .result_peak_rss = 0,
153 .test_results = .{},
154 };
70155}
71156
72pub fn make(self: *Step) !void {
73 if (self.done_flag) return;
157/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
158/// have already reported the error. Otherwise, we add a simple error report
159/// here.
160pub fn make(s: *Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void {
161 const arena = s.owner.allocator;
162
163 s.makeFn(s, prog_node) catch |err| switch (err) {
164 error.MakeFailed => return error.MakeFailed,
165 error.MakeSkipped => return error.MakeSkipped,
166 else => {
167 s.result_error_msgs.append(arena, @errorName(err)) catch @panic("OOM");
168 return error.MakeFailed;
169 },
170 };
171
172 if (!s.test_results.isSuccess()) {
173 return error.MakeFailed;
174 }
74175
75 try self.makeFn(self);
76 self.done_flag = true;
176 if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) {
177 const msg = std.fmt.allocPrint(arena, "memory usage peaked at {d} bytes, exceeding the declared upper bound of {d}", .{
178 s.result_peak_rss, s.max_rss,
179 }) catch @panic("OOM");
180 s.result_error_msgs.append(arena, msg) catch @panic("OOM");
181 return error.MakeFailed;
182 }
77183}
78184
79185pub fn dependOn(self: *Step, other: *Step) void {
80186 self.dependencies.append(other) catch @panic("OOM");
81187}
82188
83fn makeNoOp(self: *Step) anyerror!void {
84 _ = self;
189pub fn getStackTrace(s: *Step) std.builtin.StackTrace {
190 const stack_addresses = &s.debug_stack_trace;
191 var len: usize = 0;
192 while (len < n_debug_stack_frames and stack_addresses[len] != 0) {
193 len += 1;
194 }
195 return .{
196 .instruction_addresses = stack_addresses,
197 .index = len,
198 };
199}
200
201fn makeNoOp(step: *Step, prog_node: *std.Progress.Node) anyerror!void {
202 _ = prog_node;
203
204 var all_cached = true;
205
206 for (step.dependencies.items) |dep| {
207 all_cached = all_cached and dep.result_cached;
208 }
209
210 step.result_cached = all_cached;
85211}
86212
87213pub fn cast(step: *Step, comptime T: type) ?*T {
......@@ -91,7 +217,323 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
91217 return null;
92218}
93219
220/// For debugging purposes, prints identifying information about this Step.
221pub fn dump(step: *Step) void {
222 std.debug.getStderrMutex().lock();
223 defer std.debug.getStderrMutex().unlock();
224
225 const stderr = std.io.getStdErr();
226 const w = stderr.writer();
227 const tty_config = std.debug.detectTTYConfig(stderr);
228 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
229 w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{
230 @errorName(err),
231 }) catch {};
232 return;
233 };
234 const ally = debug_info.allocator;
235 w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
236 std.debug.writeStackTrace(step.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
237 stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch {};
238 return;
239 };
240}
241
94242const Step = @This();
95243const std = @import("../std.zig");
96244const Build = std.Build;
97245const Allocator = std.mem.Allocator;
246const assert = std.debug.assert;
247const builtin = @import("builtin");
248
249pub fn evalChildProcess(s: *Step, argv: []const []const u8) !void {
250 const arena = s.owner.allocator;
251
252 try handleChildProcUnsupported(s, null, argv);
253 try handleVerbose(s.owner, null, argv);
254
255 const result = std.ChildProcess.exec(.{
256 .allocator = arena,
257 .argv = argv,
258 }) catch |err| return s.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
259
260 if (result.stderr.len > 0) {
261 try s.result_error_msgs.append(arena, result.stderr);
262 }
263
264 try handleChildProcessTerm(s, result.term, null, argv);
265}
266
267pub fn fail(step: *Step, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, MakeFailed } {
268 try step.addError(fmt, args);
269 return error.MakeFailed;
270}
271
272pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void {
273 const arena = step.owner.allocator;
274 const msg = try std.fmt.allocPrint(arena, fmt, args);
275 try step.result_error_msgs.append(arena, msg);
276}
277
278/// Assumes that argv contains `--listen=-` and that the process being spawned
279/// is the zig compiler - the same version that compiled the build runner.
280pub fn evalZigProcess(
281 s: *Step,
282 argv: []const []const u8,
283 prog_node: *std.Progress.Node,
284) ![]const u8 {
285 assert(argv.len != 0);
286 const b = s.owner;
287 const arena = b.allocator;
288 const gpa = arena;
289
290 try handleChildProcUnsupported(s, null, argv);
291 try handleVerbose(s.owner, null, argv);
292
293 var child = std.ChildProcess.init(argv, arena);
294 child.env_map = b.env_map;
295 child.stdin_behavior = .Pipe;
296 child.stdout_behavior = .Pipe;
297 child.stderr_behavior = .Pipe;
298 child.request_resource_usage_statistics = true;
299
300 child.spawn() catch |err| return s.fail("unable to spawn {s}: {s}", .{
301 argv[0], @errorName(err),
302 });
303 var timer = try std.time.Timer.start();
304
305 var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
306 .stdout = child.stdout.?,
307 .stderr = child.stderr.?,
308 });
309 defer poller.deinit();
310
311 try sendMessage(child.stdin.?, .update);
312 try sendMessage(child.stdin.?, .exit);
313
314 const Header = std.zig.Server.Message.Header;
315 var result: ?[]const u8 = null;
316
317 var node_name: std.ArrayListUnmanaged(u8) = .{};
318 defer node_name.deinit(gpa);
319 var sub_prog_node = prog_node.start("", 0);
320 defer sub_prog_node.end();
321
322 const stdout = poller.fifo(.stdout);
323
324 poll: while (true) {
325 while (stdout.readableLength() < @sizeOf(Header)) {
326 if (!(try poller.poll())) break :poll;
327 }
328 const header = stdout.reader().readStruct(Header) catch unreachable;
329 while (stdout.readableLength() < header.bytes_len) {
330 if (!(try poller.poll())) break :poll;
331 }
332 const body = stdout.readableSliceOfLen(header.bytes_len);
333
334 switch (header.tag) {
335 .zig_version => {
336 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
337 return s.fail(
338 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
339 .{ builtin.zig_version_string, body },
340 );
341 }
342 },
343 .error_bundle => {
344 const EbHdr = std.zig.Server.Message.ErrorBundle;
345 const eb_hdr = @ptrCast(*align(1) const EbHdr, body);
346 const extra_bytes =
347 body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
348 const string_bytes =
349 body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
350 // TODO: use @ptrCast when the compiler supports it
351 const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
352 const extra_array = try arena.alloc(u32, unaligned_extra.len);
353 // TODO: use @memcpy when it supports slices
354 for (extra_array, unaligned_extra) |*dst, src| dst.* = src;
355 s.result_error_bundle = .{
356 .string_bytes = try arena.dupe(u8, string_bytes),
357 .extra = extra_array,
358 };
359 },
360 .progress => {
361 node_name.clearRetainingCapacity();
362 try node_name.appendSlice(gpa, body);
363 sub_prog_node.setName(node_name.items);
364 },
365 .emit_bin_path => {
366 const EbpHdr = std.zig.Server.Message.EmitBinPath;
367 const ebp_hdr = @ptrCast(*align(1) const EbpHdr, body);
368 s.result_cached = ebp_hdr.flags.cache_hit;
369 result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
370 },
371 else => {}, // ignore other messages
372 }
373
374 stdout.discard(body.len);
375 }
376
377 const stderr = poller.fifo(.stderr);
378 if (stderr.readableLength() > 0) {
379 try s.result_error_msgs.append(arena, try stderr.toOwnedSlice());
380 }
381
382 // Send EOF to stdin.
383 child.stdin.?.close();
384 child.stdin = null;
385
386 const term = child.wait() catch |err| {
387 return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) });
388 };
389 s.result_duration_ns = timer.read();
390 s.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0;
391
392 // Special handling for CompileStep that is expecting compile errors.
393 if (s.cast(Build.CompileStep)) |compile| switch (term) {
394 .Exited => {
395 // Note that the exit code may be 0 in this case due to the
396 // compiler server protocol.
397 if (compile.expect_errors.len != 0 and s.result_error_bundle.errorMessageCount() > 0) {
398 return error.NeedCompileErrorCheck;
399 }
400 },
401 else => {},
402 };
403
404 try handleChildProcessTerm(s, term, null, argv);
405
406 if (s.result_error_bundle.errorMessageCount() > 0) {
407 return s.fail("the following command failed with {d} compilation errors:\n{s}", .{
408 s.result_error_bundle.errorMessageCount(),
409 try allocPrintCmd(arena, null, argv),
410 });
411 }
412
413 return result orelse return s.fail(
414 "the following command failed to communicate the compilation result:\n{s}",
415 .{try allocPrintCmd(arena, null, argv)},
416 );
417}
418
419fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
420 const header: std.zig.Client.Message.Header = .{
421 .tag = tag,
422 .bytes_len = 0,
423 };
424 try file.writeAll(std.mem.asBytes(&header));
425}
426
427pub fn handleVerbose(
428 b: *Build,
429 opt_cwd: ?[]const u8,
430 argv: []const []const u8,
431) error{OutOfMemory}!void {
432 return handleVerbose2(b, opt_cwd, null, argv);
433}
434
435pub fn handleVerbose2(
436 b: *Build,
437 opt_cwd: ?[]const u8,
438 opt_env: ?*const std.process.EnvMap,
439 argv: []const []const u8,
440) error{OutOfMemory}!void {
441 if (b.verbose) {
442 // Intention of verbose is to print all sub-process command lines to
443 // stderr before spawning them.
444 const text = try allocPrintCmd2(b.allocator, opt_cwd, opt_env, argv);
445 std.debug.print("{s}\n", .{text});
446 }
447}
448
449pub inline fn handleChildProcUnsupported(
450 s: *Step,
451 opt_cwd: ?[]const u8,
452 argv: []const []const u8,
453) error{ OutOfMemory, MakeFailed }!void {
454 if (!std.process.can_spawn) {
455 return s.fail(
456 "unable to execute the following command: host cannot spawn child processes\n{s}",
457 .{try allocPrintCmd(s.owner.allocator, opt_cwd, argv)},
458 );
459 }
460}
461
462pub fn handleChildProcessTerm(
463 s: *Step,
464 term: std.ChildProcess.Term,
465 opt_cwd: ?[]const u8,
466 argv: []const []const u8,
467) error{ MakeFailed, OutOfMemory }!void {
468 const arena = s.owner.allocator;
469 switch (term) {
470 .Exited => |code| {
471 if (code != 0) {
472 return s.fail(
473 "the following command exited with error code {d}:\n{s}",
474 .{ code, try allocPrintCmd(arena, opt_cwd, argv) },
475 );
476 }
477 },
478 .Signal, .Stopped, .Unknown => {
479 return s.fail(
480 "the following command terminated unexpectedly:\n{s}",
481 .{try allocPrintCmd(arena, opt_cwd, argv)},
482 );
483 },
484 }
485}
486
487pub fn allocPrintCmd(
488 arena: Allocator,
489 opt_cwd: ?[]const u8,
490 argv: []const []const u8,
491) Allocator.Error![]u8 {
492 return allocPrintCmd2(arena, opt_cwd, null, argv);
493}
494
495pub fn allocPrintCmd2(
496 arena: Allocator,
497 opt_cwd: ?[]const u8,
498 opt_env: ?*const std.process.EnvMap,
499 argv: []const []const u8,
500) Allocator.Error![]u8 {
501 var buf: std.ArrayListUnmanaged(u8) = .{};
502 if (opt_cwd) |cwd| try buf.writer(arena).print("cd {s} && ", .{cwd});
503 if (opt_env) |env| {
504 const process_env_map = std.process.getEnvMap(arena) catch std.process.EnvMap.init(arena);
505 var it = env.iterator();
506 while (it.next()) |entry| {
507 const key = entry.key_ptr.*;
508 const value = entry.value_ptr.*;
509 if (process_env_map.get(key)) |process_value| {
510 if (std.mem.eql(u8, value, process_value)) continue;
511 }
512 try buf.writer(arena).print("{s}={s} ", .{ key, value });
513 }
514 }
515 for (argv) |arg| {
516 try buf.writer(arena).print("{s} ", .{arg});
517 }
518 return buf.toOwnedSlice(arena);
519}
520
521pub fn cacheHit(s: *Step, man: *std.Build.Cache.Manifest) !bool {
522 s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err);
523 return s.result_cached;
524}
525
526fn failWithCacheError(s: *Step, man: *const std.Build.Cache.Manifest, err: anyerror) anyerror {
527 const i = man.failed_file_index orelse return err;
528 const pp = man.files.items[i].prefixed_path orelse return err;
529 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
530 return s.fail("{s}: {s}/{s}", .{ @errorName(err), prefix, pp.sub_path });
531}
532
533pub fn writeManifest(s: *Step, man: *std.Build.Cache.Manifest) !void {
534 if (s.test_results.isSuccess()) {
535 man.writeManifest() catch |err| {
536 try s.addError("unable to write cache manifest: {s}", .{@errorName(err)});
537 };
538 }
539}
lib/std/Build/TranslateCStep.zig+30-22
......@@ -11,7 +11,6 @@ const TranslateCStep = @This();
1111pub const base_id = .translate_c;
1212
1313step: Step,
14builder: *std.Build,
1514source: std.Build.FileSource,
1615include_dirs: std.ArrayList([]const u8),
1716c_macros: std.ArrayList([]const u8),
......@@ -26,15 +25,19 @@ pub const Options = struct {
2625 optimize: std.builtin.OptimizeMode,
2726};
2827
29pub fn create(builder: *std.Build, options: Options) *TranslateCStep {
30 const self = builder.allocator.create(TranslateCStep) catch @panic("OOM");
31 const source = options.source_file.dupe(builder);
28pub fn create(owner: *std.Build, options: Options) *TranslateCStep {
29 const self = owner.allocator.create(TranslateCStep) catch @panic("OOM");
30 const source = options.source_file.dupe(owner);
3231 self.* = TranslateCStep{
33 .step = Step.init(.translate_c, "translate-c", builder.allocator, make),
34 .builder = builder,
32 .step = Step.init(.{
33 .id = .translate_c,
34 .name = "translate-c",
35 .owner = owner,
36 .makeFn = make,
37 }),
3538 .source = source,
36 .include_dirs = std.ArrayList([]const u8).init(builder.allocator),
37 .c_macros = std.ArrayList([]const u8).init(builder.allocator),
39 .include_dirs = std.ArrayList([]const u8).init(owner.allocator),
40 .c_macros = std.ArrayList([]const u8).init(owner.allocator),
3841 .out_basename = undefined,
3942 .target = options.target,
4043 .optimize = options.optimize,
......@@ -54,7 +57,7 @@ pub const AddExecutableOptions = struct {
5457
5558/// Creates a step to build an executable from the translated source.
5659pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep {
57 return self.builder.addExecutable(.{
60 return self.step.owner.addExecutable(.{
5861 .root_source_file = .{ .generated = &self.output_file },
5962 .name = options.name orelse "translated_c",
6063 .version = options.version,
......@@ -65,43 +68,49 @@ pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *Comp
6568}
6669
6770pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void {
68 self.include_dirs.append(self.builder.dupePath(include_dir)) catch @panic("OOM");
71 self.include_dirs.append(self.step.owner.dupePath(include_dir)) catch @panic("OOM");
6972}
7073
7174pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep {
72 return CheckFileStep.create(self.builder, .{ .generated = &self.output_file }, self.builder.dupeStrings(expected_matches));
75 return CheckFileStep.create(
76 self.step.owner,
77 .{ .generated = &self.output_file },
78 .{ .expected_matches = expected_matches },
79 );
7380}
7481
7582/// If the value is omitted, it is set to 1.
7683/// `name` and `value` need not live longer than the function call.
7784pub fn defineCMacro(self: *TranslateCStep, name: []const u8, value: ?[]const u8) void {
78 const macro = std.Build.constructCMacro(self.builder.allocator, name, value);
85 const macro = std.Build.constructCMacro(self.step.owner.allocator, name, value);
7986 self.c_macros.append(macro) catch @panic("OOM");
8087}
8188
8289/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1.
8390pub fn defineCMacroRaw(self: *TranslateCStep, name_and_value: []const u8) void {
84 self.c_macros.append(self.builder.dupe(name_and_value)) catch @panic("OOM");
91 self.c_macros.append(self.step.owner.dupe(name_and_value)) catch @panic("OOM");
8592}
8693
87fn make(step: *Step) !void {
94fn make(step: *Step, prog_node: *std.Progress.Node) !void {
95 const b = step.owner;
8896 const self = @fieldParentPtr(TranslateCStep, "step", step);
8997
90 var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);
91 try argv_list.append(self.builder.zig_exe);
98 var argv_list = std.ArrayList([]const u8).init(b.allocator);
99 try argv_list.append(b.zig_exe);
92100 try argv_list.append("translate-c");
93101 try argv_list.append("-lc");
94102
95103 try argv_list.append("--enable-cache");
104 try argv_list.append("--listen=-");
96105
97106 if (!self.target.isNative()) {
98107 try argv_list.append("-target");
99 try argv_list.append(try self.target.zigTriple(self.builder.allocator));
108 try argv_list.append(try self.target.zigTriple(b.allocator));
100109 }
101110
102111 switch (self.optimize) {
103112 .Debug => {}, // Skip since it's the default.
104 else => try argv_list.append(self.builder.fmt("-O{s}", .{@tagName(self.optimize)})),
113 else => try argv_list.append(b.fmt("-O{s}", .{@tagName(self.optimize)})),
105114 }
106115
107116 for (self.include_dirs.items) |include_dir| {
......@@ -114,16 +123,15 @@ fn make(step: *Step) !void {
114123 try argv_list.append(c_macro);
115124 }
116125
117 try argv_list.append(self.source.getPath(self.builder));
126 try argv_list.append(self.source.getPath(b));
118127
119 const output_path_nl = try self.builder.execFromStep(argv_list.items, &self.step);
120 const output_path = mem.trimRight(u8, output_path_nl, "\r\n");
128 const output_path = try step.evalZigProcess(argv_list.items, prog_node);
121129
122130 self.out_basename = fs.path.basename(output_path);
123131 const output_dir = fs.path.dirname(output_path).?;
124132
125133 self.output_file.path = try fs.path.join(
126 self.builder.allocator,
134 b.allocator,
127135 &[_][]const u8{ output_dir, self.out_basename },
128136 );
129137}
lib/std/Build/WriteFileStep.zig+145-67
......@@ -10,11 +10,11 @@
1010//! control.
1111
1212step: Step,
13builder: *std.Build,
1413/// The elements here are pointers because we need stable pointers for the
1514/// GeneratedFile field.
1615files: std.ArrayListUnmanaged(*File),
1716output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
17generated_directory: std.Build.GeneratedFile,
1818
1919pub const base_id = .write_file;
2020
......@@ -34,24 +34,34 @@ pub const Contents = union(enum) {
3434 copy: std.Build.FileSource,
3535};
3636
37pub fn init(builder: *std.Build) WriteFileStep {
38 return .{
39 .builder = builder,
40 .step = Step.init(.write_file, "writefile", builder.allocator, make),
37pub fn create(owner: *std.Build) *WriteFileStep {
38 const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM");
39 wf.* = .{
40 .step = Step.init(.{
41 .id = .write_file,
42 .name = "WriteFile",
43 .owner = owner,
44 .makeFn = make,
45 }),
4146 .files = .{},
4247 .output_source_files = .{},
48 .generated_directory = .{ .step = &wf.step },
4349 };
50 return wf;
4451}
4552
4653pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {
47 const gpa = wf.builder.allocator;
54 const b = wf.step.owner;
55 const gpa = b.allocator;
4856 const file = gpa.create(File) catch @panic("OOM");
4957 file.* = .{
5058 .generated_file = .{ .step = &wf.step },
51 .sub_path = wf.builder.dupePath(sub_path),
52 .contents = .{ .bytes = wf.builder.dupe(bytes) },
59 .sub_path = b.dupePath(sub_path),
60 .contents = .{ .bytes = b.dupe(bytes) },
5361 };
5462 wf.files.append(gpa, file) catch @panic("OOM");
63
64 wf.maybeUpdateName();
5565}
5666
5767/// Place the file into the generated directory within the local cache,
......@@ -62,14 +72,18 @@ pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {
6272/// required sub-path exists.
6373/// This is the option expected to be used most commonly with `addCopyFile`.
6474pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void {
65 const gpa = wf.builder.allocator;
75 const b = wf.step.owner;
76 const gpa = b.allocator;
6677 const file = gpa.create(File) catch @panic("OOM");
6778 file.* = .{
6879 .generated_file = .{ .step = &wf.step },
69 .sub_path = wf.builder.dupePath(sub_path),
80 .sub_path = b.dupePath(sub_path),
7081 .contents = .{ .copy = source },
7182 };
7283 wf.files.append(gpa, file) catch @panic("OOM");
84
85 wf.maybeUpdateName();
86 source.addStepDependencies(&wf.step);
7387}
7488
7589/// A path relative to the package root.
......@@ -79,10 +93,26 @@ pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: [
7993/// those changes to version control.
8094/// A file added this way is not available with `getFileSource`.
8195pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void {
82 wf.output_source_files.append(wf.builder.allocator, .{
96 const b = wf.step.owner;
97 wf.output_source_files.append(b.allocator, .{
8398 .contents = .{ .copy = source },
8499 .sub_path = sub_path,
85100 }) catch @panic("OOM");
101 source.addStepDependencies(&wf.step);
102}
103
104/// A path relative to the package root.
105/// Be careful with this because it updates source files. This should not be
106/// used as part of the normal build process, but as a utility occasionally
107/// run by a developer with intent to modify source files and then commit
108/// those changes to version control.
109/// A file added this way is not available with `getFileSource`.
110pub fn addBytesToSource(wf: *WriteFileStep, bytes: []const u8, sub_path: []const u8) void {
111 const b = wf.step.owner;
112 wf.output_source_files.append(b.allocator, .{
113 .contents = .{ .bytes = bytes },
114 .sub_path = sub_path,
115 }) catch @panic("OOM");
86116}
87117
88118/// Gets a file source for the given sub_path. If the file does not exist, returns `null`.
......@@ -95,21 +125,63 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo
95125 return null;
96126}
97127
98fn make(step: *Step) !void {
128/// Returns a `FileSource` representing the base directory that contains all the
129/// files from this `WriteFileStep`.
130pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource {
131 return .{ .generated = &wf.generated_directory };
132}
133
134fn maybeUpdateName(wf: *WriteFileStep) void {
135 if (wf.files.items.len == 1) {
136 // First time adding a file; update name.
137 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
138 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path});
139 }
140 }
141}
142
143fn make(step: *Step, prog_node: *std.Progress.Node) !void {
144 _ = prog_node;
145 const b = step.owner;
99146 const wf = @fieldParentPtr(WriteFileStep, "step", step);
100147
101148 // Writing to source files is kind of an extra capability of this
102149 // WriteFileStep - arguably it should be a different step. But anyway here
103150 // it is, it happens unconditionally and does not interact with the other
104151 // files here.
152 var any_miss = false;
105153 for (wf.output_source_files.items) |output_source_file| {
106 const basename = fs.path.basename(output_source_file.sub_path);
107154 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
108 var dir = try wf.builder.build_root.handle.makeOpenPath(dirname, .{});
109 defer dir.close();
110 try writeFile(wf, dir, output_source_file.contents, basename);
111 } else {
112 try writeFile(wf, wf.builder.build_root.handle, output_source_file.contents, basename);
155 b.build_root.handle.makePath(dirname) catch |err| {
156 return step.fail("unable to make path '{}{s}': {s}", .{
157 b.build_root, dirname, @errorName(err),
158 });
159 };
160 }
161 switch (output_source_file.contents) {
162 .bytes => |bytes| {
163 b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| {
164 return step.fail("unable to write file '{}{s}': {s}", .{
165 b.build_root, output_source_file.sub_path, @errorName(err),
166 });
167 };
168 any_miss = true;
169 },
170 .copy => |file_source| {
171 const source_path = file_source.getPath(b);
172 const prev_status = fs.Dir.updateFile(
173 fs.cwd(),
174 source_path,
175 b.build_root.handle,
176 output_source_file.sub_path,
177 .{},
178 ) catch |err| {
179 return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{
180 source_path, b.build_root, output_source_file.sub_path, @errorName(err),
181 });
182 };
183 any_miss = any_miss or prev_status == .stale;
184 },
113185 }
114186 }
115187
......@@ -120,7 +192,7 @@ fn make(step: *Step) !void {
120192 // If, for example, a hard-coded path was used as the location to put WriteFileStep
121193 // files, then two WriteFileSteps executing in parallel might clobber each other.
122194
123 var man = wf.builder.cache.obtain();
195 var man = b.cache.obtain();
124196 defer man.deinit();
125197
126198 // Random bytes to make WriteFileStep unique. Refresh this with
......@@ -135,76 +207,82 @@ fn make(step: *Step) !void {
135207 man.hash.addBytes(bytes);
136208 },
137209 .copy => |file_source| {
138 _ = try man.addFile(file_source.getPath(wf.builder), null);
210 _ = try man.addFile(file_source.getPath(b), null);
139211 },
140212 }
141213 }
142214
143 if (man.hit() catch |err| failWithCacheError(man, err)) {
144 // Cache hit, skip writing file data.
215 if (try step.cacheHit(&man)) {
145216 const digest = man.final();
146217 for (wf.files.items) |file| {
147 file.generated_file.path = try wf.builder.cache_root.join(
148 wf.builder.allocator,
149 &.{ "o", &digest, file.sub_path },
150 );
218 file.generated_file.path = try b.cache_root.join(b.allocator, &.{
219 "o", &digest, file.sub_path,
220 });
151221 }
222 wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
152223 return;
153224 }
154225
155226 const digest = man.final();
156227 const cache_path = "o" ++ fs.path.sep_str ++ digest;
157228
158 var cache_dir = wf.builder.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
159 std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });
160 return err;
229 wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
230
231 var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {
232 return step.fail("unable to make path '{}{s}': {s}", .{
233 b.cache_root, cache_path, @errorName(err),
234 });
161235 };
162236 defer cache_dir.close();
163237
164238 for (wf.files.items) |file| {
165 const basename = fs.path.basename(file.sub_path);
166239 if (fs.path.dirname(file.sub_path)) |dirname| {
167 var dir = try wf.builder.cache_root.handle.makeOpenPath(dirname, .{});
168 defer dir.close();
169 try writeFile(wf, dir, file.contents, basename);
170 } else {
171 try writeFile(wf, cache_dir, file.contents, basename);
240 cache_dir.makePath(dirname) catch |err| {
241 return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{
242 b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err),
243 });
244 };
245 }
246 switch (file.contents) {
247 .bytes => |bytes| {
248 cache_dir.writeFile(file.sub_path, bytes) catch |err| {
249 return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{
250 b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err),
251 });
252 };
253 },
254 .copy => |file_source| {
255 const source_path = file_source.getPath(b);
256 const prev_status = fs.Dir.updateFile(
257 fs.cwd(),
258 source_path,
259 cache_dir,
260 file.sub_path,
261 .{},
262 ) catch |err| {
263 return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{
264 source_path,
265 b.cache_root,
266 cache_path,
267 fs.path.sep,
268 file.sub_path,
269 @errorName(err),
270 });
271 };
272 // At this point we already will mark the step as a cache miss.
273 // But this is kind of a partial cache hit since individual
274 // file copies may be avoided. Oh well, this information is
275 // discarded.
276 _ = prev_status;
277 },
172278 }
173279
174 file.generated_file.path = try wf.builder.cache_root.join(
175 wf.builder.allocator,
176 &.{ cache_path, file.sub_path },
177 );
178 }
179
180 try man.writeManifest();
181}
182
183fn writeFile(wf: *WriteFileStep, dir: fs.Dir, contents: Contents, basename: []const u8) !void {
184 // TODO after landing concurrency PR, improve error reporting here
185 switch (contents) {
186 .bytes => |bytes| return dir.writeFile(basename, bytes),
187 .copy => |file_source| {
188 const source_path = file_source.getPath(wf.builder);
189 const prev_status = try fs.Dir.updateFile(fs.cwd(), source_path, dir, basename, .{});
190 _ = prev_status; // TODO logging (affected by open PR regarding concurrency)
191 },
280 file.generated_file.path = try b.cache_root.join(b.allocator, &.{
281 cache_path, file.sub_path,
282 });
192283 }
193}
194
195/// TODO consolidate this with the same function in RunStep?
196/// Also properly deal with concurrency (see open PR)
197fn failWithCacheError(man: std.Build.Cache.Manifest, err: anyerror) noreturn {
198 const i = man.failed_file_index orelse failWithSimpleError(err);
199 const pp = man.files.items[i].prefixed_path orelse failWithSimpleError(err);
200 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";
201 std.debug.print("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path });
202 std.process.exit(1);
203}
204284
205fn failWithSimpleError(err: anyerror) noreturn {
206 std.debug.print("{s}\n", .{@errorName(err)});
207 std.process.exit(1);
285 try step.writeManifest(&man);
208286}
209287
210288const std = @import("../std.zig");
lib/std/Progress.zig+72-26
......@@ -126,6 +126,21 @@ pub const Node = struct {
126126 }
127127 }
128128
129 /// Thread-safe.
130 pub fn setName(self: *Node, name: []const u8) void {
131 const progress = self.context;
132 progress.update_mutex.lock();
133 defer progress.update_mutex.unlock();
134 self.name = name;
135 if (self.parent) |parent| {
136 @atomicStore(?*Node, &parent.recently_updated_child, self, .Release);
137 if (parent.parent) |grand_parent| {
138 @atomicStore(?*Node, &grand_parent.recently_updated_child, parent, .Release);
139 }
140 if (progress.timer) |*timer| progress.maybeRefreshWithHeldLock(timer);
141 }
142 }
143
129144 /// Thread-safe. 0 means unknown.
130145 pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
131146 @atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic);
......@@ -174,16 +189,20 @@ pub fn maybeRefresh(self: *Progress) void {
174189 if (self.timer) |*timer| {
175190 if (!self.update_mutex.tryLock()) return;
176191 defer self.update_mutex.unlock();
177 const now = timer.read();
178 if (now < self.initial_delay_ns) return;
179 // TODO I have observed this to happen sometimes. I think we need to follow Rust's
180 // lead and guarantee monotonically increasing times in the std lib itself.
181 if (now < self.prev_refresh_timestamp) return;
182 if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
183 return self.refreshWithHeldLock();
192 maybeRefreshWithHeldLock(self, timer);
184193 }
185194}
186195
196fn maybeRefreshWithHeldLock(self: *Progress, timer: *std.time.Timer) void {
197 const now = timer.read();
198 if (now < self.initial_delay_ns) return;
199 // TODO I have observed this to happen sometimes. I think we need to follow Rust's
200 // lead and guarantee monotonically increasing times in the std lib itself.
201 if (now < self.prev_refresh_timestamp) return;
202 if (now - self.prev_refresh_timestamp < self.refresh_rate_ns) return;
203 return self.refreshWithHeldLock();
204}
205
187206/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe.
188207pub fn refresh(self: *Progress) void {
189208 if (!self.update_mutex.tryLock()) return;
......@@ -192,32 +211,28 @@ pub fn refresh(self: *Progress) void {
192211 return self.refreshWithHeldLock();
193212}
194213
195fn refreshWithHeldLock(self: *Progress) void {
196 const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;
197 if (is_dumb and self.dont_print_on_dumb) return;
198
199 const file = self.terminal orelse return;
200
201 var end: usize = 0;
202 if (self.columns_written > 0) {
214fn clearWithHeldLock(p: *Progress, end_ptr: *usize) void {
215 const file = p.terminal orelse return;
216 var end = end_ptr.*;
217 if (p.columns_written > 0) {
203218 // restore the cursor position by moving the cursor
204219 // `columns_written` cells to the left, then clear the rest of the
205220 // line
206 if (self.supports_ansi_escape_codes) {
207 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len;
208 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
221 if (p.supports_ansi_escape_codes) {
222 end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[{d}D", .{p.columns_written}) catch unreachable).len;
223 end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
209224 } else if (builtin.os.tag == .windows) winapi: {
210 std.debug.assert(self.is_windows_terminal);
225 std.debug.assert(p.is_windows_terminal);
211226
212227 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
213228 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
214229 // stop trying to write to this file
215 self.terminal = null;
230 p.terminal = null;
216231 break :winapi;
217232 }
218233
219234 var cursor_pos = windows.COORD{
220 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
235 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, p.columns_written),
221236 .Y = info.dwCursorPosition.Y,
222237 };
223238
......@@ -235,7 +250,7 @@ fn refreshWithHeldLock(self: *Progress) void {
235250 &written,
236251 ) != windows.TRUE) {
237252 // stop trying to write to this file
238 self.terminal = null;
253 p.terminal = null;
239254 break :winapi;
240255 }
241256 if (windows.kernel32.FillConsoleOutputCharacterW(
......@@ -246,22 +261,33 @@ fn refreshWithHeldLock(self: *Progress) void {
246261 &written,
247262 ) != windows.TRUE) {
248263 // stop trying to write to this file
249 self.terminal = null;
264 p.terminal = null;
250265 break :winapi;
251266 }
252267 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) {
253268 // stop trying to write to this file
254 self.terminal = null;
269 p.terminal = null;
255270 break :winapi;
256271 }
257272 } else {
258273 // we are in a "dumb" terminal like in acme or writing to a file
259 self.output_buffer[end] = '\n';
274 p.output_buffer[end] = '\n';
260275 end += 1;
261276 }
262277
263 self.columns_written = 0;
278 p.columns_written = 0;
264279 }
280 end_ptr.* = end;
281}
282
283fn refreshWithHeldLock(self: *Progress) void {
284 const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;
285 if (is_dumb and self.dont_print_on_dumb) return;
286
287 const file = self.terminal orelse return;
288
289 var end: usize = 0;
290 clearWithHeldLock(self, &end);
265291
266292 if (!self.done) {
267293 var need_ellipse = false;
......@@ -318,6 +344,26 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
318344 self.columns_written = 0;
319345}
320346
347/// Allows the caller to freely write to stderr until unlock_stderr() is called.
348/// During the lock, the progress information is cleared from the terminal.
349pub fn lock_stderr(p: *Progress) void {
350 p.update_mutex.lock();
351 if (p.terminal) |file| {
352 var end: usize = 0;
353 clearWithHeldLock(p, &end);
354 _ = file.write(p.output_buffer[0..end]) catch {
355 // stop trying to write to this file
356 p.terminal = null;
357 };
358 }
359 std.debug.getStderrMutex().lock();
360}
361
362pub fn unlock_stderr(p: *Progress) void {
363 std.debug.getStderrMutex().unlock();
364 p.update_mutex.unlock();
365}
366
321367fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
322368 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
323369 const amt = written.len;
lib/std/Thread.zig+2
......@@ -16,6 +16,8 @@ pub const Mutex = @import("Thread/Mutex.zig");
1616pub const Semaphore = @import("Thread/Semaphore.zig");
1717pub const Condition = @import("Thread/Condition.zig");
1818pub const RwLock = @import("Thread/RwLock.zig");
19pub const Pool = @import("Thread/Pool.zig");
20pub const WaitGroup = @import("Thread/WaitGroup.zig");
1921
2022pub const use_pthreads = target.os.tag != .windows and target.os.tag != .wasi and builtin.link_libc;
2123const is_gnu = target.abi.isGnu();
lib/std/Thread/Pool.zig created+159
......@@ -0,0 +1,159 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const Pool = @This();
4const WaitGroup = @import("WaitGroup.zig");
5
6mutex: std.Thread.Mutex = .{},
7cond: std.Thread.Condition = .{},
8run_queue: RunQueue = .{},
9is_running: bool = true,
10allocator: std.mem.Allocator,
11threads: []std.Thread,
12
13const RunQueue = std.SinglyLinkedList(Runnable);
14const Runnable = struct {
15 runFn: RunProto,
16};
17
18const RunProto = *const fn (*Runnable) void;
19
20pub const Options = struct {
21 allocator: std.mem.Allocator,
22 n_jobs: ?u32 = null,
23};
24
25pub fn init(pool: *Pool, options: Options) !void {
26 const allocator = options.allocator;
27
28 pool.* = .{
29 .allocator = allocator,
30 .threads = &[_]std.Thread{},
31 };
32
33 if (builtin.single_threaded) {
34 return;
35 }
36
37 const thread_count = options.n_jobs orelse @max(1, std.Thread.getCpuCount() catch 1);
38 pool.threads = try allocator.alloc(std.Thread, thread_count);
39 errdefer allocator.free(pool.threads);
40
41 // kill and join any threads we spawned previously on error.
42 var spawned: usize = 0;
43 errdefer pool.join(spawned);
44
45 for (pool.threads) |*thread| {
46 thread.* = try std.Thread.spawn(.{}, worker, .{pool});
47 spawned += 1;
48 }
49}
50
51pub fn deinit(pool: *Pool) void {
52 pool.join(pool.threads.len); // kill and join all threads.
53 pool.* = undefined;
54}
55
56fn join(pool: *Pool, spawned: usize) void {
57 if (builtin.single_threaded) {
58 return;
59 }
60
61 {
62 pool.mutex.lock();
63 defer pool.mutex.unlock();
64
65 // ensure future worker threads exit the dequeue loop
66 pool.is_running = false;
67 }
68
69 // wake up any sleeping threads (this can be done outside the mutex)
70 // then wait for all the threads we know are spawned to complete.
71 pool.cond.broadcast();
72 for (pool.threads[0..spawned]) |thread| {
73 thread.join();
74 }
75
76 pool.allocator.free(pool.threads);
77}
78
79pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) !void {
80 if (builtin.single_threaded) {
81 @call(.auto, func, args);
82 return;
83 }
84
85 const Args = @TypeOf(args);
86 const Closure = struct {
87 arguments: Args,
88 pool: *Pool,
89 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
90
91 fn runFn(runnable: *Runnable) void {
92 const run_node = @fieldParentPtr(RunQueue.Node, "data", runnable);
93 const closure = @fieldParentPtr(@This(), "run_node", run_node);
94 @call(.auto, func, closure.arguments);
95
96 // The thread pool's allocator is protected by the mutex.
97 const mutex = &closure.pool.mutex;
98 mutex.lock();
99 defer mutex.unlock();
100
101 closure.pool.allocator.destroy(closure);
102 }
103 };
104
105 {
106 pool.mutex.lock();
107 defer pool.mutex.unlock();
108
109 const closure = try pool.allocator.create(Closure);
110 closure.* = .{
111 .arguments = args,
112 .pool = pool,
113 };
114
115 pool.run_queue.prepend(&closure.run_node);
116 }
117
118 // Notify waiting threads outside the lock to try and keep the critical section small.
119 pool.cond.signal();
120}
121
122fn worker(pool: *Pool) void {
123 pool.mutex.lock();
124 defer pool.mutex.unlock();
125
126 while (true) {
127 while (pool.run_queue.popFirst()) |run_node| {
128 // Temporarily unlock the mutex in order to execute the run_node
129 pool.mutex.unlock();
130 defer pool.mutex.lock();
131
132 const runFn = run_node.data.runFn;
133 runFn(&run_node.data);
134 }
135
136 // Stop executing instead of waiting if the thread pool is no longer running.
137 if (pool.is_running) {
138 pool.cond.wait(&pool.mutex);
139 } else {
140 break;
141 }
142 }
143}
144
145pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void {
146 while (!wait_group.isDone()) {
147 if (blk: {
148 pool.mutex.lock();
149 defer pool.mutex.unlock();
150 break :blk pool.run_queue.popFirst();
151 }) |run_node| {
152 run_node.data.runFn(&run_node.data);
153 continue;
154 }
155
156 wait_group.wait();
157 return;
158 }
159}
lib/std/Thread/WaitGroup.zig created+46
......@@ -0,0 +1,46 @@
1const std = @import("std");
2const Atomic = std.atomic.Atomic;
3const assert = std.debug.assert;
4const WaitGroup = @This();
5
6const is_waiting: usize = 1 << 0;
7const one_pending: usize = 1 << 1;
8
9state: Atomic(usize) = Atomic(usize).init(0),
10event: std.Thread.ResetEvent = .{},
11
12pub fn start(self: *WaitGroup) void {
13 const state = self.state.fetchAdd(one_pending, .Monotonic);
14 assert((state / one_pending) < (std.math.maxInt(usize) / one_pending));
15}
16
17pub fn finish(self: *WaitGroup) void {
18 const state = self.state.fetchSub(one_pending, .Release);
19 assert((state / one_pending) > 0);
20
21 if (state == (one_pending | is_waiting)) {
22 self.state.fence(.Acquire);
23 self.event.set();
24 }
25}
26
27pub fn wait(self: *WaitGroup) void {
28 var state = self.state.fetchAdd(is_waiting, .Acquire);
29 assert(state & is_waiting == 0);
30
31 if ((state / one_pending) > 0) {
32 self.event.wait();
33 }
34}
35
36pub fn reset(self: *WaitGroup) void {
37 self.state.store(0, .Monotonic);
38 self.event.reset();
39}
40
41pub fn isDone(wg: *WaitGroup) bool {
42 const state = wg.state.load(.Acquire);
43 assert(state & is_waiting == 0);
44
45 return (state / one_pending) == 0;
46}
lib/std/c.zig+2-1
......@@ -153,7 +153,8 @@ pub extern "c" fn linkat(oldfd: c.fd_t, oldpath: [*:0]const u8, newfd: c.fd_t, n
153153pub extern "c" fn unlink(path: [*:0]const u8) c_int;
154154pub extern "c" fn unlinkat(dirfd: c.fd_t, path: [*:0]const u8, flags: c_uint) c_int;
155155pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;
156pub extern "c" fn waitpid(pid: c.pid_t, stat_loc: ?*c_int, options: c_int) c.pid_t;
156pub extern "c" fn waitpid(pid: c.pid_t, status: ?*c_int, options: c_int) c.pid_t;
157pub extern "c" fn wait4(pid: c.pid_t, status: ?*c_int, options: c_int, ru: ?*c.rusage) c.pid_t;
157158pub extern "c" fn fork() c_int;
158159pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) c_int;
159160pub extern "c" fn faccessat(dirfd: c.fd_t, path: [*:0]const u8, mode: c_uint, flags: c_uint) c_int;
lib/std/child_process.zig+49-1
......@@ -17,10 +17,12 @@ const Os = std.builtin.Os;
1717const TailQueue = std.TailQueue;
1818const maxInt = std.math.maxInt;
1919const assert = std.debug.assert;
20const is_darwin = builtin.target.isDarwin();
2021
2122pub const ChildProcess = struct {
2223 pub const Id = switch (builtin.os.tag) {
2324 .windows => windows.HANDLE,
25 .wasi => void,
2426 else => os.pid_t,
2527 };
2628
......@@ -70,6 +72,43 @@ pub const ChildProcess = struct {
7072 /// Darwin-only. Start child process in suspended state as if SIGSTOP was sent.
7173 start_suspended: bool = false,
7274
75 /// Set to true to obtain rusage information for the child process.
76 /// Depending on the target platform and implementation status, the
77 /// requested statistics may or may not be available. If they are
78 /// available, then the `resource_usage_statistics` field will be populated
79 /// after calling `wait`.
80 /// On Linux, this obtains rusage statistics from wait4().
81 request_resource_usage_statistics: bool = false,
82
83 /// This is available after calling wait if
84 /// `request_resource_usage_statistics` was set to `true` before calling
85 /// `spawn`.
86 resource_usage_statistics: ResourceUsageStatistics = .{},
87
88 pub const ResourceUsageStatistics = struct {
89 rusage: @TypeOf(rusage_init) = rusage_init,
90
91 /// Returns the peak resident set size of the child process, in bytes,
92 /// if available.
93 pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize {
94 switch (builtin.os.tag) {
95 .linux => {
96 if (rus.rusage) |ru| {
97 return @intCast(usize, ru.maxrss) * 1024;
98 } else {
99 return null;
100 }
101 },
102 else => return null,
103 }
104 }
105
106 const rusage_init = switch (builtin.os.tag) {
107 .linux => @as(?std.os.rusage, null),
108 else => {},
109 };
110 };
111
73112 pub const Arg0Expand = os.Arg0Expand;
74113
75114 pub const SpawnError = error{
......@@ -332,7 +371,16 @@ pub const ChildProcess = struct {
332371 }
333372
334373 fn waitUnwrapped(self: *ChildProcess) !void {
335 const res: os.WaitPidResult = os.waitpid(self.id, 0);
374 const res: os.WaitPidResult = res: {
375 if (builtin.os.tag == .linux and self.request_resource_usage_statistics) {
376 var ru: std.os.rusage = undefined;
377 const res = os.wait4(self.id, 0, &ru);
378 self.resource_usage_statistics.rusage = ru;
379 break :res res;
380 }
381
382 break :res os.waitpid(self.id, 0);
383 };
336384 const status = res.status;
337385 self.cleanupStreams();
338386 self.handleWaitResult(status);
lib/std/debug.zig+33
......@@ -635,6 +635,7 @@ pub const TTY = struct {
635635 pub const Color = enum {
636636 Red,
637637 Green,
638 Yellow,
638639 Cyan,
639640 White,
640641 Dim,
......@@ -659,6 +660,7 @@ pub const TTY = struct {
659660 const color_string = switch (color) {
660661 .Red => "\x1b[31;1m",
661662 .Green => "\x1b[32;1m",
663 .Yellow => "\x1b[33;1m",
662664 .Cyan => "\x1b[36;1m",
663665 .White => "\x1b[37;1m",
664666 .Bold => "\x1b[1m",
......@@ -671,6 +673,7 @@ pub const TTY = struct {
671673 const attributes = switch (color) {
672674 .Red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
673675 .Green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
676 .Yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
674677 .Cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
675678 .White, .Bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
676679 .Dim => windows.FOREGROUND_INTENSITY,
......@@ -682,6 +685,36 @@ pub const TTY = struct {
682685 },
683686 };
684687 }
688
689 pub fn writeDEC(conf: Config, writer: anytype, codepoint: u8) !void {
690 const bytes = switch (conf) {
691 .no_color, .windows_api => switch (codepoint) {
692 0x50...0x5e => @as(*const [1]u8, &codepoint),
693 0x6a => "+", // ┘
694 0x6b => "+", // ┐
695 0x6c => "+", // ┌
696 0x6d => "+", // └
697 0x6e => "+", // ┼
698 0x71 => "-", // ─
699 0x74 => "+", // ├
700 0x75 => "+", // ┤
701 0x76 => "+", // ┴
702 0x77 => "+", // ┬
703 0x78 => "|", // │
704 else => " ", // TODO
705 },
706 .escape_codes => switch (codepoint) {
707 // Here we avoid writing the DEC beginning sequence and
708 // ending sequence in separate syscalls by putting the
709 // beginning and ending sequence into the same string
710 // literals, to prevent terminals ending up in bad states
711 // in case a crash happens between syscalls.
712 inline 0x50...0x7f => |x| "\x1B\x28\x30" ++ [1]u8{x} ++ "\x1B\x28\x42",
713 else => unreachable,
714 },
715 };
716 return writer.writeAll(bytes);
717 }
685718 };
686719};
687720
lib/std/fifo.zig+27
......@@ -164,6 +164,17 @@ pub fn LinearFifo(
164164 return self.readableSliceMut(offset);
165165 }
166166
167 pub fn readableSliceOfLen(self: *Self, len: usize) []const T {
168 assert(len <= self.count);
169 const buf = self.readableSlice(0);
170 if (buf.len >= len) {
171 return buf[0..len];
172 } else {
173 self.realign();
174 return self.readableSlice(0)[0..len];
175 }
176 }
177
167178 /// Discard first `count` items in the fifo
168179 pub fn discard(self: *Self, count: usize) void {
169180 assert(count <= self.count);
......@@ -383,6 +394,22 @@ pub fn LinearFifo(
383394 self.discard(try dest_writer.write(self.readableSlice(0)));
384395 }
385396 }
397
398 pub fn toOwnedSlice(self: *Self) Allocator.Error![]T {
399 if (self.head != 0) self.realign();
400 assert(self.head == 0);
401 assert(self.count <= self.buf.len);
402 const allocator = self.allocator;
403 if (allocator.resize(self.buf, self.count)) {
404 const result = self.buf[0..self.count];
405 self.* = Self.init(allocator);
406 return result;
407 }
408 const new_memory = try allocator.dupe(T, self.buf[0..self.count]);
409 allocator.free(self.buf);
410 self.* = Self.init(allocator);
411 return new_memory;
412 }
386413 };
387414}
388415
lib/std/fs/file.zig+33-5
......@@ -1048,12 +1048,27 @@ pub const File = struct {
10481048 /// Returns the number of bytes read. If the number read is smaller than the total bytes
10491049 /// from all the buffers, it means the file reached the end. Reaching the end of a file
10501050 /// is not an error condition.
1051 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1052 /// order to handle partial reads from the underlying OS layer.
1053 /// See https://github.com/ziglang/zig/issues/7699
1051 ///
1052 /// The `iovecs` parameter is mutable because:
1053 /// * This function needs to mutate the fields in order to handle partial
1054 /// reads from the underlying OS layer.
1055 /// * The OS layer expects pointer addresses to be inside the application's address space
1056 /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1057 /// addresses when the length is zero. So this function modifies the iov_base fields
1058 /// when the length is zero.
1059 ///
1060 /// Related open issue: https://github.com/ziglang/zig/issues/7699
10541061 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize {
10551062 if (iovecs.len == 0) return 0;
10561063
1064 // We use the address of this local variable for all zero-length
1065 // vectors so that the OS does not complain that we are giving it
1066 // addresses outside the application's address space.
1067 var garbage: [1]u8 = undefined;
1068 for (iovecs) |*v| {
1069 if (v.iov_len == 0) v.iov_base = &garbage;
1070 }
1071
10571072 var i: usize = 0;
10581073 var off: usize = 0;
10591074 while (true) {
......@@ -1181,13 +1196,26 @@ pub const File = struct {
11811196 }
11821197 }
11831198
1184 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1185 /// order to handle partial writes from the underlying OS layer.
1199 /// The `iovecs` parameter is mutable because:
1200 /// * This function needs to mutate the fields in order to handle partial
1201 /// writes from the underlying OS layer.
1202 /// * The OS layer expects pointer addresses to be inside the application's address space
1203 /// even if the length is zero. Meanwhile, in Zig, slices may have undefined pointer
1204 /// addresses when the length is zero. So this function modifies the iov_base fields
1205 /// when the length is zero.
11861206 /// See https://github.com/ziglang/zig/issues/7699
11871207 /// See equivalent function: `std.net.Stream.writevAll`.
11881208 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
11891209 if (iovecs.len == 0) return;
11901210
1211 // We use the address of this local variable for all zero-length
1212 // vectors so that the OS does not complain that we are giving it
1213 // addresses outside the application's address space.
1214 var garbage: [1]u8 = undefined;
1215 for (iovecs) |*v| {
1216 if (v.iov_len == 0) v.iov_base = &garbage;
1217 }
1218
11911219 var i: usize = 0;
11921220 while (true) {
11931221 var amt = try self.writev(iovecs[i..]);
lib/std/fs/test.zig+22-8
......@@ -1124,17 +1124,31 @@ test "open file with exclusive lock twice, make sure second lock waits" {
11241124test "open file with exclusive nonblocking lock twice (absolute paths)" {
11251125 if (builtin.os.tag == .wasi) return error.SkipZigTest;
11261126
1127 const allocator = testing.allocator;
1127 var random_bytes: [12]u8 = undefined;
1128 std.crypto.random.bytes(&random_bytes);
11281129
1129 const cwd = try std.process.getCwdAlloc(allocator);
1130 defer allocator.free(cwd);
1131 const file_paths: [2][]const u8 = .{ cwd, "zig-test-absolute-paths.txt" };
1132 const filename = try fs.path.resolve(allocator, &file_paths);
1133 defer allocator.free(filename);
1130 var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
1131 _ = fs.base64_encoder.encode(&random_b64, &random_bytes);
11341132
1135 const file1 = try fs.createFileAbsolute(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
1133 const sub_path = random_b64 ++ "-zig-test-absolute-paths.txt";
11361134
1137 const file2 = fs.createFileAbsolute(filename, .{ .lock = .Exclusive, .lock_nonblocking = true });
1135 const gpa = testing.allocator;
1136
1137 const cwd = try std.process.getCwdAlloc(gpa);
1138 defer gpa.free(cwd);
1139
1140 const filename = try fs.path.resolve(gpa, &[_][]const u8{ cwd, sub_path });
1141 defer gpa.free(filename);
1142
1143 const file1 = try fs.createFileAbsolute(filename, .{
1144 .lock = .Exclusive,
1145 .lock_nonblocking = true,
1146 });
1147
1148 const file2 = fs.createFileAbsolute(filename, .{
1149 .lock = .Exclusive,
1150 .lock_nonblocking = true,
1151 });
11381152 file1.close();
11391153 try testing.expectError(error.WouldBlock, file2);
11401154
lib/std/heap.zig+1
......@@ -19,6 +19,7 @@ pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig"
1919pub const WasmAllocator = @import("heap/WasmAllocator.zig");
2020pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");
2121pub const PageAllocator = @import("heap/PageAllocator.zig");
22pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig");
2223
2324const memory_pool = @import("heap/memory_pool.zig");
2425pub const MemoryPool = memory_pool.MemoryPool;
lib/std/heap/ThreadSafeAllocator.zig created+45
......@@ -0,0 +1,45 @@
1//! Wraps a non-thread-safe allocator and makes it thread-safe.
2
3child_allocator: Allocator,
4mutex: std.Thread.Mutex = .{},
5
6pub fn allocator(self: *ThreadSafeAllocator) Allocator {
7 return .{
8 .ptr = self,
9 .vtable = &.{
10 .alloc = alloc,
11 .resize = resize,
12 .free = free,
13 },
14 };
15}
16
17fn alloc(ctx: *anyopaque, n: usize, log2_ptr_align: u8, ra: usize) ?[*]u8 {
18 const self = @ptrCast(*ThreadSafeAllocator, @alignCast(@alignOf(ThreadSafeAllocator), ctx));
19 self.mutex.lock();
20 defer self.mutex.unlock();
21
22 return self.child_allocator.rawAlloc(n, log2_ptr_align, ra);
23}
24
25fn resize(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, new_len: usize, ret_addr: usize) bool {
26 const self = @ptrCast(*ThreadSafeAllocator, @alignCast(@alignOf(ThreadSafeAllocator), ctx));
27
28 self.mutex.lock();
29 defer self.mutex.unlock();
30
31 return self.child_allocator.rawResize(buf, log2_buf_align, new_len, ret_addr);
32}
33
34fn free(ctx: *anyopaque, buf: []u8, log2_buf_align: u8, ret_addr: usize) void {
35 const self = @ptrCast(*ThreadSafeAllocator, @alignCast(@alignOf(ThreadSafeAllocator), ctx));
36
37 self.mutex.lock();
38 defer self.mutex.unlock();
39
40 return self.child_allocator.rawFree(buf, log2_buf_align, ret_addr);
41}
42
43const std = @import("../std.zig");
44const ThreadSafeAllocator = @This();
45const Allocator = std.mem.Allocator;
lib/std/mem.zig+4-9
......@@ -196,13 +196,8 @@ test "Allocator.resize" {
196196/// dest.len must be >= source.len.
197197/// If the slices overlap, dest.ptr must be <= src.ptr.
198198pub fn copy(comptime T: type, dest: []T, source: []const T) void {
199 // TODO instead of manually doing this check for the whole array
200 // and turning off runtime safety, the compiler should detect loops like
201 // this and automatically omit safety checks for loops
202 @setRuntimeSafety(false);
203 assert(dest.len >= source.len);
204 for (source, 0..) |s, i|
205 dest[i] = s;
199 for (dest[0..source.len], source) |*d, s|
200 d.* = s;
206201}
207202
208203/// Copy all of source into dest at position 0.
......@@ -611,8 +606,8 @@ test "lessThan" {
611606pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
612607 if (a.len != b.len) return false;
613608 if (a.ptr == b.ptr) return true;
614 for (a, 0..) |item, index| {
615 if (b[index] != item) return false;
609 for (a, b) |a_elem, b_elem| {
610 if (a_elem != b_elem) return false;
616611 }
617612 return true;
618613}
lib/std/os.zig+27-1
......@@ -766,6 +766,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
766766/// This operation is non-atomic on the following systems:
767767/// * Windows
768768/// On these systems, the read races with concurrent writes to the same file descriptor.
769///
770/// This function assumes that all vectors, including zero-length vectors, have
771/// a pointer within the address space of the application.
769772pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
770773 if (builtin.os.tag == .windows) {
771774 // TODO improve this to use ReadFileScatter
......@@ -1167,6 +1170,9 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
11671170/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
11681171///
11691172/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur.
1173///
1174/// This function assumes that all vectors, including zero-length vectors, have
1175/// a pointer within the address space of the application.
11701176pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
11711177 if (builtin.os.tag == .windows) {
11721178 // TODO improve this to use WriteFileScatter
......@@ -4000,8 +4006,28 @@ pub const WaitPidResult = struct {
40004006pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
40014007 const Status = if (builtin.link_libc) c_int else u32;
40024008 var status: Status = undefined;
4009 const coerced_flags = if (builtin.link_libc) @intCast(c_int, flags) else flags;
4010 while (true) {
4011 const rc = system.waitpid(pid, &status, coerced_flags);
4012 switch (errno(rc)) {
4013 .SUCCESS => return .{
4014 .pid = @intCast(pid_t, rc),
4015 .status = @bitCast(u32, status),
4016 },
4017 .INTR => continue,
4018 .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error.
4019 .INVAL => unreachable, // Invalid flags.
4020 else => unreachable,
4021 }
4022 }
4023}
4024
4025pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult {
4026 const Status = if (builtin.link_libc) c_int else u32;
4027 var status: Status = undefined;
4028 const coerced_flags = if (builtin.link_libc) @intCast(c_int, flags) else flags;
40034029 while (true) {
4004 const rc = system.waitpid(pid, &status, if (builtin.link_libc) @intCast(c_int, flags) else flags);
4030 const rc = system.wait4(pid, &status, coerced_flags, ru);
40054031 switch (errno(rc)) {
40064032 .SUCCESS => return .{
40074033 .pid = @intCast(pid_t, rc),
lib/std/os/linux.zig+74-10
......@@ -944,6 +944,16 @@ pub fn waitpid(pid: pid_t, status: *u32, flags: u32) usize {
944944 return syscall4(.wait4, @bitCast(usize, @as(isize, pid)), @ptrToInt(status), flags, 0);
945945}
946946
947pub fn wait4(pid: pid_t, status: *u32, flags: u32, usage: ?*rusage) usize {
948 return syscall4(
949 .wait4,
950 @bitCast(usize, @as(isize, pid)),
951 @ptrToInt(status),
952 flags,
953 @ptrToInt(usage),
954 );
955}
956
947957pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32) usize {
948958 return syscall5(.waitid, @enumToInt(id_type), @bitCast(usize, @as(isize, id)), @ptrToInt(infop), flags, 0);
949959}
......@@ -1716,26 +1726,26 @@ pub fn pidfd_send_signal(pidfd: fd_t, sig: i32, info: ?*siginfo_t, flags: u32) u
17161726 );
17171727}
17181728
1719pub fn process_vm_readv(pid: pid_t, local: [*]const iovec, local_count: usize, remote: [*]const iovec, remote_count: usize, flags: usize) usize {
1729pub fn process_vm_readv(pid: pid_t, local: []iovec, remote: []const iovec_const, flags: usize) usize {
17201730 return syscall6(
17211731 .process_vm_readv,
17221732 @bitCast(usize, @as(isize, pid)),
1723 @ptrToInt(local),
1724 local_count,
1725 @ptrToInt(remote),
1726 remote_count,
1733 @ptrToInt(local.ptr),
1734 local.len,
1735 @ptrToInt(remote.ptr),
1736 remote.len,
17271737 flags,
17281738 );
17291739}
17301740
1731pub fn process_vm_writev(pid: pid_t, local: [*]const iovec, local_count: usize, remote: [*]const iovec, remote_count: usize, flags: usize) usize {
1741pub fn process_vm_writev(pid: pid_t, local: []const iovec_const, remote: []const iovec_const, flags: usize) usize {
17321742 return syscall6(
17331743 .process_vm_writev,
17341744 @bitCast(usize, @as(isize, pid)),
1735 @ptrToInt(local),
1736 local_count,
1737 @ptrToInt(remote),
1738 remote_count,
1745 @ptrToInt(local.ptr),
1746 local.len,
1747 @ptrToInt(remote.ptr),
1748 remote.len,
17391749 flags,
17401750 );
17411751}
......@@ -1820,6 +1830,23 @@ pub fn seccomp(operation: u32, flags: u32, args: ?*const anyopaque) usize {
18201830 return syscall3(.seccomp, operation, flags, @ptrToInt(args));
18211831}
18221832
1833pub fn ptrace(
1834 req: u32,
1835 pid: pid_t,
1836 addr: usize,
1837 data: usize,
1838 addr2: usize,
1839) usize {
1840 return syscall5(
1841 .ptrace,
1842 req,
1843 @bitCast(usize, @as(isize, pid)),
1844 addr,
1845 data,
1846 addr2,
1847 );
1848}
1849
18231850pub const E = switch (native_arch) {
18241851 .mips, .mipsel => @import("linux/errno/mips.zig").E,
18251852 .sparc, .sparcel, .sparc64 => @import("linux/errno/sparc.zig").E,
......@@ -5721,3 +5748,40 @@ pub const AUDIT = struct {
57215748 }
57225749 };
57235750};
5751
5752pub const PTRACE = struct {
5753 pub const TRACEME = 0;
5754 pub const PEEKTEXT = 1;
5755 pub const PEEKDATA = 2;
5756 pub const PEEKUSER = 3;
5757 pub const POKETEXT = 4;
5758 pub const POKEDATA = 5;
5759 pub const POKEUSER = 6;
5760 pub const CONT = 7;
5761 pub const KILL = 8;
5762 pub const SINGLESTEP = 9;
5763 pub const GETREGS = 12;
5764 pub const SETREGS = 13;
5765 pub const GETFPREGS = 14;
5766 pub const SETFPREGS = 15;
5767 pub const ATTACH = 16;
5768 pub const DETACH = 17;
5769 pub const GETFPXREGS = 18;
5770 pub const SETFPXREGS = 19;
5771 pub const SYSCALL = 24;
5772 pub const SETOPTIONS = 0x4200;
5773 pub const GETEVENTMSG = 0x4201;
5774 pub const GETSIGINFO = 0x4202;
5775 pub const SETSIGINFO = 0x4203;
5776 pub const GETREGSET = 0x4204;
5777 pub const SETREGSET = 0x4205;
5778 pub const SEIZE = 0x4206;
5779 pub const INTERRUPT = 0x4207;
5780 pub const LISTEN = 0x4208;
5781 pub const PEEKSIGINFO = 0x4209;
5782 pub const GETSIGMASK = 0x420a;
5783 pub const SETSIGMASK = 0x420b;
5784 pub const SECCOMP_GET_FILTER = 0x420c;
5785 pub const SECCOMP_GET_METADATA = 0x420d;
5786 pub const GET_SYSCALL_INFO = 0x420e;
5787};
lib/std/os/linux/io_uring.zig+104-66
......@@ -1728,10 +1728,12 @@ test "writev/fsync/readv" {
17281728 };
17291729 defer ring.deinit();
17301730
1731 var tmp = std.testing.tmpDir(.{});
1732 defer tmp.cleanup();
1733
17311734 const path = "test_io_uring_writev_fsync_readv";
1732 const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true });
1735 const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true });
17331736 defer file.close();
1734 defer std.fs.cwd().deleteFile(path) catch {};
17351737 const fd = file.handle;
17361738
17371739 const buffer_write = [_]u8{42} ** 128;
......@@ -1796,10 +1798,11 @@ test "write/read" {
17961798 };
17971799 defer ring.deinit();
17981800
1801 var tmp = std.testing.tmpDir(.{});
1802 defer tmp.cleanup();
17991803 const path = "test_io_uring_write_read";
1800 const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true });
1804 const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true });
18011805 defer file.close();
1802 defer std.fs.cwd().deleteFile(path) catch {};
18031806 const fd = file.handle;
18041807
18051808 const buffer_write = [_]u8{97} ** 20;
......@@ -1842,10 +1845,12 @@ test "write_fixed/read_fixed" {
18421845 };
18431846 defer ring.deinit();
18441847
1848 var tmp = std.testing.tmpDir(.{});
1849 defer tmp.cleanup();
1850
18451851 const path = "test_io_uring_write_read_fixed";
1846 const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true });
1852 const file = try tmp.dir.createFile(path, .{ .read = true, .truncate = true });
18471853 defer file.close();
1848 defer std.fs.cwd().deleteFile(path) catch {};
18491854 const fd = file.handle;
18501855
18511856 var raw_buffers: [2][11]u8 = undefined;
......@@ -1899,8 +1904,10 @@ test "openat" {
18991904 };
19001905 defer ring.deinit();
19011906
1907 var tmp = std.testing.tmpDir(.{});
1908 defer tmp.cleanup();
1909
19021910 const path = "test_io_uring_openat";
1903 defer std.fs.cwd().deleteFile(path) catch {};
19041911
19051912 // Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014
19061913 const path_addr = if (builtin.zig_backend == .stage2_llvm) p: {
......@@ -1910,12 +1917,12 @@ test "openat" {
19101917
19111918 const flags: u32 = os.O.CLOEXEC | os.O.RDWR | os.O.CREAT;
19121919 const mode: os.mode_t = 0o666;
1913 const sqe_openat = try ring.openat(0x33333333, linux.AT.FDCWD, path, flags, mode);
1920 const sqe_openat = try ring.openat(0x33333333, tmp.dir.fd, path, flags, mode);
19141921 try testing.expectEqual(linux.io_uring_sqe{
19151922 .opcode = .OPENAT,
19161923 .flags = 0,
19171924 .ioprio = 0,
1918 .fd = linux.AT.FDCWD,
1925 .fd = tmp.dir.fd,
19191926 .off = 0,
19201927 .addr = path_addr,
19211928 .len = mode,
......@@ -1931,12 +1938,6 @@ test "openat" {
19311938 const cqe_openat = try ring.copy_cqe();
19321939 try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data);
19331940 if (cqe_openat.err() == .INVAL) return error.SkipZigTest;
1934 // AT.FDCWD is not fully supported before kernel 5.6:
1935 // See https://lore.kernel.org/io-uring/20200207155039.12819-1-axboe@kernel.dk/T/
1936 // We use IORING_FEAT_RW_CUR_POS to know if we are pre-5.6 since that feature was added in 5.6.
1937 if (cqe_openat.err() == .BADF and (ring.features & linux.IORING_FEAT_RW_CUR_POS) == 0) {
1938 return error.SkipZigTest;
1939 }
19401941 if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res});
19411942 try testing.expect(cqe_openat.res > 0);
19421943 try testing.expectEqual(@as(u32, 0), cqe_openat.flags);
......@@ -1954,10 +1955,12 @@ test "close" {
19541955 };
19551956 defer ring.deinit();
19561957
1958 var tmp = std.testing.tmpDir(.{});
1959 defer tmp.cleanup();
1960
19571961 const path = "test_io_uring_close";
1958 const file = try std.fs.cwd().createFile(path, .{});
1962 const file = try tmp.dir.createFile(path, .{});
19591963 errdefer file.close();
1960 defer std.fs.cwd().deleteFile(path) catch {};
19611964
19621965 const sqe_close = try ring.close(0x44444444, file.handle);
19631966 try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode);
......@@ -1976,6 +1979,11 @@ test "close" {
19761979test "accept/connect/send/recv" {
19771980 if (builtin.os.tag != .linux) return error.SkipZigTest;
19781981
1982 if (true) {
1983 // https://github.com/ziglang/zig/issues/14907
1984 return error.SkipZigTest;
1985 }
1986
19791987 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
19801988 error.SystemOutdated => return error.SkipZigTest,
19811989 error.PermissionDenied => return error.SkipZigTest,
......@@ -2017,6 +2025,11 @@ test "accept/connect/send/recv" {
20172025test "sendmsg/recvmsg" {
20182026 if (builtin.os.tag != .linux) return error.SkipZigTest;
20192027
2028 if (true) {
2029 // https://github.com/ziglang/zig/issues/14907
2030 return error.SkipZigTest;
2031 }
2032
20202033 var ring = IO_Uring.init(2, 0) catch |err| switch (err) {
20212034 error.SystemOutdated => return error.SkipZigTest,
20222035 error.PermissionDenied => return error.SkipZigTest,
......@@ -2024,6 +2037,7 @@ test "sendmsg/recvmsg" {
20242037 };
20252038 defer ring.deinit();
20262039
2040 if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907
20272041 const address_server = try net.Address.parseIp4("127.0.0.1", 3131);
20282042
20292043 const server = try os.socket(address_server.any.family, os.SOCK.DGRAM, 0);
......@@ -2223,6 +2237,11 @@ test "timeout_remove" {
22232237test "accept/connect/recv/link_timeout" {
22242238 if (builtin.os.tag != .linux) return error.SkipZigTest;
22252239
2240 if (true) {
2241 // https://github.com/ziglang/zig/issues/14907
2242 return error.SkipZigTest;
2243 }
2244
22262245 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
22272246 error.SystemOutdated => return error.SkipZigTest,
22282247 error.PermissionDenied => return error.SkipZigTest,
......@@ -2279,10 +2298,12 @@ test "fallocate" {
22792298 };
22802299 defer ring.deinit();
22812300
2301 var tmp = std.testing.tmpDir(.{});
2302 defer tmp.cleanup();
2303
22822304 const path = "test_io_uring_fallocate";
2283 const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
2305 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
22842306 defer file.close();
2285 defer std.fs.cwd().deleteFile(path) catch {};
22862307
22872308 try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
22882309
......@@ -2323,10 +2344,11 @@ test "statx" {
23232344 };
23242345 defer ring.deinit();
23252346
2347 var tmp = std.testing.tmpDir(.{});
2348 defer tmp.cleanup();
23262349 const path = "test_io_uring_statx";
2327 const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
2350 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
23282351 defer file.close();
2329 defer std.fs.cwd().deleteFile(path) catch {};
23302352
23312353 try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
23322354
......@@ -2335,14 +2357,14 @@ test "statx" {
23352357 var buf: linux.Statx = undefined;
23362358 const sqe = try ring.statx(
23372359 0xaaaaaaaa,
2338 linux.AT.FDCWD,
2360 tmp.dir.fd,
23392361 path,
23402362 0,
23412363 linux.STATX_SIZE,
23422364 &buf,
23432365 );
23442366 try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode);
2345 try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
2367 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
23462368 try testing.expectEqual(@as(u32, 1), try ring.submit());
23472369
23482370 const cqe = try ring.copy_cqe();
......@@ -2355,8 +2377,6 @@ test "statx" {
23552377 // The filesystem containing the file referred to by fd does not support this operation;
23562378 // or the mode is not supported by the filesystem containing the file referred to by fd:
23572379 .OPNOTSUPP => return error.SkipZigTest,
2358 // The kernel is too old to support FDCWD for dir_fd
2359 .BADF => return error.SkipZigTest,
23602380 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
23612381 }
23622382 try testing.expectEqual(linux.io_uring_cqe{
......@@ -2372,6 +2392,11 @@ test "statx" {
23722392test "accept/connect/recv/cancel" {
23732393 if (builtin.os.tag != .linux) return error.SkipZigTest;
23742394
2395 if (true) {
2396 // https://github.com/ziglang/zig/issues/14907
2397 return error.SkipZigTest;
2398 }
2399
23752400 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
23762401 error.SystemOutdated => return error.SkipZigTest,
23772402 error.PermissionDenied => return error.SkipZigTest,
......@@ -2509,6 +2534,11 @@ test "register_files_update" {
25092534test "shutdown" {
25102535 if (builtin.os.tag != .linux) return error.SkipZigTest;
25112536
2537 if (true) {
2538 // https://github.com/ziglang/zig/issues/14907
2539 return error.SkipZigTest;
2540 }
2541
25122542 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
25132543 error.SystemOutdated => return error.SkipZigTest,
25142544 error.PermissionDenied => return error.SkipZigTest,
......@@ -2516,6 +2546,7 @@ test "shutdown" {
25162546 };
25172547 defer ring.deinit();
25182548
2549 if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907
25192550 const address = try net.Address.parseIp4("127.0.0.1", 3131);
25202551
25212552 // Socket bound, expect shutdown to work
......@@ -2579,28 +2610,28 @@ test "renameat" {
25792610 const old_path = "test_io_uring_renameat_old";
25802611 const new_path = "test_io_uring_renameat_new";
25812612
2613 var tmp = std.testing.tmpDir(.{});
2614 defer tmp.cleanup();
2615
25822616 // Write old file with data
25832617
2584 const old_file = try std.fs.cwd().createFile(old_path, .{ .truncate = true, .mode = 0o666 });
2585 defer {
2586 old_file.close();
2587 std.fs.cwd().deleteFile(new_path) catch {};
2588 }
2618 const old_file = try tmp.dir.createFile(old_path, .{ .truncate = true, .mode = 0o666 });
2619 defer old_file.close();
25892620 try old_file.writeAll("hello");
25902621
25912622 // Submit renameat
25922623
25932624 var sqe = try ring.renameat(
25942625 0x12121212,
2595 linux.AT.FDCWD,
2626 tmp.dir.fd,
25962627 old_path,
2597 linux.AT.FDCWD,
2628 tmp.dir.fd,
25982629 new_path,
25992630 0,
26002631 );
26012632 try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode);
2602 try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
2603 try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len));
2633 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
2634 try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len));
26042635 try testing.expectEqual(@as(u32, 1), try ring.submit());
26052636
26062637 const cqe = try ring.copy_cqe();
......@@ -2618,7 +2649,7 @@ test "renameat" {
26182649
26192650 // Validate that the old file doesn't exist anymore
26202651 {
2621 _ = std.fs.cwd().openFile(old_path, .{}) catch |err| switch (err) {
2652 _ = tmp.dir.openFile(old_path, .{}) catch |err| switch (err) {
26222653 error.FileNotFound => {},
26232654 else => std.debug.panic("unexpected error: {}", .{err}),
26242655 };
......@@ -2626,7 +2657,7 @@ test "renameat" {
26262657
26272658 // Validate that the new file exists with the proper content
26282659 {
2629 const new_file = try std.fs.cwd().openFile(new_path, .{});
2660 const new_file = try tmp.dir.openFile(new_path, .{});
26302661 defer new_file.close();
26312662
26322663 var new_file_data: [16]u8 = undefined;
......@@ -2647,22 +2678,24 @@ test "unlinkat" {
26472678
26482679 const path = "test_io_uring_unlinkat";
26492680
2681 var tmp = std.testing.tmpDir(.{});
2682 defer tmp.cleanup();
2683
26502684 // Write old file with data
26512685
2652 const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
2686 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
26532687 defer file.close();
2654 defer std.fs.cwd().deleteFile(path) catch {};
26552688
26562689 // Submit unlinkat
26572690
26582691 var sqe = try ring.unlinkat(
26592692 0x12121212,
2660 linux.AT.FDCWD,
2693 tmp.dir.fd,
26612694 path,
26622695 0,
26632696 );
26642697 try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode);
2665 try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
2698 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
26662699 try testing.expectEqual(@as(u32, 1), try ring.submit());
26672700
26682701 const cqe = try ring.copy_cqe();
......@@ -2679,7 +2712,7 @@ test "unlinkat" {
26792712 }, cqe);
26802713
26812714 // Validate that the file doesn't exist anymore
2682 _ = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
2715 _ = tmp.dir.openFile(path, .{}) catch |err| switch (err) {
26832716 error.FileNotFound => {},
26842717 else => std.debug.panic("unexpected error: {}", .{err}),
26852718 };
......@@ -2695,20 +2728,21 @@ test "mkdirat" {
26952728 };
26962729 defer ring.deinit();
26972730
2698 const path = "test_io_uring_mkdirat";
2731 var tmp = std.testing.tmpDir(.{});
2732 defer tmp.cleanup();
26992733
2700 defer std.fs.cwd().deleteDir(path) catch {};
2734 const path = "test_io_uring_mkdirat";
27012735
27022736 // Submit mkdirat
27032737
27042738 var sqe = try ring.mkdirat(
27052739 0x12121212,
2706 linux.AT.FDCWD,
2740 tmp.dir.fd,
27072741 path,
27082742 0o0755,
27092743 );
27102744 try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode);
2711 try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
2745 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
27122746 try testing.expectEqual(@as(u32, 1), try ring.submit());
27132747
27142748 const cqe = try ring.copy_cqe();
......@@ -2725,7 +2759,7 @@ test "mkdirat" {
27252759 }, cqe);
27262760
27272761 // Validate that the directory exist
2728 _ = try std.fs.cwd().openDir(path, .{});
2762 _ = try tmp.dir.openDir(path, .{});
27292763}
27302764
27312765test "symlinkat" {
......@@ -2738,26 +2772,25 @@ test "symlinkat" {
27382772 };
27392773 defer ring.deinit();
27402774
2775 var tmp = std.testing.tmpDir(.{});
2776 defer tmp.cleanup();
2777
27412778 const path = "test_io_uring_symlinkat";
27422779 const link_path = "test_io_uring_symlinkat_link";
27432780
2744 const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
2745 defer {
2746 file.close();
2747 std.fs.cwd().deleteFile(path) catch {};
2748 std.fs.cwd().deleteFile(link_path) catch {};
2749 }
2781 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
2782 defer file.close();
27502783
27512784 // Submit symlinkat
27522785
27532786 var sqe = try ring.symlinkat(
27542787 0x12121212,
27552788 path,
2756 linux.AT.FDCWD,
2789 tmp.dir.fd,
27572790 link_path,
27582791 );
27592792 try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode);
2760 try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
2793 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
27612794 try testing.expectEqual(@as(u32, 1), try ring.submit());
27622795
27632796 const cqe = try ring.copy_cqe();
......@@ -2774,7 +2807,7 @@ test "symlinkat" {
27742807 }, cqe);
27752808
27762809 // Validate that the symlink exist
2777 _ = try std.fs.cwd().openFile(link_path, .{});
2810 _ = try tmp.dir.openFile(link_path, .{});
27782811}
27792812
27802813test "linkat" {
......@@ -2787,32 +2820,31 @@ test "linkat" {
27872820 };
27882821 defer ring.deinit();
27892822
2823 var tmp = std.testing.tmpDir(.{});
2824 defer tmp.cleanup();
2825
27902826 const first_path = "test_io_uring_linkat_first";
27912827 const second_path = "test_io_uring_linkat_second";
27922828
27932829 // Write file with data
27942830
2795 const first_file = try std.fs.cwd().createFile(first_path, .{ .truncate = true, .mode = 0o666 });
2796 defer {
2797 first_file.close();
2798 std.fs.cwd().deleteFile(first_path) catch {};
2799 std.fs.cwd().deleteFile(second_path) catch {};
2800 }
2831 const first_file = try tmp.dir.createFile(first_path, .{ .truncate = true, .mode = 0o666 });
2832 defer first_file.close();
28012833 try first_file.writeAll("hello");
28022834
28032835 // Submit linkat
28042836
28052837 var sqe = try ring.linkat(
28062838 0x12121212,
2807 linux.AT.FDCWD,
2839 tmp.dir.fd,
28082840 first_path,
2809 linux.AT.FDCWD,
2841 tmp.dir.fd,
28102842 second_path,
28112843 0,
28122844 );
28132845 try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode);
2814 try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);
2815 try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len));
2846 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
2847 try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len));
28162848 try testing.expectEqual(@as(u32, 1), try ring.submit());
28172849
28182850 const cqe = try ring.copy_cqe();
......@@ -2829,7 +2861,7 @@ test "linkat" {
28292861 }, cqe);
28302862
28312863 // Validate the second file
2832 const second_file = try std.fs.cwd().openFile(second_path, .{});
2864 const second_file = try tmp.dir.openFile(second_path, .{});
28332865 defer second_file.close();
28342866
28352867 var second_file_data: [16]u8 = undefined;
......@@ -3060,6 +3092,11 @@ test "remove_buffers" {
30603092test "provide_buffers: accept/connect/send/recv" {
30613093 if (builtin.os.tag != .linux) return error.SkipZigTest;
30623094
3095 if (true) {
3096 // https://github.com/ziglang/zig/issues/14907
3097 return error.SkipZigTest;
3098 }
3099
30633100 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
30643101 error.SystemOutdated => return error.SkipZigTest,
30653102 error.PermissionDenied => return error.SkipZigTest,
......@@ -3236,6 +3273,7 @@ const SocketTestHarness = struct {
32363273fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
32373274 // Create a TCP server socket
32383275
3276 if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907
32393277 const address = try net.Address.parseIp4("127.0.0.1", 3131);
32403278 const kernel_backlog = 1;
32413279 const listener_socket = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);
lib/std/os/linux/test.zig+15-18
......@@ -8,10 +8,12 @@ const expectEqual = std.testing.expectEqual;
88const fs = std.fs;
99
1010test "fallocate" {
11 var tmp = std.testing.tmpDir(.{});
12 defer tmp.cleanup();
13
1114 const path = "test_fallocate";
12 const file = try fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });
15 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
1316 defer file.close();
14 defer fs.cwd().deleteFile(path) catch {};
1517
1618 try expect((try file.stat()).size == 0);
1719
......@@ -67,12 +69,12 @@ test "timer" {
6769}
6870
6971test "statx" {
72 var tmp = std.testing.tmpDir(.{});
73 defer tmp.cleanup();
74
7075 const tmp_file_name = "just_a_temporary_file.txt";
71 var file = try fs.cwd().createFile(tmp_file_name, .{});
72 defer {
73 file.close();
74 fs.cwd().deleteFile(tmp_file_name) catch {};
75 }
76 var file = try tmp.dir.createFile(tmp_file_name, .{});
77 defer file.close();
7678
7779 var statx_buf: linux.Statx = undefined;
7880 switch (linux.getErrno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) {
......@@ -105,21 +107,16 @@ test "user and group ids" {
105107}
106108
107109test "fadvise" {
110 var tmp = std.testing.tmpDir(.{});
111 defer tmp.cleanup();
112
108113 const tmp_file_name = "temp_posix_fadvise.txt";
109 var file = try fs.cwd().createFile(tmp_file_name, .{});
110 defer {
111 file.close();
112 fs.cwd().deleteFile(tmp_file_name) catch {};
113 }
114 var file = try tmp.dir.createFile(tmp_file_name, .{});
115 defer file.close();
114116
115117 var buf: [2048]u8 = undefined;
116118 try file.writeAll(&buf);
117119
118 const ret = linux.fadvise(
119 file.handle,
120 0,
121 0,
122 linux.POSIX_FADV.SEQUENTIAL,
123 );
120 const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL);
124121 try expectEqual(@as(usize, 0), ret);
125122}
lib/std/os/windows.zig+47-35
......@@ -105,41 +105,53 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
105105 // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
106106 const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;
107107
108 const rc = ntdll.NtCreateFile(
109 &result,
110 options.access_mask,
111 &attr,
112 &io,
113 null,
114 FILE_ATTRIBUTE_NORMAL,
115 options.share_access,
116 options.creation,
117 flags,
118 null,
119 0,
120 );
121 switch (rc) {
122 .SUCCESS => {
123 if (std.io.is_async and options.io_mode == .evented) {
124 _ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined;
125 }
126 return result;
127 },
128 .OBJECT_NAME_INVALID => unreachable,
129 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
130 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
131 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
132 .INVALID_PARAMETER => unreachable,
133 .SHARING_VIOLATION => return error.AccessDenied,
134 .ACCESS_DENIED => return error.AccessDenied,
135 .PIPE_BUSY => return error.PipeBusy,
136 .OBJECT_PATH_SYNTAX_BAD => unreachable,
137 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
138 .FILE_IS_A_DIRECTORY => return error.IsDir,
139 .NOT_A_DIRECTORY => return error.NotDir,
140 .USER_MAPPED_FILE => return error.AccessDenied,
141 .INVALID_HANDLE => unreachable,
142 else => return unexpectedStatus(rc),
108 while (true) {
109 const rc = ntdll.NtCreateFile(
110 &result,
111 options.access_mask,
112 &attr,
113 &io,
114 null,
115 FILE_ATTRIBUTE_NORMAL,
116 options.share_access,
117 options.creation,
118 flags,
119 null,
120 0,
121 );
122 switch (rc) {
123 .SUCCESS => {
124 if (std.io.is_async and options.io_mode == .evented) {
125 _ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined;
126 }
127 return result;
128 },
129 .OBJECT_NAME_INVALID => unreachable,
130 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
131 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
132 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
133 .INVALID_PARAMETER => unreachable,
134 .SHARING_VIOLATION => return error.AccessDenied,
135 .ACCESS_DENIED => return error.AccessDenied,
136 .PIPE_BUSY => return error.PipeBusy,
137 .OBJECT_PATH_SYNTAX_BAD => unreachable,
138 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
139 .FILE_IS_A_DIRECTORY => return error.IsDir,
140 .NOT_A_DIRECTORY => return error.NotDir,
141 .USER_MAPPED_FILE => return error.AccessDenied,
142 .INVALID_HANDLE => unreachable,
143 .DELETE_PENDING => {
144 // This error means that there *was* a file in this location on
145 // the file system, but it was deleted. However, the OS is not
146 // finished with the deletion operation, and so this CreateFile
147 // call has failed. There is not really a sane way to handle
148 // this other than retrying the creation after the OS finishes
149 // the deletion.
150 std.time.sleep(std.time.ns_per_ms);
151 continue;
152 },
153 else => return unexpectedStatus(rc),
154 }
143155 }
144156}
145157
lib/std/os/windows/kernel32.zig+3
......@@ -67,6 +67,7 @@ const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
6767const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
6868const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;
6969const MODULEENTRY32 = windows.MODULEENTRY32;
70const ULONGLONG = windows.ULONGLONG;
7071
7172pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*anyopaque;
7273pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;
......@@ -457,3 +458,5 @@ pub extern "kernel32" fn RegOpenKeyExW(
457458 samDesired: REGSAM,
458459 phkResult: *HKEY,
459460) callconv(WINAPI) LSTATUS;
461
462pub extern "kernel32" fn GetPhysicallyInstalledSystemMemory(TotalMemoryInKilobytes: *ULONGLONG) BOOL;
lib/std/process.zig+48-18
......@@ -828,24 +828,6 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
828828 return ArgIterator.initWithAllocator(allocator);
829829}
830830
831test "args iterator" {
832 var ga = std.testing.allocator;
833 var it = try argsWithAllocator(ga);
834 defer it.deinit(); // no-op unless WASI or Windows
835
836 const prog_name = it.next() orelse unreachable;
837 const expected_suffix = switch (builtin.os.tag) {
838 .wasi => "test.wasm",
839 .windows => "test.exe",
840 else => "test",
841 };
842 const given_suffix = std.fs.path.basename(prog_name);
843
844 try testing.expect(mem.eql(u8, expected_suffix, given_suffix));
845 try testing.expect(it.next() == null);
846 try testing.expect(!it.skip());
847}
848
849831/// Caller must call argsFree on result.
850832pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
851833 // TODO refactor to only make 1 allocation.
......@@ -1169,3 +1151,51 @@ pub fn execve(
11691151
11701152 return os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp);
11711153}
1154
1155pub const TotalSystemMemoryError = error{
1156 UnknownTotalSystemMemory,
1157};
1158
1159/// Returns the total system memory, in bytes.
1160pub fn totalSystemMemory() TotalSystemMemoryError!usize {
1161 switch (builtin.os.tag) {
1162 .linux => {
1163 return totalSystemMemoryLinux() catch return error.UnknownTotalSystemMemory;
1164 },
1165 .windows => {
1166 var kilobytes: std.os.windows.ULONGLONG = undefined;
1167 assert(std.os.windows.kernel32.GetPhysicallyInstalledSystemMemory(&kilobytes) == std.os.windows.TRUE);
1168 return kilobytes * 1024;
1169 },
1170 else => return error.UnknownTotalSystemMemory,
1171 }
1172}
1173
1174fn totalSystemMemoryLinux() !usize {
1175 var file = try std.fs.openFileAbsoluteZ("/proc/meminfo", .{});
1176 defer file.close();
1177 var buf: [50]u8 = undefined;
1178 const amt = try file.read(&buf);
1179 if (amt != 50) return error.Unexpected;
1180 var it = std.mem.tokenize(u8, buf[0..amt], " \n");
1181 const label = it.next().?;
1182 if (!std.mem.eql(u8, label, "MemTotal:")) return error.Unexpected;
1183 const int_text = it.next() orelse return error.Unexpected;
1184 const units = it.next() orelse return error.Unexpected;
1185 if (!std.mem.eql(u8, units, "kB")) return error.Unexpected;
1186 const kilobytes = try std.fmt.parseInt(usize, int_text, 10);
1187 return kilobytes * 1024;
1188}
1189
1190/// Indicate that we are now terminating with a successful exit code.
1191/// In debug builds, this is a no-op, so that the calling code's
1192/// cleanup mechanisms are tested and so that external tools that
1193/// check for resource leaks can be accurate. In release builds, this
1194/// calls exit(0), and does not return.
1195pub fn cleanExit() void {
1196 if (builtin.mode == .Debug) {
1197 return;
1198 } else {
1199 exit(0);
1200 }
1201}
lib/std/zig.zig+3
......@@ -3,6 +3,9 @@ const tokenizer = @import("zig/tokenizer.zig");
33const fmt = @import("zig/fmt.zig");
44const assert = std.debug.assert;
55
6pub const ErrorBundle = @import("zig/ErrorBundle.zig");
7pub const Server = @import("zig/Server.zig");
8pub const Client = @import("zig/Client.zig");
69pub const Token = tokenizer.Token;
710pub const Tokenizer = tokenizer.Tokenizer;
811pub const fmtId = fmt.fmtId;
lib/std/zig/Client.zig created+39
......@@ -0,0 +1,39 @@
1pub const Message = struct {
2 pub const Header = extern struct {
3 tag: Tag,
4 /// Size of the body only; does not include this Header.
5 bytes_len: u32,
6 };
7
8 pub const Tag = enum(u32) {
9 /// Tells the compiler to shut down cleanly.
10 /// No body.
11 exit,
12 /// Tells the compiler to detect changes in source files and update the
13 /// affected output compilation artifacts.
14 /// If one of the compilation artifacts is an executable that is
15 /// running as a child process, the compiler will wait for it to exit
16 /// before performing the update.
17 /// No body.
18 update,
19 /// Tells the compiler to execute the executable as a child process.
20 /// No body.
21 run,
22 /// Tells the compiler to detect changes in source files and update the
23 /// affected output compilation artifacts.
24 /// If one of the compilation artifacts is an executable that is
25 /// running as a child process, the compiler will perform a hot code
26 /// swap.
27 /// No body.
28 hot_update,
29 /// Ask the test runner for metadata about all the unit tests that can
30 /// be run. Server will respond with a `test_metadata` message.
31 /// No body.
32 query_test_metadata,
33 /// Ask the test runner to run a particular test.
34 /// The message body is a u32 test index.
35 run_test,
36
37 _,
38 };
39};
lib/std/zig/ErrorBundle.zig created+515
......@@ -0,0 +1,515 @@
1//! To support incremental compilation, errors are stored in various places
2//! so that they can be created and destroyed appropriately. This structure
3//! is used to collect all the errors from the various places into one
4//! convenient place for API users to consume.
5//!
6//! There is one special encoding for this data structure. If both arrays are
7//! empty, it means there are no errors. This special encoding exists so that
8//! heap allocation is not needed in the common case of no errors.
9
10string_bytes: []const u8,
11/// The first thing in this array is an `ErrorMessageList`.
12extra: []const u32,
13
14/// Special encoding when there are no errors.
15pub const empty: ErrorBundle = .{
16 .string_bytes = &.{},
17 .extra = &.{},
18};
19
20// An index into `extra` pointing at an `ErrorMessage`.
21pub const MessageIndex = enum(u32) {
22 _,
23};
24
25// An index into `extra` pointing at an `SourceLocation`.
26pub const SourceLocationIndex = enum(u32) {
27 none = 0,
28 _,
29};
30
31/// There will be a MessageIndex for each len at start.
32pub const ErrorMessageList = struct {
33 len: u32,
34 start: u32,
35 /// null-terminated string index. 0 means no compile log text.
36 compile_log_text: u32,
37};
38
39/// Trailing:
40/// * ReferenceTrace for each reference_trace_len
41pub const SourceLocation = struct {
42 /// null terminated string index
43 src_path: u32,
44 line: u32,
45 column: u32,
46 /// byte offset of starting token
47 span_start: u32,
48 /// byte offset of main error location
49 span_main: u32,
50 /// byte offset of end of last token
51 span_end: u32,
52 /// null terminated string index, possibly null.
53 /// Does not include the trailing newline.
54 source_line: u32 = 0,
55 reference_trace_len: u32 = 0,
56};
57
58/// Trailing:
59/// * MessageIndex for each notes_len.
60pub const ErrorMessage = struct {
61 /// null terminated string index
62 msg: u32,
63 /// Usually one, but incremented for redundant messages.
64 count: u32 = 1,
65 src_loc: SourceLocationIndex = .none,
66 notes_len: u32 = 0,
67};
68
69pub const ReferenceTrace = struct {
70 /// null terminated string index
71 /// Except for the sentinel ReferenceTrace element, in which case:
72 /// * 0 means remaining references hidden
73 /// * >0 means N references hidden
74 decl_name: u32,
75 /// Index into extra of a SourceLocation
76 /// If this is 0, this is the sentinel ReferenceTrace element.
77 src_loc: SourceLocationIndex,
78};
79
80pub fn deinit(eb: *ErrorBundle, gpa: Allocator) void {
81 gpa.free(eb.string_bytes);
82 gpa.free(eb.extra);
83 eb.* = undefined;
84}
85
86pub fn errorMessageCount(eb: ErrorBundle) u32 {
87 if (eb.extra.len == 0) return 0;
88 return eb.getErrorMessageList().len;
89}
90
91pub fn getErrorMessageList(eb: ErrorBundle) ErrorMessageList {
92 return eb.extraData(ErrorMessageList, 0).data;
93}
94
95pub fn getMessages(eb: ErrorBundle) []const MessageIndex {
96 const list = eb.getErrorMessageList();
97 return @ptrCast([]const MessageIndex, eb.extra[list.start..][0..list.len]);
98}
99
100pub fn getErrorMessage(eb: ErrorBundle, index: MessageIndex) ErrorMessage {
101 return eb.extraData(ErrorMessage, @enumToInt(index)).data;
102}
103
104pub fn getSourceLocation(eb: ErrorBundle, index: SourceLocationIndex) SourceLocation {
105 assert(index != .none);
106 return eb.extraData(SourceLocation, @enumToInt(index)).data;
107}
108
109pub fn getNotes(eb: ErrorBundle, index: MessageIndex) []const MessageIndex {
110 const notes_len = eb.getErrorMessage(index).notes_len;
111 const start = @enumToInt(index) + @typeInfo(ErrorMessage).Struct.fields.len;
112 return @ptrCast([]const MessageIndex, eb.extra[start..][0..notes_len]);
113}
114
115pub fn getCompileLogOutput(eb: ErrorBundle) [:0]const u8 {
116 return nullTerminatedString(eb, getErrorMessageList(eb).compile_log_text);
117}
118
119/// Returns the requested data, as well as the new index which is at the start of the
120/// trailers for the object.
121fn extraData(eb: ErrorBundle, comptime T: type, index: usize) struct { data: T, end: usize } {
122 const fields = @typeInfo(T).Struct.fields;
123 var i: usize = index;
124 var result: T = undefined;
125 inline for (fields) |field| {
126 @field(result, field.name) = switch (field.type) {
127 u32 => eb.extra[i],
128 MessageIndex => @intToEnum(MessageIndex, eb.extra[i]),
129 SourceLocationIndex => @intToEnum(SourceLocationIndex, eb.extra[i]),
130 else => @compileError("bad field type"),
131 };
132 i += 1;
133 }
134 return .{
135 .data = result,
136 .end = i,
137 };
138}
139
140/// Given an index into `string_bytes` returns the null-terminated string found there.
141pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 {
142 const string_bytes = eb.string_bytes;
143 var end: usize = index;
144 while (string_bytes[end] != 0) {
145 end += 1;
146 }
147 return string_bytes[index..end :0];
148}
149
150pub const RenderOptions = struct {
151 ttyconf: std.debug.TTY.Config,
152 include_reference_trace: bool = true,
153 include_source_line: bool = true,
154 include_log_text: bool = true,
155};
156
157pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void {
158 std.debug.getStderrMutex().lock();
159 defer std.debug.getStderrMutex().unlock();
160 const stderr = std.io.getStdErr();
161 return renderToWriter(eb, options, stderr.writer()) catch return;
162}
163
164pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) anyerror!void {
165 for (eb.getMessages()) |err_msg| {
166 try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .Red, 0);
167 }
168
169 if (options.include_log_text) {
170 const log_text = eb.getCompileLogOutput();
171 if (log_text.len != 0) {
172 try writer.writeAll("\nCompile Log Output:\n");
173 try writer.writeAll(log_text);
174 }
175 }
176}
177
178fn renderErrorMessageToWriter(
179 eb: ErrorBundle,
180 options: RenderOptions,
181 err_msg_index: MessageIndex,
182 stderr: anytype,
183 kind: []const u8,
184 color: std.debug.TTY.Color,
185 indent: usize,
186) anyerror!void {
187 const ttyconf = options.ttyconf;
188 var counting_writer = std.io.countingWriter(stderr);
189 const counting_stderr = counting_writer.writer();
190 const err_msg = eb.getErrorMessage(err_msg_index);
191 if (err_msg.src_loc != .none) {
192 const src = eb.extraData(SourceLocation, @enumToInt(err_msg.src_loc));
193 try counting_stderr.writeByteNTimes(' ', indent);
194 try ttyconf.setColor(stderr, .Bold);
195 try counting_stderr.print("{s}:{d}:{d}: ", .{
196 eb.nullTerminatedString(src.data.src_path),
197 src.data.line + 1,
198 src.data.column + 1,
199 });
200 try ttyconf.setColor(stderr, color);
201 try counting_stderr.writeAll(kind);
202 try counting_stderr.writeAll(": ");
203 // This is the length of the part before the error message:
204 // e.g. "file.zig:4:5: error: "
205 const prefix_len = @intCast(usize, counting_stderr.context.bytes_written);
206 try ttyconf.setColor(stderr, .Reset);
207 try ttyconf.setColor(stderr, .Bold);
208 if (err_msg.count == 1) {
209 try writeMsg(eb, err_msg, stderr, prefix_len);
210 try stderr.writeByte('\n');
211 } else {
212 try writeMsg(eb, err_msg, stderr, prefix_len);
213 try ttyconf.setColor(stderr, .Dim);
214 try stderr.print(" ({d} times)\n", .{err_msg.count});
215 }
216 try ttyconf.setColor(stderr, .Reset);
217 if (src.data.source_line != 0 and options.include_source_line) {
218 const line = eb.nullTerminatedString(src.data.source_line);
219 for (line) |b| switch (b) {
220 '\t' => try stderr.writeByte(' '),
221 else => try stderr.writeByte(b),
222 };
223 try stderr.writeByte('\n');
224 // TODO basic unicode code point monospace width
225 const before_caret = src.data.span_main - src.data.span_start;
226 // -1 since span.main includes the caret
227 const after_caret = src.data.span_end - src.data.span_main -| 1;
228 try stderr.writeByteNTimes(' ', src.data.column - before_caret);
229 try ttyconf.setColor(stderr, .Green);
230 try stderr.writeByteNTimes('~', before_caret);
231 try stderr.writeByte('^');
232 try stderr.writeByteNTimes('~', after_caret);
233 try stderr.writeByte('\n');
234 try ttyconf.setColor(stderr, .Reset);
235 }
236 for (eb.getNotes(err_msg_index)) |note| {
237 try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent);
238 }
239 if (src.data.reference_trace_len > 0 and options.include_reference_trace) {
240 try ttyconf.setColor(stderr, .Reset);
241 try ttyconf.setColor(stderr, .Dim);
242 try stderr.print("referenced by:\n", .{});
243 var ref_index = src.end;
244 for (0..src.data.reference_trace_len) |_| {
245 const ref_trace = eb.extraData(ReferenceTrace, ref_index);
246 ref_index = ref_trace.end;
247 if (ref_trace.data.src_loc != .none) {
248 const ref_src = eb.getSourceLocation(ref_trace.data.src_loc);
249 try stderr.print(" {s}: {s}:{d}:{d}\n", .{
250 eb.nullTerminatedString(ref_trace.data.decl_name),
251 eb.nullTerminatedString(ref_src.src_path),
252 ref_src.line + 1,
253 ref_src.column + 1,
254 });
255 } else if (ref_trace.data.decl_name != 0) {
256 const count = ref_trace.data.decl_name;
257 try stderr.print(
258 " {d} reference(s) hidden; use '-freference-trace={d}' to see all references\n",
259 .{ count, count + src.data.reference_trace_len - 1 },
260 );
261 } else {
262 try stderr.print(
263 " remaining reference traces hidden; use '-freference-trace' to see all reference traces\n",
264 .{},
265 );
266 }
267 }
268 try stderr.writeByte('\n');
269 try ttyconf.setColor(stderr, .Reset);
270 }
271 } else {
272 try ttyconf.setColor(stderr, color);
273 try stderr.writeByteNTimes(' ', indent);
274 try stderr.writeAll(kind);
275 try stderr.writeAll(": ");
276 try ttyconf.setColor(stderr, .Reset);
277 const msg = eb.nullTerminatedString(err_msg.msg);
278 if (err_msg.count == 1) {
279 try stderr.print("{s}\n", .{msg});
280 } else {
281 try stderr.print("{s}", .{msg});
282 try ttyconf.setColor(stderr, .Dim);
283 try stderr.print(" ({d} times)\n", .{err_msg.count});
284 }
285 try ttyconf.setColor(stderr, .Reset);
286 for (eb.getNotes(err_msg_index)) |note| {
287 try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent + 4);
288 }
289 }
290}
291
292/// Splits the error message up into lines to properly indent them
293/// to allow for long, good-looking error messages.
294///
295/// This is used to split the message in `@compileError("hello\nworld")` for example.
296fn writeMsg(eb: ErrorBundle, err_msg: ErrorMessage, stderr: anytype, indent: usize) !void {
297 var lines = std.mem.split(u8, eb.nullTerminatedString(err_msg.msg), "\n");
298 while (lines.next()) |line| {
299 try stderr.writeAll(line);
300 if (lines.index == null) break;
301 try stderr.writeByte('\n');
302 try stderr.writeByteNTimes(' ', indent);
303 }
304}
305
306const std = @import("std");
307const ErrorBundle = @This();
308const Allocator = std.mem.Allocator;
309const assert = std.debug.assert;
310
311pub const Wip = struct {
312 gpa: Allocator,
313 string_bytes: std.ArrayListUnmanaged(u8),
314 /// The first thing in this array is a ErrorMessageList.
315 extra: std.ArrayListUnmanaged(u32),
316 root_list: std.ArrayListUnmanaged(MessageIndex),
317
318 pub fn init(wip: *Wip, gpa: Allocator) !void {
319 wip.* = .{
320 .gpa = gpa,
321 .string_bytes = .{},
322 .extra = .{},
323 .root_list = .{},
324 };
325
326 // So that 0 can be used to indicate a null string.
327 try wip.string_bytes.append(gpa, 0);
328
329 assert(0 == try addExtra(wip, ErrorMessageList{
330 .len = 0,
331 .start = 0,
332 .compile_log_text = 0,
333 }));
334 }
335
336 pub fn deinit(wip: *Wip) void {
337 const gpa = wip.gpa;
338 wip.root_list.deinit(gpa);
339 wip.string_bytes.deinit(gpa);
340 wip.extra.deinit(gpa);
341 wip.* = undefined;
342 }
343
344 pub fn toOwnedBundle(wip: *Wip, compile_log_text: []const u8) !ErrorBundle {
345 const gpa = wip.gpa;
346 if (wip.root_list.items.len == 0) {
347 assert(compile_log_text.len == 0);
348 // Special encoding when there are no errors.
349 wip.deinit();
350 wip.* = .{
351 .gpa = gpa,
352 .string_bytes = .{},
353 .extra = .{},
354 .root_list = .{},
355 };
356 return empty;
357 }
358
359 const compile_log_str_index = if (compile_log_text.len == 0) 0 else str: {
360 const str = @intCast(u32, wip.string_bytes.items.len);
361 try wip.string_bytes.ensureUnusedCapacity(gpa, compile_log_text.len + 1);
362 wip.string_bytes.appendSliceAssumeCapacity(compile_log_text);
363 wip.string_bytes.appendAssumeCapacity(0);
364 break :str str;
365 };
366
367 wip.setExtra(0, ErrorMessageList{
368 .len = @intCast(u32, wip.root_list.items.len),
369 .start = @intCast(u32, wip.extra.items.len),
370 .compile_log_text = compile_log_str_index,
371 });
372 try wip.extra.appendSlice(gpa, @ptrCast([]const u32, wip.root_list.items));
373 wip.root_list.clearAndFree(gpa);
374 return .{
375 .string_bytes = try wip.string_bytes.toOwnedSlice(gpa),
376 .extra = try wip.extra.toOwnedSlice(gpa),
377 };
378 }
379
380 pub fn tmpBundle(wip: Wip) ErrorBundle {
381 return .{
382 .string_bytes = wip.string_bytes.items,
383 .extra = wip.extra.items,
384 };
385 }
386
387 pub fn addString(wip: *Wip, s: []const u8) !u32 {
388 const gpa = wip.gpa;
389 const index = @intCast(u32, wip.string_bytes.items.len);
390 try wip.string_bytes.ensureUnusedCapacity(gpa, s.len + 1);
391 wip.string_bytes.appendSliceAssumeCapacity(s);
392 wip.string_bytes.appendAssumeCapacity(0);
393 return index;
394 }
395
396 pub fn printString(wip: *Wip, comptime fmt: []const u8, args: anytype) !u32 {
397 const gpa = wip.gpa;
398 const index = @intCast(u32, wip.string_bytes.items.len);
399 try wip.string_bytes.writer(gpa).print(fmt, args);
400 try wip.string_bytes.append(gpa, 0);
401 return index;
402 }
403
404 pub fn addRootErrorMessage(wip: *Wip, em: ErrorMessage) !void {
405 try wip.root_list.ensureUnusedCapacity(wip.gpa, 1);
406 wip.root_list.appendAssumeCapacity(try addErrorMessage(wip, em));
407 }
408
409 pub fn addErrorMessage(wip: *Wip, em: ErrorMessage) !MessageIndex {
410 return @intToEnum(MessageIndex, try addExtra(wip, em));
411 }
412
413 pub fn addErrorMessageAssumeCapacity(wip: *Wip, em: ErrorMessage) MessageIndex {
414 return @intToEnum(MessageIndex, addExtraAssumeCapacity(wip, em));
415 }
416
417 pub fn addSourceLocation(wip: *Wip, sl: SourceLocation) !SourceLocationIndex {
418 return @intToEnum(SourceLocationIndex, try addExtra(wip, sl));
419 }
420
421 pub fn addReferenceTrace(wip: *Wip, rt: ReferenceTrace) !void {
422 _ = try addExtra(wip, rt);
423 }
424
425 pub fn addBundle(wip: *Wip, other: ErrorBundle) !void {
426 const gpa = wip.gpa;
427
428 try wip.string_bytes.ensureUnusedCapacity(gpa, other.string_bytes.len);
429 try wip.extra.ensureUnusedCapacity(gpa, other.extra.len);
430
431 const other_list = other.getMessages();
432
433 // The ensureUnusedCapacity call above guarantees this.
434 const notes_start = wip.reserveNotes(@intCast(u32, other_list.len)) catch unreachable;
435 for (notes_start.., other_list) |note, message| {
436 wip.extra.items[note] = @enumToInt(wip.addOtherMessage(other, message) catch unreachable);
437 }
438 }
439
440 pub fn reserveNotes(wip: *Wip, notes_len: u32) !u32 {
441 try wip.extra.ensureUnusedCapacity(wip.gpa, notes_len +
442 notes_len * @typeInfo(ErrorBundle.ErrorMessage).Struct.fields.len);
443 wip.extra.items.len += notes_len;
444 return @intCast(u32, wip.extra.items.len - notes_len);
445 }
446
447 fn addOtherMessage(wip: *Wip, other: ErrorBundle, msg_index: MessageIndex) !MessageIndex {
448 const other_msg = other.getErrorMessage(msg_index);
449 const src_loc = try wip.addOtherSourceLocation(other, other_msg.src_loc);
450 const msg = try wip.addErrorMessage(.{
451 .msg = try wip.addString(other.nullTerminatedString(other_msg.msg)),
452 .count = other_msg.count,
453 .src_loc = src_loc,
454 .notes_len = other_msg.notes_len,
455 });
456 const notes_start = try wip.reserveNotes(other_msg.notes_len);
457 for (notes_start.., other.getNotes(msg_index)) |note, other_note| {
458 wip.extra.items[note] = @enumToInt(try wip.addOtherMessage(other, other_note));
459 }
460 return msg;
461 }
462
463 fn addOtherSourceLocation(
464 wip: *Wip,
465 other: ErrorBundle,
466 index: SourceLocationIndex,
467 ) !SourceLocationIndex {
468 if (index == .none) return .none;
469 const other_sl = other.getSourceLocation(index);
470
471 const src_loc = try wip.addSourceLocation(.{
472 .src_path = try wip.addString(other.nullTerminatedString(other_sl.src_path)),
473 .line = other_sl.line,
474 .column = other_sl.column,
475 .span_start = other_sl.span_start,
476 .span_main = other_sl.span_main,
477 .span_end = other_sl.span_end,
478 .source_line = try wip.addString(other.nullTerminatedString(other_sl.source_line)),
479 .reference_trace_len = other_sl.reference_trace_len,
480 });
481
482 // TODO: also add the reference trace
483
484 return src_loc;
485 }
486
487 fn addExtra(wip: *Wip, extra: anytype) Allocator.Error!u32 {
488 const gpa = wip.gpa;
489 const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
490 try wip.extra.ensureUnusedCapacity(gpa, fields.len);
491 return addExtraAssumeCapacity(wip, extra);
492 }
493
494 fn addExtraAssumeCapacity(wip: *Wip, extra: anytype) u32 {
495 const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
496 const result = @intCast(u32, wip.extra.items.len);
497 wip.extra.items.len += fields.len;
498 setExtra(wip, result, extra);
499 return result;
500 }
501
502 fn setExtra(wip: *Wip, index: usize, extra: anytype) void {
503 const fields = @typeInfo(@TypeOf(extra)).Struct.fields;
504 var i = index;
505 inline for (fields) |field| {
506 wip.extra.items[i] = switch (field.type) {
507 u32 => @field(extra, field.name),
508 MessageIndex => @enumToInt(@field(extra, field.name)),
509 SourceLocationIndex => @enumToInt(@field(extra, field.name)),
510 else => @compileError("bad field type"),
511 };
512 i += 1;
513 }
514 }
515};
lib/std/zig/Server.zig created+305
......@@ -0,0 +1,305 @@
1in: std.fs.File,
2out: std.fs.File,
3receive_fifo: std.fifo.LinearFifo(u8, .Dynamic),
4
5pub const Message = struct {
6 pub const Header = extern struct {
7 tag: Tag,
8 /// Size of the body only; does not include this Header.
9 bytes_len: u32,
10 };
11
12 pub const Tag = enum(u32) {
13 /// Body is a UTF-8 string.
14 zig_version,
15 /// Body is an ErrorBundle.
16 error_bundle,
17 /// Body is a UTF-8 string.
18 progress,
19 /// Body is a EmitBinPath.
20 emit_bin_path,
21 /// Body is a TestMetadata
22 test_metadata,
23 /// Body is a TestResults
24 test_results,
25
26 _,
27 };
28
29 /// Trailing:
30 /// * extra: [extra_len]u32,
31 /// * string_bytes: [string_bytes_len]u8,
32 /// See `std.zig.ErrorBundle`.
33 pub const ErrorBundle = extern struct {
34 extra_len: u32,
35 string_bytes_len: u32,
36 };
37
38 /// Trailing:
39 /// * name: [tests_len]u32
40 /// - null-terminated string_bytes index
41 /// * async_frame_len: [tests_len]u32,
42 /// - 0 means not async
43 /// * expected_panic_msg: [tests_len]u32,
44 /// - null-terminated string_bytes index
45 /// - 0 means does not expect pani
46 /// * string_bytes: [string_bytes_len]u8,
47 pub const TestMetadata = extern struct {
48 string_bytes_len: u32,
49 tests_len: u32,
50 };
51
52 pub const TestResults = extern struct {
53 index: u32,
54 flags: Flags,
55
56 pub const Flags = packed struct(u8) {
57 fail: bool,
58 skip: bool,
59 leak: bool,
60
61 reserved: u5 = 0,
62 };
63 };
64
65 /// Trailing:
66 /// * the file system path the emitted binary can be found
67 pub const EmitBinPath = extern struct {
68 flags: Flags,
69
70 pub const Flags = packed struct(u8) {
71 cache_hit: bool,
72 reserved: u7 = 0,
73 };
74 };
75};
76
77pub const Options = struct {
78 gpa: Allocator,
79 in: std.fs.File,
80 out: std.fs.File,
81 zig_version: []const u8,
82};
83
84pub fn init(options: Options) !Server {
85 var s: Server = .{
86 .in = options.in,
87 .out = options.out,
88 .receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa),
89 };
90 try s.serveStringMessage(.zig_version, options.zig_version);
91 return s;
92}
93
94pub fn deinit(s: *Server) void {
95 s.receive_fifo.deinit();
96 s.* = undefined;
97}
98
99pub fn receiveMessage(s: *Server) !InMessage.Header {
100 const Header = InMessage.Header;
101 const fifo = &s.receive_fifo;
102
103 while (true) {
104 const buf = fifo.readableSlice(0);
105 assert(fifo.readableLength() == buf.len);
106 if (buf.len >= @sizeOf(Header)) {
107 const header = @ptrCast(*align(1) const Header, buf[0..@sizeOf(Header)]);
108 // workaround for https://github.com/ziglang/zig/issues/14904
109 const bytes_len = bswap_and_workaround_u32(&header.bytes_len);
110 // workaround for https://github.com/ziglang/zig/issues/14904
111 const tag = bswap_and_workaround_tag(&header.tag);
112
113 if (buf.len - @sizeOf(Header) >= bytes_len) {
114 fifo.discard(@sizeOf(Header));
115 return .{
116 .tag = tag,
117 .bytes_len = bytes_len,
118 };
119 } else {
120 const needed = bytes_len - (buf.len - @sizeOf(Header));
121 const write_buffer = try fifo.writableWithSize(needed);
122 const amt = try s.in.read(write_buffer);
123 fifo.update(amt);
124 continue;
125 }
126 }
127
128 const write_buffer = try fifo.writableWithSize(256);
129 const amt = try s.in.read(write_buffer);
130 fifo.update(amt);
131 }
132}
133
134pub fn receiveBody_u32(s: *Server) !u32 {
135 const fifo = &s.receive_fifo;
136 const buf = fifo.readableSlice(0);
137 const result = @ptrCast(*align(1) const u32, buf[0..4]).*;
138 fifo.discard(4);
139 return bswap(result);
140}
141
142pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void {
143 return s.serveMessage(.{
144 .tag = tag,
145 .bytes_len = @intCast(u32, msg.len),
146 }, &.{msg});
147}
148
149pub fn serveMessage(
150 s: *const Server,
151 header: OutMessage.Header,
152 bufs: []const []const u8,
153) !void {
154 var iovecs: [10]std.os.iovec_const = undefined;
155 const header_le = bswap(header);
156 iovecs[0] = .{
157 .iov_base = @ptrCast([*]const u8, &header_le),
158 .iov_len = @sizeOf(OutMessage.Header),
159 };
160 for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| {
161 iovec.* = .{
162 .iov_base = buf.ptr,
163 .iov_len = buf.len,
164 };
165 }
166 try s.out.writevAll(iovecs[0 .. bufs.len + 1]);
167}
168
169pub fn serveEmitBinPath(
170 s: *Server,
171 fs_path: []const u8,
172 header: OutMessage.EmitBinPath,
173) !void {
174 try s.serveMessage(.{
175 .tag = .emit_bin_path,
176 .bytes_len = @intCast(u32, fs_path.len + @sizeOf(OutMessage.EmitBinPath)),
177 }, &.{
178 std.mem.asBytes(&header),
179 fs_path,
180 });
181}
182
183pub fn serveTestResults(
184 s: *Server,
185 msg: OutMessage.TestResults,
186) !void {
187 const msg_le = bswap(msg);
188 try s.serveMessage(.{
189 .tag = .test_results,
190 .bytes_len = @intCast(u32, @sizeOf(OutMessage.TestResults)),
191 }, &.{
192 std.mem.asBytes(&msg_le),
193 });
194}
195
196pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
197 const eb_hdr: OutMessage.ErrorBundle = .{
198 .extra_len = @intCast(u32, error_bundle.extra.len),
199 .string_bytes_len = @intCast(u32, error_bundle.string_bytes.len),
200 };
201 const bytes_len = @sizeOf(OutMessage.ErrorBundle) +
202 4 * error_bundle.extra.len + error_bundle.string_bytes.len;
203 try s.serveMessage(.{
204 .tag = .error_bundle,
205 .bytes_len = @intCast(u32, bytes_len),
206 }, &.{
207 std.mem.asBytes(&eb_hdr),
208 // TODO: implement @ptrCast between slices changing the length
209 std.mem.sliceAsBytes(error_bundle.extra),
210 error_bundle.string_bytes,
211 });
212}
213
214pub const TestMetadata = struct {
215 names: []u32,
216 async_frame_sizes: []u32,
217 expected_panic_msgs: []u32,
218 string_bytes: []const u8,
219};
220
221pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {
222 const header: OutMessage.TestMetadata = .{
223 .tests_len = bswap(@intCast(u32, test_metadata.names.len)),
224 .string_bytes_len = bswap(@intCast(u32, test_metadata.string_bytes.len)),
225 };
226 const bytes_len = @sizeOf(OutMessage.TestMetadata) +
227 3 * 4 * test_metadata.names.len + test_metadata.string_bytes.len;
228
229 if (need_bswap) {
230 bswap_u32_array(test_metadata.names);
231 bswap_u32_array(test_metadata.async_frame_sizes);
232 bswap_u32_array(test_metadata.expected_panic_msgs);
233 }
234 defer if (need_bswap) {
235 bswap_u32_array(test_metadata.names);
236 bswap_u32_array(test_metadata.async_frame_sizes);
237 bswap_u32_array(test_metadata.expected_panic_msgs);
238 };
239
240 return s.serveMessage(.{
241 .tag = .test_metadata,
242 .bytes_len = @intCast(u32, bytes_len),
243 }, &.{
244 std.mem.asBytes(&header),
245 // TODO: implement @ptrCast between slices changing the length
246 std.mem.sliceAsBytes(test_metadata.names),
247 std.mem.sliceAsBytes(test_metadata.async_frame_sizes),
248 std.mem.sliceAsBytes(test_metadata.expected_panic_msgs),
249 test_metadata.string_bytes,
250 });
251}
252
253fn bswap(x: anytype) @TypeOf(x) {
254 if (!need_bswap) return x;
255
256 const T = @TypeOf(x);
257 switch (@typeInfo(T)) {
258 .Enum => return @intToEnum(T, @byteSwap(@enumToInt(x))),
259 .Int => return @byteSwap(x),
260 .Struct => |info| switch (info.layout) {
261 .Extern => {
262 var result: T = undefined;
263 inline for (info.fields) |field| {
264 @field(result, field.name) = bswap(@field(x, field.name));
265 }
266 return result;
267 },
268 .Packed => {
269 const I = info.backing_integer.?;
270 return @bitCast(T, @byteSwap(@bitCast(I, x)));
271 },
272 .Auto => @compileError("auto layout struct"),
273 },
274 else => @compileError("bswap on type " ++ @typeName(T)),
275 }
276}
277
278fn bswap_u32_array(slice: []u32) void {
279 comptime assert(need_bswap);
280 for (slice) |*elem| elem.* = @byteSwap(elem.*);
281}
282
283/// workaround for https://github.com/ziglang/zig/issues/14904
284fn bswap_and_workaround_u32(x: *align(1) const u32) u32 {
285 const bytes_ptr = @ptrCast(*const [4]u8, x);
286 return std.mem.readIntLittle(u32, bytes_ptr);
287}
288
289/// workaround for https://github.com/ziglang/zig/issues/14904
290fn bswap_and_workaround_tag(x: *align(1) const InMessage.Tag) InMessage.Tag {
291 const bytes_ptr = @ptrCast(*const [4]u8, x);
292 const int = std.mem.readIntLittle(u32, bytes_ptr);
293 return @intToEnum(InMessage.Tag, int);
294}
295
296const OutMessage = std.zig.Server.Message;
297const InMessage = std.zig.Client.Message;
298
299const Server = @This();
300const builtin = @import("builtin");
301const std = @import("std");
302const Allocator = std.mem.Allocator;
303const assert = std.debug.assert;
304const native_endian = builtin.target.cpu.arch.endian();
305const need_bswap = native_endian != .Little;
lib/std/zig/system/NativeTargetInfo.zig+5
......@@ -1090,6 +1090,11 @@ pub fn getExternalExecutor(
10901090 switch (candidate.target.os.tag) {
10911091 .windows => {
10921092 if (options.allow_wine) {
1093 // x86_64 wine does not support emulating aarch64-windows and
1094 // vice versa.
1095 if (candidate.target.cpu.arch != builtin.cpu.arch) {
1096 return bad_result;
1097 }
10931098 switch (candidate.target.cpu.arch.ptrBitWidth()) {
10941099 32 => return Executor{ .wine = "wine" },
10951100 64 => return Executor{ .wine = "wine64" },
lib/test_runner.zig+123-45
......@@ -8,14 +8,126 @@ pub const std_options = struct {
88};
99
1010var log_err_count: usize = 0;
11var cmdline_buffer: [4096]u8 = undefined;
12var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
1113
1214pub fn main() void {
13 if (builtin.zig_backend != .stage1 and
14 builtin.zig_backend != .stage2_llvm and
15 builtin.zig_backend != .stage2_c)
15 if (builtin.zig_backend == .stage2_wasm or
16 builtin.zig_backend == .stage2_x86_64 or
17 builtin.zig_backend == .stage2_aarch64)
1618 {
17 return main2() catch @panic("test failure");
19 return mainSimple() catch @panic("test failure");
20 }
21
22 const args = std.process.argsAlloc(fba.allocator()) catch
23 @panic("unable to parse command line args");
24
25 var listen = false;
26
27 for (args[1..]) |arg| {
28 if (std.mem.eql(u8, arg, "--listen=-")) {
29 listen = true;
30 } else {
31 @panic("unrecognized command line argument");
32 }
33 }
34
35 if (listen) {
36 return mainServer() catch @panic("internal test runner failure");
37 } else {
38 return mainTerminal();
39 }
40}
41
42fn mainServer() !void {
43 var server = try std.zig.Server.init(.{
44 .gpa = fba.allocator(),
45 .in = std.io.getStdIn(),
46 .out = std.io.getStdOut(),
47 .zig_version = builtin.zig_version_string,
48 });
49 defer server.deinit();
50
51 while (true) {
52 const hdr = try server.receiveMessage();
53 switch (hdr.tag) {
54 .exit => {
55 return std.process.exit(0);
56 },
57 .query_test_metadata => {
58 std.testing.allocator_instance = .{};
59 defer if (std.testing.allocator_instance.deinit()) {
60 @panic("internal test runner memory leak");
61 };
62
63 var string_bytes: std.ArrayListUnmanaged(u8) = .{};
64 defer string_bytes.deinit(std.testing.allocator);
65 try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null.
66
67 const test_fns = builtin.test_functions;
68 const names = try std.testing.allocator.alloc(u32, test_fns.len);
69 defer std.testing.allocator.free(names);
70 const async_frame_sizes = try std.testing.allocator.alloc(u32, test_fns.len);
71 defer std.testing.allocator.free(async_frame_sizes);
72 const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len);
73 defer std.testing.allocator.free(expected_panic_msgs);
74
75 for (test_fns, names, async_frame_sizes, expected_panic_msgs) |test_fn, *name, *async_frame_size, *expected_panic_msg| {
76 name.* = @intCast(u32, string_bytes.items.len);
77 try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1);
78 string_bytes.appendSliceAssumeCapacity(test_fn.name);
79 string_bytes.appendAssumeCapacity(0);
80
81 async_frame_size.* = @intCast(u32, test_fn.async_frame_size orelse 0);
82 expected_panic_msg.* = 0;
83 }
84
85 try server.serveTestMetadata(.{
86 .names = names,
87 .async_frame_sizes = async_frame_sizes,
88 .expected_panic_msgs = expected_panic_msgs,
89 .string_bytes = string_bytes.items,
90 });
91 },
92
93 .run_test => {
94 std.testing.allocator_instance = .{};
95 const index = try server.receiveBody_u32();
96 const test_fn = builtin.test_functions[index];
97 if (test_fn.async_frame_size != null)
98 @panic("TODO test runner implement async tests");
99 var fail = false;
100 var skip = false;
101 var leak = false;
102 test_fn.func() catch |err| switch (err) {
103 error.SkipZigTest => skip = true,
104 else => {
105 fail = true;
106 if (@errorReturnTrace()) |trace| {
107 std.debug.dumpStackTrace(trace.*);
108 }
109 },
110 };
111 leak = std.testing.allocator_instance.deinit();
112 try server.serveTestResults(.{
113 .index = index,
114 .flags = .{
115 .fail = fail,
116 .skip = skip,
117 .leak = leak,
118 },
119 });
120 },
121
122 else => {
123 std.debug.print("unsupported message: {x}", .{@enumToInt(hdr.tag)});
124 std.process.exit(1);
125 },
126 }
18127 }
128}
129
130fn mainTerminal() void {
19131 const test_fn_list = builtin.test_functions;
20132 var ok_count: usize = 0;
21133 var skip_count: usize = 0;
......@@ -118,51 +230,17 @@ pub fn log(
118230 }
119231}
120232
121pub fn main2() anyerror!void {
122 var skipped: usize = 0;
123 var failed: usize = 0;
124 // Simpler main(), exercising fewer language features, so that stage2 can handle it.
233/// Simpler main(), exercising fewer language features, so that
234/// work-in-progress backends can handle it.
235pub fn mainSimple() anyerror!void {
236 //const stderr = std.io.getStdErr();
125237 for (builtin.test_functions) |test_fn| {
126238 test_fn.func() catch |err| {
127239 if (err != error.SkipZigTest) {
128 failed += 1;
129 } else {
130 skipped += 1;
240 //stderr.writeAll(test_fn.name) catch {};
241 //stderr.writeAll("\n") catch {};
242 return err;
131243 }
132244 };
133245 }
134 if (builtin.zig_backend == .stage2_wasm or
135 builtin.zig_backend == .stage2_x86_64 or
136 builtin.zig_backend == .stage2_aarch64 or
137 builtin.zig_backend == .stage2_llvm or
138 builtin.zig_backend == .stage2_c)
139 {
140 const passed = builtin.test_functions.len - skipped - failed;
141 const stderr = std.io.getStdErr();
142 writeInt(stderr, passed) catch {};
143 stderr.writeAll(" passed; ") catch {};
144 writeInt(stderr, skipped) catch {};
145 stderr.writeAll(" skipped; ") catch {};
146 writeInt(stderr, failed) catch {};
147 stderr.writeAll(" failed.\n") catch {};
148 }
149 if (failed != 0) {
150 return error.TestsFailed;
151 }
152}
153
154fn writeInt(stderr: std.fs.File, int: usize) anyerror!void {
155 const base = 10;
156 var buf: [100]u8 = undefined;
157 var a: usize = int;
158 var index: usize = buf.len;
159 while (true) {
160 const digit = a % base;
161 index -= 1;
162 buf[index] = std.fmt.digitToChar(@intCast(u8, digit), .lower);
163 a /= base;
164 if (a == 0) break;
165 }
166 const slice = buf[index..];
167 try stderr.writeAll(slice);
168246}
src/AstGen.zig+88-40
......@@ -148,18 +148,24 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
148148 };
149149 defer gz_instructions.deinit(gpa);
150150
151 if (AstGen.structDeclInner(
152 &gen_scope,
153 &gen_scope.base,
154 0,
155 tree.containerDeclRoot(),
156 .Auto,
157 0,
158 )) |struct_decl_ref| {
159 assert(refToIndex(struct_decl_ref).? == 0);
160 } else |err| switch (err) {
161 error.OutOfMemory => return error.OutOfMemory,
162 error.AnalysisFail => {}, // Handled via compile_errors below.
151 // The AST -> ZIR lowering process assumes an AST that does not have any
152 // parse errors.
153 if (tree.errors.len == 0) {
154 if (AstGen.structDeclInner(
155 &gen_scope,
156 &gen_scope.base,
157 0,
158 tree.containerDeclRoot(),
159 .Auto,
160 0,
161 )) |struct_decl_ref| {
162 assert(refToIndex(struct_decl_ref).? == 0);
163 } else |err| switch (err) {
164 error.OutOfMemory => return error.OutOfMemory,
165 error.AnalysisFail => {}, // Handled via compile_errors below.
166 }
167 } else {
168 try lowerAstErrors(&astgen);
163169 }
164170
165171 const err_index = @enumToInt(Zir.ExtraIndex.compile_errors);
......@@ -10380,7 +10386,7 @@ fn appendErrorTok(
1038010386 comptime format: []const u8,
1038110387 args: anytype,
1038210388) !void {
10383 try astgen.appendErrorTokNotes(token, format, args, &[0]u32{});
10389 try astgen.appendErrorTokNotesOff(token, 0, format, args, &[0]u32{});
1038410390}
1038510391
1038610392fn failTokNotes(
......@@ -10390,7 +10396,7 @@ fn failTokNotes(
1039010396 args: anytype,
1039110397 notes: []const u32,
1039210398) InnerError {
10393 try appendErrorTokNotes(astgen, token, format, args, notes);
10399 try appendErrorTokNotesOff(astgen, token, 0, format, args, notes);
1039410400 return error.AnalysisFail;
1039510401}
1039610402
......@@ -10401,27 +10407,11 @@ fn appendErrorTokNotes(
1040110407 args: anytype,
1040210408 notes: []const u32,
1040310409) !void {
10404 @setCold(true);
10405 const string_bytes = &astgen.string_bytes;
10406 const msg = @intCast(u32, string_bytes.items.len);
10407 try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);
10408 const notes_index: u32 = if (notes.len != 0) blk: {
10409 const notes_start = astgen.extra.items.len;
10410 try astgen.extra.ensureTotalCapacity(astgen.gpa, notes_start + 1 + notes.len);
10411 astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len));
10412 astgen.extra.appendSliceAssumeCapacity(notes);
10413 break :blk @intCast(u32, notes_start);
10414 } else 0;
10415 try astgen.compile_errors.append(astgen.gpa, .{
10416 .msg = msg,
10417 .node = 0,
10418 .token = token,
10419 .byte_offset = 0,
10420 .notes = notes_index,
10421 });
10410 return appendErrorTokNotesOff(astgen, token, 0, format, args, notes);
1042210411}
1042310412
10424/// Same as `fail`, except given an absolute byte offset.
10413/// Same as `fail`, except given a token plus an offset from its starting byte
10414/// offset.
1042510415fn failOff(
1042610416 astgen: *AstGen,
1042710417 token: Ast.TokenIndex,
......@@ -10429,27 +10419,36 @@ fn failOff(
1042910419 comptime format: []const u8,
1043010420 args: anytype,
1043110421) InnerError {
10432 try appendErrorOff(astgen, token, byte_offset, format, args);
10422 try appendErrorTokNotesOff(astgen, token, byte_offset, format, args, &.{});
1043310423 return error.AnalysisFail;
1043410424}
1043510425
10436fn appendErrorOff(
10426fn appendErrorTokNotesOff(
1043710427 astgen: *AstGen,
1043810428 token: Ast.TokenIndex,
1043910429 byte_offset: u32,
1044010430 comptime format: []const u8,
1044110431 args: anytype,
10442) Allocator.Error!void {
10432 notes: []const u32,
10433) !void {
1044310434 @setCold(true);
10435 const gpa = astgen.gpa;
1044410436 const string_bytes = &astgen.string_bytes;
1044510437 const msg = @intCast(u32, string_bytes.items.len);
10446 try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);
10447 try astgen.compile_errors.append(astgen.gpa, .{
10438 try string_bytes.writer(gpa).print(format ++ "\x00", args);
10439 const notes_index: u32 = if (notes.len != 0) blk: {
10440 const notes_start = astgen.extra.items.len;
10441 try astgen.extra.ensureTotalCapacity(gpa, notes_start + 1 + notes.len);
10442 astgen.extra.appendAssumeCapacity(@intCast(u32, notes.len));
10443 astgen.extra.appendSliceAssumeCapacity(notes);
10444 break :blk @intCast(u32, notes_start);
10445 } else 0;
10446 try astgen.compile_errors.append(gpa, .{
1044810447 .msg = msg,
1044910448 .node = 0,
1045010449 .token = token,
1045110450 .byte_offset = byte_offset,
10452 .notes = 0,
10451 .notes = notes_index,
1045310452 });
1045410453}
1045510454
......@@ -10458,6 +10457,16 @@ fn errNoteTok(
1045810457 token: Ast.TokenIndex,
1045910458 comptime format: []const u8,
1046010459 args: anytype,
10460) Allocator.Error!u32 {
10461 return errNoteTokOff(astgen, token, 0, format, args);
10462}
10463
10464fn errNoteTokOff(
10465 astgen: *AstGen,
10466 token: Ast.TokenIndex,
10467 byte_offset: u32,
10468 comptime format: []const u8,
10469 args: anytype,
1046110470) Allocator.Error!u32 {
1046210471 @setCold(true);
1046310472 const string_bytes = &astgen.string_bytes;
......@@ -10467,7 +10476,7 @@ fn errNoteTok(
1046710476 .msg = msg,
1046810477 .node = 0,
1046910478 .token = token,
10470 .byte_offset = 0,
10479 .byte_offset = byte_offset,
1047110480 .notes = 0,
1047210481 });
1047310482}
......@@ -12634,3 +12643,42 @@ fn emitDbgStmt(gz: *GenZir, line: u32, column: u32) !void {
1263412643 },
1263512644 } });
1263612645}
12646
12647fn lowerAstErrors(astgen: *AstGen) !void {
12648 const tree = astgen.tree;
12649 assert(tree.errors.len > 0);
12650
12651 const gpa = astgen.gpa;
12652 const parse_err = tree.errors[0];
12653
12654 var msg: std.ArrayListUnmanaged(u8) = .{};
12655 defer msg.deinit(gpa);
12656
12657 const token_starts = tree.tokens.items(.start);
12658 const token_tags = tree.tokens.items(.tag);
12659
12660 var notes: std.ArrayListUnmanaged(u32) = .{};
12661 defer notes.deinit(gpa);
12662
12663 if (token_tags[parse_err.token + @boolToInt(parse_err.token_is_prev)] == .invalid) {
12664 const tok = parse_err.token + @boolToInt(parse_err.token_is_prev);
12665 const bad_off = @intCast(u32, tree.tokenSlice(parse_err.token + @boolToInt(parse_err.token_is_prev)).len);
12666 const byte_abs = token_starts[parse_err.token + @boolToInt(parse_err.token_is_prev)] + bad_off;
12667 try notes.append(gpa, try astgen.errNoteTokOff(tok, bad_off, "invalid byte: '{'}'", .{
12668 std.zig.fmtEscapes(tree.source[byte_abs..][0..1]),
12669 }));
12670 }
12671
12672 for (tree.errors[1..]) |note| {
12673 if (!note.is_note) break;
12674
12675 msg.clearRetainingCapacity();
12676 try tree.renderError(note, msg.writer(gpa));
12677 try notes.append(gpa, try astgen.errNoteTok(note.token, "{s}", .{msg.items}));
12678 }
12679
12680 const extra_offset = tree.errorOffset(parse_err);
12681 msg.clearRetainingCapacity();
12682 try tree.renderError(parse_err, msg.writer(gpa));
12683 try astgen.appendErrorTokNotesOff(parse_err.token, extra_offset, "{s}", .{msg.items}, notes.items);
12684}
src/Compilation.zig+424-608
......@@ -7,6 +7,9 @@ const Allocator = std.mem.Allocator;
77const assert = std.debug.assert;
88const log = std.log.scoped(.compilation);
99const Target = std.Target;
10const ThreadPool = std.Thread.Pool;
11const WaitGroup = std.Thread.WaitGroup;
12const ErrorBundle = std.zig.ErrorBundle;
1013
1114const Value = @import("value.zig").Value;
1215const Type = @import("type.zig").Type;
......@@ -30,8 +33,6 @@ const Cache = std.Build.Cache;
3033const translate_c = @import("translate_c.zig");
3134const clang = @import("clang.zig");
3235const c_codegen = @import("codegen/c.zig");
33const ThreadPool = @import("ThreadPool.zig");
34const WaitGroup = @import("WaitGroup.zig");
3536const libtsan = @import("libtsan.zig");
3637const Zir = @import("Zir.zig");
3738const Autodoc = @import("Autodoc.zig");
......@@ -99,6 +100,7 @@ job_queued_compiler_rt_lib: bool = false,
99100job_queued_compiler_rt_obj: bool = false,
100101alloc_failure_occurred: bool = false,
101102formatted_panics: bool = false,
103last_update_was_cache_hit: bool = false,
102104
103105c_source_files: []const CSourceFile,
104106clang_argv: []const []const u8,
......@@ -334,12 +336,41 @@ pub const MiscTask = enum {
334336 libssp,
335337 zig_libc,
336338 analyze_pkg,
339
340 @"musl crti.o",
341 @"musl crtn.o",
342 @"musl crt1.o",
343 @"musl rcrt1.o",
344 @"musl Scrt1.o",
345 @"musl libc.a",
346 @"musl libc.so",
347
348 @"wasi crt1-reactor.o",
349 @"wasi crt1-command.o",
350 @"wasi libc.a",
351 @"libwasi-emulated-process-clocks.a",
352 @"libwasi-emulated-getpid.a",
353 @"libwasi-emulated-mman.a",
354 @"libwasi-emulated-signal.a",
355
356 @"glibc crti.o",
357 @"glibc crtn.o",
358 @"glibc Scrt1.o",
359 @"glibc libc_nonshared.a",
360 @"glibc shared object",
361
362 @"mingw-w64 crt2.o",
363 @"mingw-w64 dllcrt2.o",
364 @"mingw-w64 mingw32.lib",
365 @"mingw-w64 msvcrt-os.lib",
366 @"mingw-w64 mingwex.lib",
367 @"mingw-w64 uuid.lib",
337368};
338369
339370pub const MiscError = struct {
340371 /// Allocated with gpa.
341372 msg: []u8,
342 children: ?AllErrors = null,
373 children: ?ErrorBundle = null,
343374
344375 pub fn deinit(misc_err: *MiscError, gpa: Allocator) void {
345376 gpa.free(misc_err.msg);
......@@ -365,448 +396,6 @@ pub const LldError = struct {
365396 }
366397};
367398
368/// To support incremental compilation, errors are stored in various places
369/// so that they can be created and destroyed appropriately. This structure
370/// is used to collect all the errors from the various places into one
371/// convenient place for API users to consume. It is allocated into 1 arena
372/// and freed all at once.
373pub const AllErrors = struct {
374 arena: std.heap.ArenaAllocator.State,
375 list: []const Message,
376
377 pub const Message = union(enum) {
378 src: struct {
379 msg: []const u8,
380 src_path: []const u8,
381 line: u32,
382 column: u32,
383 span: Module.SrcLoc.Span,
384 /// Usually one, but incremented for redundant messages.
385 count: u32 = 1,
386 /// Does not include the trailing newline.
387 source_line: ?[]const u8,
388 notes: []const Message = &.{},
389 reference_trace: []Message = &.{},
390
391 /// Splits the error message up into lines to properly indent them
392 /// to allow for long, good-looking error messages.
393 ///
394 /// This is used to split the message in `@compileError("hello\nworld")` for example.
395 fn writeMsg(src: @This(), stderr: anytype, indent: usize) !void {
396 var lines = mem.split(u8, src.msg, "\n");
397 while (lines.next()) |line| {
398 try stderr.writeAll(line);
399 if (lines.index == null) break;
400 try stderr.writeByte('\n');
401 try stderr.writeByteNTimes(' ', indent);
402 }
403 }
404 },
405 plain: struct {
406 msg: []const u8,
407 notes: []Message = &.{},
408 /// Usually one, but incremented for redundant messages.
409 count: u32 = 1,
410 },
411
412 pub fn incrementCount(msg: *Message) void {
413 switch (msg.*) {
414 .src => |*src| {
415 src.count += 1;
416 },
417 .plain => |*plain| {
418 plain.count += 1;
419 },
420 }
421 }
422
423 pub fn renderToStdErr(msg: Message, ttyconf: std.debug.TTY.Config) void {
424 std.debug.getStderrMutex().lock();
425 defer std.debug.getStderrMutex().unlock();
426 const stderr = std.io.getStdErr();
427 return msg.renderToWriter(ttyconf, stderr.writer(), "error", .Red, 0) catch return;
428 }
429
430 pub fn renderToWriter(
431 msg: Message,
432 ttyconf: std.debug.TTY.Config,
433 stderr: anytype,
434 kind: []const u8,
435 color: std.debug.TTY.Color,
436 indent: usize,
437 ) anyerror!void {
438 var counting_writer = std.io.countingWriter(stderr);
439 const counting_stderr = counting_writer.writer();
440 switch (msg) {
441 .src => |src| {
442 try counting_stderr.writeByteNTimes(' ', indent);
443 try ttyconf.setColor(stderr, .Bold);
444 try counting_stderr.print("{s}:{d}:{d}: ", .{
445 src.src_path,
446 src.line + 1,
447 src.column + 1,
448 });
449 try ttyconf.setColor(stderr, color);
450 try counting_stderr.writeAll(kind);
451 try counting_stderr.writeAll(": ");
452 // This is the length of the part before the error message:
453 // e.g. "file.zig:4:5: error: "
454 const prefix_len = @intCast(usize, counting_stderr.context.bytes_written);
455 try ttyconf.setColor(stderr, .Reset);
456 try ttyconf.setColor(stderr, .Bold);
457 if (src.count == 1) {
458 try src.writeMsg(stderr, prefix_len);
459 try stderr.writeByte('\n');
460 } else {
461 try src.writeMsg(stderr, prefix_len);
462 try ttyconf.setColor(stderr, .Dim);
463 try stderr.print(" ({d} times)\n", .{src.count});
464 }
465 try ttyconf.setColor(stderr, .Reset);
466 if (src.source_line) |line| {
467 for (line) |b| switch (b) {
468 '\t' => try stderr.writeByte(' '),
469 else => try stderr.writeByte(b),
470 };
471 try stderr.writeByte('\n');
472 // TODO basic unicode code point monospace width
473 const before_caret = src.span.main - src.span.start;
474 // -1 since span.main includes the caret
475 const after_caret = src.span.end - src.span.main -| 1;
476 try stderr.writeByteNTimes(' ', src.column - before_caret);
477 try ttyconf.setColor(stderr, .Green);
478 try stderr.writeByteNTimes('~', before_caret);
479 try stderr.writeByte('^');
480 try stderr.writeByteNTimes('~', after_caret);
481 try stderr.writeByte('\n');
482 try ttyconf.setColor(stderr, .Reset);
483 }
484 for (src.notes) |note| {
485 try note.renderToWriter(ttyconf, stderr, "note", .Cyan, indent);
486 }
487 if (src.reference_trace.len != 0) {
488 try ttyconf.setColor(stderr, .Reset);
489 try ttyconf.setColor(stderr, .Dim);
490 try stderr.print("referenced by:\n", .{});
491 for (src.reference_trace) |reference| {
492 switch (reference) {
493 .src => |ref_src| try stderr.print(" {s}: {s}:{d}:{d}\n", .{
494 ref_src.msg,
495 ref_src.src_path,
496 ref_src.line + 1,
497 ref_src.column + 1,
498 }),
499 .plain => |plain| if (plain.count != 0) {
500 try stderr.print(
501 " {d} reference(s) hidden; use '-freference-trace={d}' to see all references\n",
502 .{ plain.count, plain.count + src.reference_trace.len - 1 },
503 );
504 } else {
505 try stderr.print(
506 " remaining reference traces hidden; use '-freference-trace' to see all reference traces\n",
507 .{},
508 );
509 },
510 }
511 }
512 try stderr.writeByte('\n');
513 try ttyconf.setColor(stderr, .Reset);
514 }
515 },
516 .plain => |plain| {
517 try ttyconf.setColor(stderr, color);
518 try stderr.writeByteNTimes(' ', indent);
519 try stderr.writeAll(kind);
520 try stderr.writeAll(": ");
521 try ttyconf.setColor(stderr, .Reset);
522 if (plain.count == 1) {
523 try stderr.print("{s}\n", .{plain.msg});
524 } else {
525 try stderr.print("{s}", .{plain.msg});
526 try ttyconf.setColor(stderr, .Dim);
527 try stderr.print(" ({d} times)\n", .{plain.count});
528 }
529 try ttyconf.setColor(stderr, .Reset);
530 for (plain.notes) |note| {
531 try note.renderToWriter(ttyconf, stderr, "note", .Cyan, indent + 4);
532 }
533 },
534 }
535 }
536
537 pub const HashContext = struct {
538 pub fn hash(ctx: HashContext, key: *Message) u64 {
539 _ = ctx;
540 var hasher = std.hash.Wyhash.init(0);
541
542 switch (key.*) {
543 .src => |src| {
544 hasher.update(src.msg);
545 hasher.update(src.src_path);
546 std.hash.autoHash(&hasher, src.line);
547 std.hash.autoHash(&hasher, src.column);
548 std.hash.autoHash(&hasher, src.span.main);
549 },
550 .plain => |plain| {
551 hasher.update(plain.msg);
552 },
553 }
554
555 return hasher.final();
556 }
557
558 pub fn eql(ctx: HashContext, a: *Message, b: *Message) bool {
559 _ = ctx;
560 switch (a.*) {
561 .src => |a_src| switch (b.*) {
562 .src => |b_src| {
563 return mem.eql(u8, a_src.msg, b_src.msg) and
564 mem.eql(u8, a_src.src_path, b_src.src_path) and
565 a_src.line == b_src.line and
566 a_src.column == b_src.column and
567 a_src.span.main == b_src.span.main;
568 },
569 .plain => return false,
570 },
571 .plain => |a_plain| switch (b.*) {
572 .src => return false,
573 .plain => |b_plain| {
574 return mem.eql(u8, a_plain.msg, b_plain.msg);
575 },
576 },
577 }
578 }
579 };
580 };
581
582 pub fn deinit(self: *AllErrors, gpa: Allocator) void {
583 self.arena.promote(gpa).deinit();
584 }
585
586 pub fn add(
587 module: *Module,
588 arena: *std.heap.ArenaAllocator,
589 errors: *std.ArrayList(Message),
590 module_err_msg: Module.ErrorMsg,
591 ) !void {
592 const allocator = arena.allocator();
593
594 const notes_buf = try allocator.alloc(Message, module_err_msg.notes.len);
595 var note_i: usize = 0;
596
597 // De-duplicate error notes. The main use case in mind for this is
598 // too many "note: called from here" notes when eval branch quota is reached.
599 var seen_notes = std.HashMap(
600 *Message,
601 void,
602 Message.HashContext,
603 std.hash_map.default_max_load_percentage,
604 ).init(allocator);
605 const err_source = module_err_msg.src_loc.file_scope.getSource(module.gpa) catch |err| {
606 const file_path = try module_err_msg.src_loc.file_scope.fullPath(allocator);
607 try errors.append(.{
608 .plain = .{
609 .msg = try std.fmt.allocPrint(allocator, "unable to load '{s}': {s}", .{
610 file_path, @errorName(err),
611 }),
612 },
613 });
614 return;
615 };
616 const err_span = try module_err_msg.src_loc.span(module.gpa);
617 const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main);
618
619 for (module_err_msg.notes) |module_note| {
620 const source = try module_note.src_loc.file_scope.getSource(module.gpa);
621 const span = try module_note.src_loc.span(module.gpa);
622 const loc = std.zig.findLineColumn(source.bytes, span.main);
623 const file_path = try module_note.src_loc.file_scope.fullPath(allocator);
624 const note = &notes_buf[note_i];
625 note.* = .{
626 .src = .{
627 .src_path = file_path,
628 .msg = try allocator.dupe(u8, module_note.msg),
629 .span = span,
630 .line = @intCast(u32, loc.line),
631 .column = @intCast(u32, loc.column),
632 .source_line = if (err_loc.eql(loc)) null else try allocator.dupe(u8, loc.source_line),
633 },
634 };
635 const gop = try seen_notes.getOrPut(note);
636 if (gop.found_existing) {
637 gop.key_ptr.*.incrementCount();
638 } else {
639 note_i += 1;
640 }
641 }
642
643 const reference_trace = try allocator.alloc(Message, module_err_msg.reference_trace.len);
644 for (reference_trace, 0..) |*reference, i| {
645 const module_reference = module_err_msg.reference_trace[i];
646 if (module_reference.hidden != 0) {
647 reference.* = .{ .plain = .{ .msg = undefined, .count = module_reference.hidden } };
648 break;
649 } else if (module_reference.decl == null) {
650 reference.* = .{ .plain = .{ .msg = undefined, .count = 0 } };
651 break;
652 }
653 const source = try module_reference.src_loc.file_scope.getSource(module.gpa);
654 const span = try module_reference.src_loc.span(module.gpa);
655 const loc = std.zig.findLineColumn(source.bytes, span.main);
656 const file_path = try module_reference.src_loc.file_scope.fullPath(allocator);
657 reference.* = .{
658 .src = .{
659 .src_path = file_path,
660 .msg = try allocator.dupe(u8, std.mem.sliceTo(module_reference.decl.?, 0)),
661 .span = span,
662 .line = @intCast(u32, loc.line),
663 .column = @intCast(u32, loc.column),
664 .source_line = null,
665 },
666 };
667 }
668 const file_path = try module_err_msg.src_loc.file_scope.fullPath(allocator);
669 try errors.append(.{
670 .src = .{
671 .src_path = file_path,
672 .msg = try allocator.dupe(u8, module_err_msg.msg),
673 .span = err_span,
674 .line = @intCast(u32, err_loc.line),
675 .column = @intCast(u32, err_loc.column),
676 .notes = notes_buf[0..note_i],
677 .reference_trace = reference_trace,
678 .source_line = if (module_err_msg.src_loc.lazy == .entire_file) null else try allocator.dupe(u8, err_loc.source_line),
679 },
680 });
681 }
682
683 pub fn addZir(
684 arena: Allocator,
685 errors: *std.ArrayList(Message),
686 file: *Module.File,
687 ) !void {
688 assert(file.zir_loaded);
689 assert(file.tree_loaded);
690 assert(file.source_loaded);
691 const payload_index = file.zir.extra[@enumToInt(Zir.ExtraIndex.compile_errors)];
692 assert(payload_index != 0);
693
694 const header = file.zir.extraData(Zir.Inst.CompileErrors, payload_index);
695 const items_len = header.data.items_len;
696 var extra_index = header.end;
697 var item_i: usize = 0;
698 while (item_i < items_len) : (item_i += 1) {
699 const item = file.zir.extraData(Zir.Inst.CompileErrors.Item, extra_index);
700 extra_index = item.end;
701 const err_span = blk: {
702 if (item.data.node != 0) {
703 break :blk Module.SrcLoc.nodeToSpan(&file.tree, item.data.node);
704 }
705 const token_starts = file.tree.tokens.items(.start);
706 const start = token_starts[item.data.token] + item.data.byte_offset;
707 const end = start + @intCast(u32, file.tree.tokenSlice(item.data.token).len) - item.data.byte_offset;
708 break :blk Module.SrcLoc.Span{ .start = start, .end = end, .main = start };
709 };
710 const err_loc = std.zig.findLineColumn(file.source, err_span.main);
711
712 var notes: []Message = &[0]Message{};
713 if (item.data.notes != 0) {
714 const block = file.zir.extraData(Zir.Inst.Block, item.data.notes);
715 const body = file.zir.extra[block.end..][0..block.data.body_len];
716 notes = try arena.alloc(Message, body.len);
717 for (notes, 0..) |*note, i| {
718 const note_item = file.zir.extraData(Zir.Inst.CompileErrors.Item, body[i]);
719 const msg = file.zir.nullTerminatedString(note_item.data.msg);
720 const span = blk: {
721 if (note_item.data.node != 0) {
722 break :blk Module.SrcLoc.nodeToSpan(&file.tree, note_item.data.node);
723 }
724 const token_starts = file.tree.tokens.items(.start);
725 const start = token_starts[note_item.data.token] + note_item.data.byte_offset;
726 const end = start + @intCast(u32, file.tree.tokenSlice(note_item.data.token).len) - item.data.byte_offset;
727 break :blk Module.SrcLoc.Span{ .start = start, .end = end, .main = start };
728 };
729 const loc = std.zig.findLineColumn(file.source, span.main);
730
731 note.* = .{
732 .src = .{
733 .src_path = try file.fullPath(arena),
734 .msg = try arena.dupe(u8, msg),
735 .span = span,
736 .line = @intCast(u32, loc.line),
737 .column = @intCast(u32, loc.column),
738 .notes = &.{}, // TODO rework this function to be recursive
739 .source_line = if (loc.eql(err_loc)) null else try arena.dupe(u8, loc.source_line),
740 },
741 };
742 }
743 }
744
745 const msg = file.zir.nullTerminatedString(item.data.msg);
746 try errors.append(.{
747 .src = .{
748 .src_path = try file.fullPath(arena),
749 .msg = try arena.dupe(u8, msg),
750 .span = err_span,
751 .line = @intCast(u32, err_loc.line),
752 .column = @intCast(u32, err_loc.column),
753 .notes = notes,
754 .source_line = try arena.dupe(u8, err_loc.source_line),
755 },
756 });
757 }
758 }
759
760 fn addPlain(
761 arena: *std.heap.ArenaAllocator,
762 errors: *std.ArrayList(Message),
763 msg: []const u8,
764 ) !void {
765 _ = arena;
766 try errors.append(.{ .plain = .{ .msg = msg } });
767 }
768
769 fn addPlainWithChildren(
770 arena: *std.heap.ArenaAllocator,
771 errors: *std.ArrayList(Message),
772 msg: []const u8,
773 optional_children: ?AllErrors,
774 ) !void {
775 const allocator = arena.allocator();
776 const duped_msg = try allocator.dupe(u8, msg);
777 if (optional_children) |*children| {
778 try errors.append(.{ .plain = .{
779 .msg = duped_msg,
780 .notes = try dupeList(children.list, allocator),
781 } });
782 } else {
783 try errors.append(.{ .plain = .{ .msg = duped_msg } });
784 }
785 }
786
787 fn dupeList(list: []const Message, arena: Allocator) Allocator.Error![]Message {
788 const duped_list = try arena.alloc(Message, list.len);
789 for (list, 0..) |item, i| {
790 duped_list[i] = switch (item) {
791 .src => |src| .{ .src = .{
792 .msg = try arena.dupe(u8, src.msg),
793 .src_path = try arena.dupe(u8, src.src_path),
794 .line = src.line,
795 .column = src.column,
796 .span = src.span,
797 .source_line = if (src.source_line) |s| try arena.dupe(u8, s) else null,
798 .notes = try dupeList(src.notes, arena),
799 } },
800 .plain => |plain| .{ .plain = .{
801 .msg = try arena.dupe(u8, plain.msg),
802 .notes = try dupeList(plain.notes, arena),
803 } },
804 };
805 }
806 return duped_list;
807 }
808};
809
810399pub const Directory = Cache.Directory;
811400
812401pub const EmitLoc = struct {
......@@ -2259,12 +1848,20 @@ fn cleanupTmpArtifactDirectory(
22591848 }
22601849}
22611850
1851pub fn hotCodeSwap(comp: *Compilation, prog_node: *std.Progress.Node, pid: std.ChildProcess.Id) !void {
1852 comp.bin_file.child_pid = pid;
1853 try comp.makeBinFileWritable();
1854 try comp.update(prog_node);
1855 try comp.makeBinFileExecutable();
1856}
1857
22621858/// Detect changes to source files, perform semantic analysis, and update the output files.
2263pub fn update(comp: *Compilation) !void {
1859pub fn update(comp: *Compilation, main_progress_node: *std.Progress.Node) !void {
22641860 const tracy_trace = trace(@src());
22651861 defer tracy_trace.end();
22661862
22671863 comp.clearMiscFailures();
1864 comp.last_update_was_cache_hit = false;
22681865
22691866 var man: Cache.Manifest = undefined;
22701867 defer if (comp.whole_cache_manifest != null) man.deinit();
......@@ -2292,6 +1889,7 @@ pub fn update(comp: *Compilation) !void {
22921889 return err;
22931890 };
22941891 if (is_hit) {
1892 comp.last_update_was_cache_hit = true;
22951893 log.debug("CacheMode.whole cache hit for {s}", .{comp.bin_file.options.root_name});
22961894 const digest = man.final();
22971895
......@@ -2407,21 +2005,6 @@ pub fn update(comp: *Compilation) !void {
24072005 }
24082006 }
24092007
2410 // If the terminal is dumb, we dont want to show the user all the output.
2411 var progress: std.Progress = .{ .dont_print_on_dumb = true };
2412 const main_progress_node = progress.start("", 0);
2413 defer main_progress_node.end();
2414 switch (comp.color) {
2415 .off => {
2416 progress.terminal = null;
2417 },
2418 .on => {
2419 progress.terminal = std.io.getStdErr();
2420 progress.supports_ansi_escape_codes = true;
2421 },
2422 .auto => {},
2423 }
2424
24252008 try comp.performAllTheWork(main_progress_node);
24262009
24272010 if (comp.bin_file.options.module) |module| {
......@@ -2891,7 +2474,7 @@ pub fn makeBinFileWritable(self: *Compilation) !void {
28912474}
28922475
28932476/// This function is temporally single-threaded.
2894pub fn totalErrorCount(self: *Compilation) usize {
2477pub fn totalErrorCount(self: *Compilation) u32 {
28952478 var total: usize = self.failed_c_objects.count() + self.misc_failures.count() +
28962479 @boolToInt(self.alloc_failure_occurred) + self.lld_errors.items.len;
28972480
......@@ -2951,17 +2534,16 @@ pub fn totalErrorCount(self: *Compilation) usize {
29512534 }
29522535 }
29532536
2954 return total;
2537 return @intCast(u32, total);
29552538}
29562539
29572540/// This function is temporally single-threaded.
2958pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
2959 var arena = std.heap.ArenaAllocator.init(self.gpa);
2960 errdefer arena.deinit();
2961 const arena_allocator = arena.allocator();
2541pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
2542 const gpa = self.gpa;
29622543
2963 var errors = std.ArrayList(AllErrors.Message).init(self.gpa);
2964 defer errors.deinit();
2544 var bundle: ErrorBundle.Wip = undefined;
2545 try bundle.init(gpa);
2546 defer bundle.deinit();
29652547
29662548 {
29672549 var it = self.failed_c_objects.iterator();
......@@ -2970,53 +2552,58 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
29702552 const err_msg = entry.value_ptr.*;
29712553 // TODO these fields will need to be adjusted when we have proper
29722554 // C error reporting bubbling up.
2973 try errors.append(.{
2974 .src = .{
2975 .src_path = try arena_allocator.dupe(u8, c_object.src.src_path),
2976 .msg = try std.fmt.allocPrint(arena_allocator, "unable to build C object: {s}", .{
2977 err_msg.msg,
2978 }),
2979 .span = .{ .start = 0, .end = 1, .main = 0 },
2555 try bundle.addRootErrorMessage(.{
2556 .msg = try bundle.printString("unable to build C object: {s}", .{err_msg.msg}),
2557 .src_loc = try bundle.addSourceLocation(.{
2558 .src_path = try bundle.addString(c_object.src.src_path),
2559 .span_start = 0,
2560 .span_main = 0,
2561 .span_end = 1,
29802562 .line = err_msg.line,
29812563 .column = err_msg.column,
2982 .source_line = null, // TODO
2983 },
2564 .source_line = 0, // TODO
2565 }),
29842566 });
29852567 }
29862568 }
2569
29872570 for (self.lld_errors.items) |lld_error| {
2988 const notes = try arena_allocator.alloc(AllErrors.Message, lld_error.context_lines.len);
2989 for (lld_error.context_lines, 0..) |context_line, i| {
2990 notes[i] = .{ .plain = .{
2991 .msg = try arena_allocator.dupe(u8, context_line),
2992 } };
2993 }
2571 const notes_len = @intCast(u32, lld_error.context_lines.len);
29942572
2995 try errors.append(.{
2996 .plain = .{
2997 .msg = try arena_allocator.dupe(u8, lld_error.msg),
2998 .notes = notes,
2999 },
2573 try bundle.addRootErrorMessage(.{
2574 .msg = try bundle.addString(lld_error.msg),
2575 .notes_len = notes_len,
30002576 });
2577 const notes_start = try bundle.reserveNotes(notes_len);
2578 for (notes_start.., lld_error.context_lines) |note, context_line| {
2579 bundle.extra.items[note] = @enumToInt(bundle.addErrorMessageAssumeCapacity(.{
2580 .msg = try bundle.addString(context_line),
2581 }));
2582 }
30012583 }
30022584 for (self.misc_failures.values()) |*value| {
3003 try AllErrors.addPlainWithChildren(&arena, &errors, value.msg, value.children);
2585 try bundle.addRootErrorMessage(.{
2586 .msg = try bundle.addString(value.msg),
2587 .notes_len = if (value.children) |b| b.errorMessageCount() else 0,
2588 });
2589 if (value.children) |b| try bundle.addBundle(b);
30042590 }
30052591 if (self.alloc_failure_occurred) {
3006 try AllErrors.addPlain(&arena, &errors, "memory allocation failure");
2592 try bundle.addRootErrorMessage(.{
2593 .msg = try bundle.addString("memory allocation failure"),
2594 });
30072595 }
30082596 if (self.bin_file.options.module) |module| {
30092597 {
30102598 var it = module.failed_files.iterator();
30112599 while (it.next()) |entry| {
30122600 if (entry.value_ptr.*) |msg| {
3013 try AllErrors.add(module, &arena, &errors, msg.*);
2601 try addModuleErrorMsg(&bundle, msg.*);
30142602 } else {
3015 // Must be ZIR errors. In order for ZIR errors to exist, the parsing
3016 // must have completed successfully.
3017 const tree = try entry.key_ptr.*.getTree(module.gpa);
3018 assert(tree.errors.len == 0);
3019 try AllErrors.addZir(arena_allocator, &errors, entry.key_ptr.*);
2603 // Must be ZIR errors. Note that this may include AST errors.
2604 // addZirErrorMessages asserts that the tree is loaded.
2605 _ = try entry.key_ptr.*.getTree(gpa);
2606 try addZirErrorMessages(&bundle, entry.key_ptr.*);
30202607 }
30212608 }
30222609 }
......@@ -3024,7 +2611,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
30242611 var it = module.failed_embed_files.iterator();
30252612 while (it.next()) |entry| {
30262613 const msg = entry.value_ptr.*;
3027 try AllErrors.add(module, &arena, &errors, msg.*);
2614 try addModuleErrorMsg(&bundle, msg.*);
30282615 }
30292616 }
30302617 {
......@@ -3034,23 +2621,20 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
30342621 // Skip errors for Decls within files that had a parse failure.
30352622 // We'll try again once parsing succeeds.
30362623 if (decl.getFileScope().okToReportErrors()) {
3037 try AllErrors.add(module, &arena, &errors, entry.value_ptr.*.*);
2624 try addModuleErrorMsg(&bundle, entry.value_ptr.*.*);
30382625 if (module.cimport_errors.get(entry.key_ptr.*)) |cimport_errors| for (cimport_errors) |c_error| {
3039 if (c_error.path) |some|
3040 try errors.append(.{
3041 .src = .{
3042 .src_path = try arena_allocator.dupe(u8, std.mem.span(some)),
3043 .span = .{ .start = c_error.offset, .end = c_error.offset + 1, .main = c_error.offset },
3044 .msg = try arena_allocator.dupe(u8, std.mem.span(c_error.msg)),
3045 .line = c_error.line,
3046 .column = c_error.column,
3047 .source_line = if (c_error.source_line) |line| try arena_allocator.dupe(u8, std.mem.span(line)) else null,
3048 },
3049 })
3050 else
3051 try errors.append(.{
3052 .plain = .{ .msg = try arena_allocator.dupe(u8, std.mem.span(c_error.msg)) },
3053 });
2626 try bundle.addRootErrorMessage(.{
2627 .msg = try bundle.addString(std.mem.span(c_error.msg)),
2628 .src_loc = if (c_error.path) |some| try bundle.addSourceLocation(.{
2629 .src_path = try bundle.addString(std.mem.span(some)),
2630 .span_start = c_error.offset,
2631 .span_main = c_error.offset,
2632 .span_end = c_error.offset + 1,
2633 .line = c_error.line,
2634 .column = c_error.column,
2635 .source_line = if (c_error.source_line) |line| try bundle.addString(std.mem.span(line)) else 0,
2636 }) else .none,
2637 });
30542638 };
30552639 }
30562640 }
......@@ -3062,45 +2646,39 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
30622646 // Skip errors for Decls within files that had a parse failure.
30632647 // We'll try again once parsing succeeds.
30642648 if (decl.getFileScope().okToReportErrors()) {
3065 try AllErrors.add(module, &arena, &errors, entry.value_ptr.*.*);
2649 try addModuleErrorMsg(&bundle, entry.value_ptr.*.*);
30662650 }
30672651 }
30682652 }
30692653 for (module.failed_exports.values()) |value| {
3070 try AllErrors.add(module, &arena, &errors, value.*);
2654 try addModuleErrorMsg(&bundle, value.*);
30712655 }
30722656 }
30732657
3074 if (errors.items.len == 0) {
2658 if (bundle.root_list.items.len == 0) {
30752659 if (self.link_error_flags.no_entry_point_found) {
3076 try errors.append(.{
3077 .plain = .{
3078 .msg = try std.fmt.allocPrint(arena_allocator, "no entry point found", .{}),
3079 },
2660 try bundle.addRootErrorMessage(.{
2661 .msg = try bundle.addString("no entry point found"),
30802662 });
30812663 }
30822664 }
30832665
30842666 if (self.link_error_flags.missing_libc) {
3085 const notes = try arena_allocator.create([2]AllErrors.Message);
3086 notes.* = .{
3087 .{ .plain = .{
3088 .msg = try arena_allocator.dupe(u8, "run 'zig libc -h' to learn about libc installations"),
3089 } },
3090 .{ .plain = .{
3091 .msg = try arena_allocator.dupe(u8, "run 'zig targets' to see the targets for which zig can always provide libc"),
3092 } },
3093 };
3094 try errors.append(.{
3095 .plain = .{
3096 .msg = try std.fmt.allocPrint(arena_allocator, "libc not available", .{}),
3097 .notes = notes,
3098 },
2667 try bundle.addRootErrorMessage(.{
2668 .msg = try bundle.addString("libc not available"),
2669 .notes_len = 2,
30992670 });
2671 const notes_start = try bundle.reserveNotes(2);
2672 bundle.extra.items[notes_start + 0] = @enumToInt(try bundle.addErrorMessage(.{
2673 .msg = try bundle.addString("run 'zig libc -h' to learn about libc installations"),
2674 }));
2675 bundle.extra.items[notes_start + 1] = @enumToInt(try bundle.addErrorMessage(.{
2676 .msg = try bundle.addString("run 'zig targets' to see the targets for which zig can always provide libc"),
2677 }));
31002678 }
31012679
31022680 if (self.bin_file.options.module) |module| {
3103 if (errors.items.len == 0 and module.compile_log_decls.count() != 0) {
2681 if (bundle.root_list.items.len == 0 and module.compile_log_decls.count() != 0) {
31042682 const keys = module.compile_log_decls.keys();
31052683 const values = module.compile_log_decls.values();
31062684 // First one will be the error; subsequent ones will be notes.
......@@ -3109,9 +2687,9 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
31092687 const err_msg = Module.ErrorMsg{
31102688 .src_loc = src_loc,
31112689 .msg = "found compile log statement",
3112 .notes = try self.gpa.alloc(Module.ErrorMsg, module.compile_log_decls.count() - 1),
2690 .notes = try gpa.alloc(Module.ErrorMsg, module.compile_log_decls.count() - 1),
31132691 };
3114 defer self.gpa.free(err_msg.notes);
2692 defer gpa.free(err_msg.notes);
31152693
31162694 for (keys[1..], 0..) |key, i| {
31172695 const note_decl = module.declPtr(key);
......@@ -3121,21 +2699,260 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
31212699 };
31222700 }
31232701
3124 try AllErrors.add(module, &arena, &errors, err_msg);
2702 try addModuleErrorMsg(&bundle, err_msg);
2703 }
2704 }
2705
2706 assert(self.totalErrorCount() == bundle.root_list.items.len);
2707
2708 const compile_log_text = if (self.bin_file.options.module) |m| m.compile_log_text.items else "";
2709 return bundle.toOwnedBundle(compile_log_text);
2710}
2711
2712pub const ErrorNoteHashContext = struct {
2713 eb: *const ErrorBundle.Wip,
2714
2715 pub fn hash(ctx: ErrorNoteHashContext, key: ErrorBundle.ErrorMessage) u32 {
2716 var hasher = std.hash.Wyhash.init(0);
2717 const eb = ctx.eb.tmpBundle();
2718
2719 hasher.update(eb.nullTerminatedString(key.msg));
2720 if (key.src_loc != .none) {
2721 const src = eb.getSourceLocation(key.src_loc);
2722 hasher.update(eb.nullTerminatedString(src.src_path));
2723 std.hash.autoHash(&hasher, src.line);
2724 std.hash.autoHash(&hasher, src.column);
2725 std.hash.autoHash(&hasher, src.span_main);
31252726 }
2727
2728 return @truncate(u32, hasher.final());
31262729 }
31272730
3128 assert(errors.items.len == self.totalErrorCount());
2731 pub fn eql(
2732 ctx: ErrorNoteHashContext,
2733 a: ErrorBundle.ErrorMessage,
2734 b: ErrorBundle.ErrorMessage,
2735 b_index: usize,
2736 ) bool {
2737 _ = b_index;
2738 const eb = ctx.eb.tmpBundle();
2739 const msg_a = eb.nullTerminatedString(a.msg);
2740 const msg_b = eb.nullTerminatedString(b.msg);
2741 if (!std.mem.eql(u8, msg_a, msg_b)) return false;
2742
2743 if (a.src_loc == .none and b.src_loc == .none) return true;
2744 if (a.src_loc == .none or b.src_loc == .none) return false;
2745 const src_a = eb.getSourceLocation(a.src_loc);
2746 const src_b = eb.getSourceLocation(b.src_loc);
2747
2748 const src_path_a = eb.nullTerminatedString(src_a.src_path);
2749 const src_path_b = eb.nullTerminatedString(src_b.src_path);
2750
2751 return std.mem.eql(u8, src_path_a, src_path_b) and
2752 src_a.line == src_b.line and
2753 src_a.column == src_b.column and
2754 src_a.span_main == src_b.span_main;
2755 }
2756};
31292757
3130 return AllErrors{
3131 .list = try arena_allocator.dupe(AllErrors.Message, errors.items),
3132 .arena = arena.state,
2758pub fn addModuleErrorMsg(eb: *ErrorBundle.Wip, module_err_msg: Module.ErrorMsg) !void {
2759 const gpa = eb.gpa;
2760 const err_source = module_err_msg.src_loc.file_scope.getSource(gpa) catch |err| {
2761 const file_path = try module_err_msg.src_loc.file_scope.fullPath(gpa);
2762 defer gpa.free(file_path);
2763 try eb.addRootErrorMessage(.{
2764 .msg = try eb.printString("unable to load '{s}': {s}", .{
2765 file_path, @errorName(err),
2766 }),
2767 });
2768 return;
31332769 };
2770 const err_span = try module_err_msg.src_loc.span(gpa);
2771 const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main);
2772 const file_path = try module_err_msg.src_loc.file_scope.fullPath(gpa);
2773 defer gpa.free(file_path);
2774
2775 var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .{};
2776 defer ref_traces.deinit(gpa);
2777
2778 for (module_err_msg.reference_trace) |module_reference| {
2779 if (module_reference.hidden != 0) {
2780 try ref_traces.append(gpa, .{
2781 .decl_name = module_reference.hidden,
2782 .src_loc = .none,
2783 });
2784 break;
2785 } else if (module_reference.decl == null) {
2786 try ref_traces.append(gpa, .{
2787 .decl_name = 0,
2788 .src_loc = .none,
2789 });
2790 break;
2791 }
2792 const source = try module_reference.src_loc.file_scope.getSource(gpa);
2793 const span = try module_reference.src_loc.span(gpa);
2794 const loc = std.zig.findLineColumn(source.bytes, span.main);
2795 const rt_file_path = try module_reference.src_loc.file_scope.fullPath(gpa);
2796 defer gpa.free(rt_file_path);
2797 try ref_traces.append(gpa, .{
2798 .decl_name = try eb.addString(std.mem.sliceTo(module_reference.decl.?, 0)),
2799 .src_loc = try eb.addSourceLocation(.{
2800 .src_path = try eb.addString(rt_file_path),
2801 .span_start = span.start,
2802 .span_main = span.main,
2803 .span_end = span.end,
2804 .line = @intCast(u32, loc.line),
2805 .column = @intCast(u32, loc.column),
2806 .source_line = 0,
2807 }),
2808 });
2809 }
2810
2811 const src_loc = try eb.addSourceLocation(.{
2812 .src_path = try eb.addString(file_path),
2813 .span_start = err_span.start,
2814 .span_main = err_span.main,
2815 .span_end = err_span.end,
2816 .line = @intCast(u32, err_loc.line),
2817 .column = @intCast(u32, err_loc.column),
2818 .source_line = if (module_err_msg.src_loc.lazy == .entire_file)
2819 0
2820 else
2821 try eb.addString(err_loc.source_line),
2822 .reference_trace_len = @intCast(u32, ref_traces.items.len),
2823 });
2824
2825 for (ref_traces.items) |rt| {
2826 try eb.addReferenceTrace(rt);
2827 }
2828
2829 // De-duplicate error notes. The main use case in mind for this is
2830 // too many "note: called from here" notes when eval branch quota is reached.
2831 var notes: std.ArrayHashMapUnmanaged(ErrorBundle.ErrorMessage, void, ErrorNoteHashContext, true) = .{};
2832 defer notes.deinit(gpa);
2833
2834 for (module_err_msg.notes) |module_note| {
2835 const source = try module_note.src_loc.file_scope.getSource(gpa);
2836 const span = try module_note.src_loc.span(gpa);
2837 const loc = std.zig.findLineColumn(source.bytes, span.main);
2838 const note_file_path = try module_note.src_loc.file_scope.fullPath(gpa);
2839 defer gpa.free(note_file_path);
2840
2841 const gop = try notes.getOrPutContext(gpa, .{
2842 .msg = try eb.addString(module_note.msg),
2843 .src_loc = try eb.addSourceLocation(.{
2844 .src_path = try eb.addString(note_file_path),
2845 .span_start = span.start,
2846 .span_main = span.main,
2847 .span_end = span.end,
2848 .line = @intCast(u32, loc.line),
2849 .column = @intCast(u32, loc.column),
2850 .source_line = if (err_loc.eql(loc)) 0 else try eb.addString(loc.source_line),
2851 }),
2852 }, .{ .eb = eb });
2853 if (gop.found_existing) {
2854 gop.key_ptr.count += 1;
2855 }
2856 }
2857
2858 const notes_len = @intCast(u32, notes.entries.len);
2859
2860 try eb.addRootErrorMessage(.{
2861 .msg = try eb.addString(module_err_msg.msg),
2862 .src_loc = src_loc,
2863 .notes_len = notes_len,
2864 });
2865
2866 const notes_start = try eb.reserveNotes(notes_len);
2867
2868 for (notes_start.., notes.keys()) |i, note| {
2869 eb.extra.items[i] = @enumToInt(try eb.addErrorMessage(note));
2870 }
31342871}
31352872
3136pub fn getCompileLogOutput(self: *Compilation) []const u8 {
3137 const module = self.bin_file.options.module orelse return &[0]u8{};
3138 return module.compile_log_text.items;
2873pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Module.File) !void {
2874 assert(file.zir_loaded);
2875 assert(file.tree_loaded);
2876 assert(file.source_loaded);
2877 const payload_index = file.zir.extra[@enumToInt(Zir.ExtraIndex.compile_errors)];
2878 assert(payload_index != 0);
2879 const gpa = eb.gpa;
2880
2881 const header = file.zir.extraData(Zir.Inst.CompileErrors, payload_index);
2882 const items_len = header.data.items_len;
2883 var extra_index = header.end;
2884 for (0..items_len) |_| {
2885 const item = file.zir.extraData(Zir.Inst.CompileErrors.Item, extra_index);
2886 extra_index = item.end;
2887 const err_span = blk: {
2888 if (item.data.node != 0) {
2889 break :blk Module.SrcLoc.nodeToSpan(&file.tree, item.data.node);
2890 }
2891 const token_starts = file.tree.tokens.items(.start);
2892 const start = token_starts[item.data.token] + item.data.byte_offset;
2893 const end = start + @intCast(u32, file.tree.tokenSlice(item.data.token).len) - item.data.byte_offset;
2894 break :blk Module.SrcLoc.Span{ .start = start, .end = end, .main = start };
2895 };
2896 const err_loc = std.zig.findLineColumn(file.source, err_span.main);
2897
2898 {
2899 const msg = file.zir.nullTerminatedString(item.data.msg);
2900 const src_path = try file.fullPath(gpa);
2901 defer gpa.free(src_path);
2902 try eb.addRootErrorMessage(.{
2903 .msg = try eb.addString(msg),
2904 .src_loc = try eb.addSourceLocation(.{
2905 .src_path = try eb.addString(src_path),
2906 .span_start = err_span.start,
2907 .span_main = err_span.main,
2908 .span_end = err_span.end,
2909 .line = @intCast(u32, err_loc.line),
2910 .column = @intCast(u32, err_loc.column),
2911 .source_line = try eb.addString(err_loc.source_line),
2912 }),
2913 .notes_len = item.data.notesLen(file.zir),
2914 });
2915 }
2916
2917 if (item.data.notes != 0) {
2918 const notes_start = try eb.reserveNotes(item.data.notes);
2919 const block = file.zir.extraData(Zir.Inst.Block, item.data.notes);
2920 const body = file.zir.extra[block.end..][0..block.data.body_len];
2921 for (notes_start.., body) |note_i, body_elem| {
2922 const note_item = file.zir.extraData(Zir.Inst.CompileErrors.Item, body_elem);
2923 const msg = file.zir.nullTerminatedString(note_item.data.msg);
2924 const span = blk: {
2925 if (note_item.data.node != 0) {
2926 break :blk Module.SrcLoc.nodeToSpan(&file.tree, note_item.data.node);
2927 }
2928 const token_starts = file.tree.tokens.items(.start);
2929 const start = token_starts[note_item.data.token] + note_item.data.byte_offset;
2930 const end = start + @intCast(u32, file.tree.tokenSlice(note_item.data.token).len) - item.data.byte_offset;
2931 break :blk Module.SrcLoc.Span{ .start = start, .end = end, .main = start };
2932 };
2933 const loc = std.zig.findLineColumn(file.source, span.main);
2934 const src_path = try file.fullPath(gpa);
2935 defer gpa.free(src_path);
2936
2937 eb.extra.items[note_i] = @enumToInt(try eb.addErrorMessage(.{
2938 .msg = try eb.addString(msg),
2939 .src_loc = try eb.addSourceLocation(.{
2940 .src_path = try eb.addString(src_path),
2941 .span_start = span.start,
2942 .span_main = span.main,
2943 .span_end = span.end,
2944 .line = @intCast(u32, loc.line),
2945 .column = @intCast(u32, loc.column),
2946 .source_line = if (loc.eql(err_loc))
2947 0
2948 else
2949 try eb.addString(loc.source_line),
2950 }),
2951 .notes_len = 0, // TODO rework this function to be recursive
2952 }));
2953 }
2954 }
2955 }
31392956}
31402957
31412958pub fn performAllTheWork(
......@@ -3231,11 +3048,11 @@ pub fn performAllTheWork(
32313048 // backend, preventing anonymous Decls from being prematurely destroyed.
32323049 while (true) {
32333050 if (comp.work_queue.readItem()) |work_item| {
3234 try processOneJob(comp, work_item);
3051 try processOneJob(comp, work_item, main_progress_node);
32353052 continue;
32363053 }
32373054 if (comp.anon_work_queue.readItem()) |work_item| {
3238 try processOneJob(comp, work_item);
3055 try processOneJob(comp, work_item, main_progress_node);
32393056 continue;
32403057 }
32413058 break;
......@@ -3243,16 +3060,16 @@ pub fn performAllTheWork(
32433060
32443061 if (comp.job_queued_compiler_rt_lib) {
32453062 comp.job_queued_compiler_rt_lib = false;
3246 buildCompilerRtOneShot(comp, .Lib, &comp.compiler_rt_lib);
3063 buildCompilerRtOneShot(comp, .Lib, &comp.compiler_rt_lib, main_progress_node);
32473064 }
32483065
32493066 if (comp.job_queued_compiler_rt_obj) {
32503067 comp.job_queued_compiler_rt_obj = false;
3251 buildCompilerRtOneShot(comp, .Obj, &comp.compiler_rt_obj);
3068 buildCompilerRtOneShot(comp, .Obj, &comp.compiler_rt_obj, main_progress_node);
32523069 }
32533070}
32543071
3255fn processOneJob(comp: *Compilation, job: Job) !void {
3072fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !void {
32563073 switch (job) {
32573074 .codegen_decl => |decl_index| {
32583075 const module = comp.bin_file.options.module.?;
......@@ -3404,7 +3221,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
34043221 const named_frame = tracy.namedFrame("glibc_crt_file");
34053222 defer named_frame.end();
34063223
3407 glibc.buildCRTFile(comp, crt_file) catch |err| {
3224 glibc.buildCRTFile(comp, crt_file, prog_node) catch |err| {
34083225 // TODO Surface more error details.
34093226 comp.lockAndSetMiscFailure(.glibc_crt_file, "unable to build glibc CRT file: {s}", .{
34103227 @errorName(err),
......@@ -3415,7 +3232,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
34153232 const named_frame = tracy.namedFrame("glibc_shared_objects");
34163233 defer named_frame.end();
34173234
3418 glibc.buildSharedObjects(comp) catch |err| {
3235 glibc.buildSharedObjects(comp, prog_node) catch |err| {
34193236 // TODO Surface more error details.
34203237 comp.lockAndSetMiscFailure(
34213238 .glibc_shared_objects,
......@@ -3428,7 +3245,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
34283245 const named_frame = tracy.namedFrame("musl_crt_file");
34293246 defer named_frame.end();
34303247
3431 musl.buildCRTFile(comp, crt_file) catch |err| {
3248 musl.buildCRTFile(comp, crt_file, prog_node) catch |err| {
34323249 // TODO Surface more error details.
34333250 comp.lockAndSetMiscFailure(
34343251 .musl_crt_file,
......@@ -3441,7 +3258,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
34413258 const named_frame = tracy.namedFrame("mingw_crt_file");
34423259 defer named_frame.end();
34433260
3444 mingw.buildCRTFile(comp, crt_file) catch |err| {
3261 mingw.buildCRTFile(comp, crt_file, prog_node) catch |err| {
34453262 // TODO Surface more error details.
34463263 comp.lockAndSetMiscFailure(
34473264 .mingw_crt_file,
......@@ -3468,7 +3285,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
34683285 const named_frame = tracy.namedFrame("libunwind");
34693286 defer named_frame.end();
34703287
3471 libunwind.buildStaticLib(comp) catch |err| {
3288 libunwind.buildStaticLib(comp, prog_node) catch |err| {
34723289 // TODO Surface more error details.
34733290 comp.lockAndSetMiscFailure(
34743291 .libunwind,
......@@ -3481,7 +3298,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
34813298 const named_frame = tracy.namedFrame("libcxx");
34823299 defer named_frame.end();
34833300
3484 libcxx.buildLibCXX(comp) catch |err| {
3301 libcxx.buildLibCXX(comp, prog_node) catch |err| {
34853302 // TODO Surface more error details.
34863303 comp.lockAndSetMiscFailure(
34873304 .libcxx,
......@@ -3494,7 +3311,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
34943311 const named_frame = tracy.namedFrame("libcxxabi");
34953312 defer named_frame.end();
34963313
3497 libcxx.buildLibCXXABI(comp) catch |err| {
3314 libcxx.buildLibCXXABI(comp, prog_node) catch |err| {
34983315 // TODO Surface more error details.
34993316 comp.lockAndSetMiscFailure(
35003317 .libcxxabi,
......@@ -3507,7 +3324,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
35073324 const named_frame = tracy.namedFrame("libtsan");
35083325 defer named_frame.end();
35093326
3510 libtsan.buildTsan(comp) catch |err| {
3327 libtsan.buildTsan(comp, prog_node) catch |err| {
35113328 // TODO Surface more error details.
35123329 comp.lockAndSetMiscFailure(
35133330 .libtsan,
......@@ -3520,7 +3337,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
35203337 const named_frame = tracy.namedFrame("wasi_libc_crt_file");
35213338 defer named_frame.end();
35223339
3523 wasi_libc.buildCRTFile(comp, crt_file) catch |err| {
3340 wasi_libc.buildCRTFile(comp, crt_file, prog_node) catch |err| {
35243341 // TODO Surface more error details.
35253342 comp.lockAndSetMiscFailure(
35263343 .wasi_libc_crt_file,
......@@ -3538,6 +3355,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
35383355 .Lib,
35393356 &comp.libssp_static_lib,
35403357 .libssp,
3358 prog_node,
35413359 ) catch |err| switch (err) {
35423360 error.OutOfMemory => return error.OutOfMemory,
35433361 error.SubCompilationFailed => return, // error reported already
......@@ -3557,6 +3375,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
35573375 .Lib,
35583376 &comp.libc_static_lib,
35593377 .zig_libc,
3378 prog_node,
35603379 ) catch |err| switch (err) {
35613380 error.OutOfMemory => return error.OutOfMemory,
35623381 error.SubCompilationFailed => return, // error reported already
......@@ -3897,8 +3716,15 @@ fn buildCompilerRtOneShot(
38973716 comp: *Compilation,
38983717 output_mode: std.builtin.OutputMode,
38993718 out: *?CRTFile,
3719 prog_node: *std.Progress.Node,
39003720) void {
3901 comp.buildOutputFromZig("compiler_rt.zig", output_mode, out, .compiler_rt) catch |err| switch (err) {
3721 comp.buildOutputFromZig(
3722 "compiler_rt.zig",
3723 output_mode,
3724 out,
3725 .compiler_rt,
3726 prog_node,
3727 ) catch |err| switch (err) {
39023728 error.SubCompilationFailed => return, // error reported already
39033729 else => comp.lockAndSetMiscFailure(
39043730 .compiler_rt,
......@@ -5230,7 +5056,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
52305056 \\const std = @import("std");
52315057 \\/// Zig version. When writing code that supports multiple versions of Zig, prefer
52325058 \\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks.
5233 \\pub const zig_version = std.SemanticVersion.parse("{s}") catch unreachable;
5059 \\pub const zig_version = std.SemanticVersion.parse(zig_version_string) catch unreachable;
5060 \\pub const zig_version_string = "{s}";
52345061 \\pub const zig_backend = std.builtin.CompilerBackend.{};
52355062 \\
52365063 \\pub const output_mode = std.builtin.OutputMode.{};
......@@ -5417,34 +5244,36 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
54175244 return buffer.toOwnedSliceSentinel(0);
54185245}
54195246
5420pub fn updateSubCompilation(sub_compilation: *Compilation) !void {
5421 try sub_compilation.update();
5422
5423 // Look for compilation errors in this sub_compilation
5424 // TODO instead of logging these errors, handle them in the callsites
5425 // of updateSubCompilation and attach them as sub-errors, properly
5426 // surfacing the errors. You can see an example of this already
5427 // done inside buildOutputFromZig.
5428 var errors = try sub_compilation.getAllErrorsAlloc();
5429 defer errors.deinit(sub_compilation.gpa);
5430
5431 if (errors.list.len != 0) {
5432 for (errors.list) |full_err_msg| {
5433 switch (full_err_msg) {
5434 .src => |src| {
5435 log.err("{s}:{d}:{d}: {s}", .{
5436 src.src_path,
5437 src.line + 1,
5438 src.column + 1,
5439 src.msg,
5440 });
5441 },
5442 .plain => |plain| {
5443 log.err("{s}", .{plain.msg});
5444 },
5445 }
5446 }
5447 return error.BuildingLibCObjectFailed;
5247pub fn updateSubCompilation(
5248 parent_comp: *Compilation,
5249 sub_comp: *Compilation,
5250 misc_task: MiscTask,
5251 prog_node: *std.Progress.Node,
5252) !void {
5253 {
5254 var sub_node = prog_node.start(@tagName(misc_task), 0);
5255 sub_node.activate();
5256 defer sub_node.end();
5257
5258 try sub_comp.update(prog_node);
5259 }
5260
5261 // Look for compilation errors in this sub compilation
5262 const gpa = parent_comp.gpa;
5263 var keep_errors = false;
5264 var errors = try sub_comp.getAllErrorsAlloc();
5265 defer if (!keep_errors) errors.deinit(gpa);
5266
5267 if (errors.errorMessageCount() > 0) {
5268 try parent_comp.misc_failures.ensureUnusedCapacity(gpa, 1);
5269 parent_comp.misc_failures.putAssumeCapacityNoClobber(misc_task, .{
5270 .msg = try std.fmt.allocPrint(gpa, "sub-compilation of {s} failed", .{
5271 @tagName(misc_task),
5272 }),
5273 .children = errors,
5274 });
5275 keep_errors = true;
5276 return error.SubCompilationFailed;
54485277 }
54495278}
54505279
......@@ -5454,6 +5283,7 @@ fn buildOutputFromZig(
54545283 output_mode: std.builtin.OutputMode,
54555284 out: *?CRTFile,
54565285 misc_task_tag: MiscTask,
5286 prog_node: *std.Progress.Node,
54575287) !void {
54585288 const tracy_trace = trace(@src());
54595289 defer tracy_trace.end();
......@@ -5520,23 +5350,7 @@ fn buildOutputFromZig(
55205350 });
55215351 defer sub_compilation.destroy();
55225352
5523 try sub_compilation.update();
5524 // Look for compilation errors in this sub_compilation.
5525 var keep_errors = false;
5526 var errors = try sub_compilation.getAllErrorsAlloc();
5527 defer if (!keep_errors) errors.deinit(sub_compilation.gpa);
5528
5529 if (errors.list.len != 0) {
5530 try comp.misc_failures.ensureUnusedCapacity(comp.gpa, 1);
5531 comp.misc_failures.putAssumeCapacityNoClobber(misc_task_tag, .{
5532 .msg = try std.fmt.allocPrint(comp.gpa, "sub-compilation of {s} failed", .{
5533 @tagName(misc_task_tag),
5534 }),
5535 .children = errors,
5536 });
5537 keep_errors = true;
5538 return error.SubCompilationFailed;
5539 }
5353 try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node);
55405354
55415355 assert(out.* == null);
55425356 out.* = Compilation.CRTFile{
......@@ -5551,6 +5365,8 @@ pub fn build_crt_file(
55515365 comp: *Compilation,
55525366 root_name: []const u8,
55535367 output_mode: std.builtin.OutputMode,
5368 misc_task_tag: MiscTask,
5369 prog_node: *std.Progress.Node,
55545370 c_source_files: []const Compilation.CSourceFile,
55555371) !void {
55565372 const tracy_trace = trace(@src());
......@@ -5611,7 +5427,7 @@ pub fn build_crt_file(
56115427 });
56125428 defer sub_compilation.destroy();
56135429
5614 try sub_compilation.updateSubCompilation();
5430 try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node);
56155431
56165432 try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1);
56175433
src/Module.zig+4-60
......@@ -3756,67 +3756,9 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
37563756 file.source_loaded = true;
37573757
37583758 file.tree = try Ast.parse(gpa, source, .zig);
3759 defer if (!file.tree_loaded) file.tree.deinit(gpa);
3760
3761 if (file.tree.errors.len != 0) {
3762 const parse_err = file.tree.errors[0];
3763
3764 var msg = std.ArrayList(u8).init(gpa);
3765 defer msg.deinit();
3766
3767 const token_starts = file.tree.tokens.items(.start);
3768 const token_tags = file.tree.tokens.items(.tag);
3769
3770 const extra_offset = file.tree.errorOffset(parse_err);
3771 try file.tree.renderError(parse_err, msg.writer());
3772 const err_msg = try gpa.create(ErrorMsg);
3773 err_msg.* = .{
3774 .src_loc = .{
3775 .file_scope = file,
3776 .parent_decl_node = 0,
3777 .lazy = if (extra_offset == 0) .{
3778 .token_abs = parse_err.token,
3779 } else .{
3780 .byte_abs = token_starts[parse_err.token] + extra_offset,
3781 },
3782 },
3783 .msg = try msg.toOwnedSlice(),
3784 };
3785 if (token_tags[parse_err.token + @boolToInt(parse_err.token_is_prev)] == .invalid) {
3786 const bad_off = @intCast(u32, file.tree.tokenSlice(parse_err.token + @boolToInt(parse_err.token_is_prev)).len);
3787 const byte_abs = token_starts[parse_err.token + @boolToInt(parse_err.token_is_prev)] + bad_off;
3788 try mod.errNoteNonLazy(.{
3789 .file_scope = file,
3790 .parent_decl_node = 0,
3791 .lazy = .{ .byte_abs = byte_abs },
3792 }, err_msg, "invalid byte: '{'}'", .{std.zig.fmtEscapes(source[byte_abs..][0..1])});
3793 }
3794
3795 for (file.tree.errors[1..]) |note| {
3796 if (!note.is_note) break;
3797
3798 try file.tree.renderError(note, msg.writer());
3799 err_msg.notes = try mod.gpa.realloc(err_msg.notes, err_msg.notes.len + 1);
3800 err_msg.notes[err_msg.notes.len - 1] = .{
3801 .src_loc = .{
3802 .file_scope = file,
3803 .parent_decl_node = 0,
3804 .lazy = .{ .token_abs = note.token },
3805 },
3806 .msg = try msg.toOwnedSlice(),
3807 };
3808 }
3809
3810 {
3811 comp.mutex.lock();
3812 defer comp.mutex.unlock();
3813 try mod.failed_files.putNoClobber(gpa, file, err_msg);
3814 }
3815 file.status = .parse_failure;
3816 return error.AnalysisFail;
3817 }
38183759 file.tree_loaded = true;
38193760
3761 // Any potential AST errors are converted to ZIR errors here.
38203762 file.zir = try AstGen.generate(gpa, file.tree);
38213763 file.zir_loaded = true;
38223764 file.status = .success_zir;
......@@ -3925,6 +3867,9 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {
39253867 const gpa = mod.gpa;
39263868 const new_zir = file.zir;
39273869
3870 // The root decl will be null if the previous ZIR had AST errors.
3871 const root_decl = file.root_decl.unwrap() orelse return;
3872
39283873 // Maps from old ZIR to new ZIR, struct_decl, enum_decl, etc. Any instruction which
39293874 // creates a namespace, gets mapped from old to new here.
39303875 var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
......@@ -3942,7 +3887,6 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {
39423887 var decl_stack: ArrayListUnmanaged(Decl.Index) = .{};
39433888 defer decl_stack.deinit(gpa);
39443889
3945 const root_decl = file.root_decl.unwrap().?;
39463890 try decl_stack.append(gpa, root_decl);
39473891
39483892 file.deleted_decls.clearRetainingCapacity();
src/Package.zig+53-57
......@@ -8,11 +8,11 @@ const Allocator = mem.Allocator;
88const assert = std.debug.assert;
99const log = std.log.scoped(.package);
1010const main = @import("main.zig");
11const ThreadPool = std.Thread.Pool;
12const WaitGroup = std.Thread.WaitGroup;
1113
1214const Compilation = @import("Compilation.zig");
1315const Module = @import("Module.zig");
14const ThreadPool = @import("ThreadPool.zig");
15const WaitGroup = @import("WaitGroup.zig");
1616const Cache = std.Build.Cache;
1717const build_options = @import("build_options");
1818const Manifest = @import("Manifest.zig");
......@@ -225,7 +225,7 @@ pub fn fetchAndAddDependencies(
225225 dependencies_source: *std.ArrayList(u8),
226226 build_roots_source: *std.ArrayList(u8),
227227 name_prefix: []const u8,
228 color: main.Color,
228 error_bundle: *std.zig.ErrorBundle.Wip,
229229 all_modules: *AllModules,
230230) !void {
231231 const max_bytes = 10 * 1024 * 1024;
......@@ -250,7 +250,7 @@ pub fn fetchAndAddDependencies(
250250
251251 if (ast.errors.len > 0) {
252252 const file_path = try directory.join(arena, &.{Manifest.basename});
253 try main.printErrsMsgToStdErr(gpa, arena, ast, file_path, color);
253 try main.putAstErrorsIntoBundle(gpa, ast, file_path, error_bundle);
254254 return error.PackageFetchFailed;
255255 }
256256
......@@ -258,14 +258,9 @@ pub fn fetchAndAddDependencies(
258258 defer manifest.deinit(gpa);
259259
260260 if (manifest.errors.len > 0) {
261 const ttyconf: std.debug.TTY.Config = switch (color) {
262 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
263 .on => .escape_codes,
264 .off => .no_color,
265 };
266261 const file_path = try directory.join(arena, &.{Manifest.basename});
267262 for (manifest.errors) |msg| {
268 Report.renderErrorMessage(ast, file_path, ttyconf, msg, &.{});
263 try Report.addErrorMessage(ast, file_path, error_bundle, 0, msg);
269264 }
270265 return error.PackageFetchFailed;
271266 }
......@@ -273,8 +268,7 @@ pub fn fetchAndAddDependencies(
273268 const report: Report = .{
274269 .ast = &ast,
275270 .directory = directory,
276 .color = color,
277 .arena = arena,
271 .error_bundle = error_bundle,
278272 };
279273
280274 var any_error = false;
......@@ -307,7 +301,7 @@ pub fn fetchAndAddDependencies(
307301 dependencies_source,
308302 build_roots_source,
309303 sub_prefix,
310 color,
304 error_bundle,
311305 all_modules,
312306 );
313307
......@@ -350,8 +344,7 @@ pub fn createFilePkg(
350344const Report = struct {
351345 ast: *const std.zig.Ast,
352346 directory: Compilation.Directory,
353 color: main.Color,
354 arena: Allocator,
347 error_bundle: *std.zig.ErrorBundle.Wip,
355348
356349 fn fail(
357350 report: Report,
......@@ -359,52 +352,46 @@ const Report = struct {
359352 comptime fmt_string: []const u8,
360353 fmt_args: anytype,
361354 ) error{ PackageFetchFailed, OutOfMemory } {
362 return failWithNotes(report, &.{}, tok, fmt_string, fmt_args);
363 }
355 const gpa = report.error_bundle.gpa;
364356
365 fn failWithNotes(
366 report: Report,
367 notes: []const Compilation.AllErrors.Message,
368 tok: std.zig.Ast.TokenIndex,
369 comptime fmt_string: []const u8,
370 fmt_args: anytype,
371 ) error{ PackageFetchFailed, OutOfMemory } {
372 const ttyconf: std.debug.TTY.Config = switch (report.color) {
373 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
374 .on => .escape_codes,
375 .off => .no_color,
376 };
377 const file_path = try report.directory.join(report.arena, &.{Manifest.basename});
378 renderErrorMessage(report.ast.*, file_path, ttyconf, .{
357 const file_path = try report.directory.join(gpa, &.{Manifest.basename});
358 defer gpa.free(file_path);
359
360 const msg = try std.fmt.allocPrint(gpa, fmt_string, fmt_args);
361 defer gpa.free(msg);
362
363 try addErrorMessage(report.ast.*, file_path, report.error_bundle, 0, .{
379364 .tok = tok,
380365 .off = 0,
381 .msg = try std.fmt.allocPrint(report.arena, fmt_string, fmt_args),
382 }, notes);
366 .msg = msg,
367 });
368
383369 return error.PackageFetchFailed;
384370 }
385371
386 fn renderErrorMessage(
372 fn addErrorMessage(
387373 ast: std.zig.Ast,
388374 file_path: []const u8,
389 ttyconf: std.debug.TTY.Config,
375 eb: *std.zig.ErrorBundle.Wip,
376 notes_len: u32,
390377 msg: Manifest.ErrorMessage,
391 notes: []const Compilation.AllErrors.Message,
392 ) void {
378 ) error{OutOfMemory}!void {
393379 const token_starts = ast.tokens.items(.start);
394380 const start_loc = ast.tokenLocation(0, msg.tok);
395 Compilation.AllErrors.Message.renderToStdErr(.{ .src = .{
396 .msg = msg.msg,
397 .src_path = file_path,
398 .line = @intCast(u32, start_loc.line),
399 .column = @intCast(u32, start_loc.column),
400 .span = .{
401 .start = token_starts[msg.tok],
402 .end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
403 .main = token_starts[msg.tok] + msg.off,
404 },
405 .source_line = ast.source[start_loc.line_start..start_loc.line_end],
406 .notes = notes,
407 } }, ttyconf);
381
382 try eb.addRootErrorMessage(.{
383 .msg = try eb.addString(msg.msg),
384 .src_loc = try eb.addSourceLocation(.{
385 .src_path = try eb.addString(file_path),
386 .span_start = token_starts[msg.tok],
387 .span_end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
388 .span_main = token_starts[msg.tok] + msg.off,
389 .line = @intCast(u32, start_loc.line),
390 .column = @intCast(u32, start_loc.column),
391 .source_line = try eb.addString(ast.source[start_loc.line_start..start_loc.line_end]),
392 }),
393 .notes_len = notes_len,
394 });
408395 }
409396};
410397
......@@ -504,9 +491,7 @@ fn fetchAndUnpack(
504491 // by default, so the same logic applies for buffering the reader as for gzip.
505492 try unpackTarball(gpa, &req, tmp_directory.handle, std.compress.xz);
506493 } else {
507 return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{
508 uri.path,
509 });
494 return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{uri.path});
510495 }
511496
512497 // TODO: delete files not included in the package prior to computing the package hash.
......@@ -533,10 +518,21 @@ fn fetchAndUnpack(
533518 });
534519 }
535520 } else {
536 const notes: [1]Compilation.AllErrors.Message = .{.{ .plain = .{
537 .msg = try std.fmt.allocPrint(report.arena, "expected .hash = \"{s}\",", .{&actual_hex}),
538 } }};
539 return report.failWithNotes(&notes, dep.url_tok, "url field is missing corresponding hash field", .{});
521 const file_path = try report.directory.join(gpa, &.{Manifest.basename});
522 defer gpa.free(file_path);
523
524 const eb = report.error_bundle;
525 const notes_len = 1;
526 try Report.addErrorMessage(report.ast.*, file_path, eb, notes_len, .{
527 .tok = dep.url_tok,
528 .off = 0,
529 .msg = "url field is missing corresponding hash field",
530 });
531 const notes_start = try eb.reserveNotes(notes_len);
532 eb.extra.items[notes_start] = @enumToInt(try eb.addErrorMessage(.{
533 .msg = try eb.printString("expected .hash = \"{s}\",", .{&actual_hex}),
534 }));
535 return error.PackageFetchFailed;
540536 }
541537
542538 const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});
src/Sema.zig+12-14
......@@ -2211,29 +2211,27 @@ pub fn fail(
22112211
22122212fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
22132213 @setCold(true);
2214 const gpa = sema.gpa;
22142215
22152216 if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) {
22162217 if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation;
2217 var arena = std.heap.ArenaAllocator.init(sema.gpa);
2218 errdefer arena.deinit();
2219 var errors = std.ArrayList(Compilation.AllErrors.Message).init(sema.gpa);
2220 defer errors.deinit();
2221
2222 Compilation.AllErrors.add(sema.mod, &arena, &errors, err_msg.*) catch unreachable;
2223
2218 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
2219 wip_errors.init(gpa) catch unreachable;
2220 Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable;
22242221 std.debug.print("compile error during Sema:\n", .{});
2225 Compilation.AllErrors.Message.renderToStdErr(errors.items[0], .no_color);
2222 var error_bundle = wip_errors.toOwnedBundle("") catch unreachable;
2223 error_bundle.renderToStdErr(.{ .ttyconf = .no_color });
22262224 crash_report.compilerPanic("unexpected compile error occurred", null, null);
22272225 }
22282226
22292227 const mod = sema.mod;
22302228 ref: {
2231 errdefer err_msg.destroy(mod.gpa);
2229 errdefer err_msg.destroy(gpa);
22322230 if (err_msg.src_loc.lazy == .unneeded) {
22332231 return error.NeededSourceLocation;
22342232 }
2235 try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1);
2236 try mod.failed_files.ensureUnusedCapacity(mod.gpa, 1);
2233 try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
2234 try mod.failed_files.ensureUnusedCapacity(gpa, 1);
22372235
22382236 const max_references = blk: {
22392237 if (sema.mod.comp.reference_trace) |num| break :blk num;
......@@ -2243,11 +2241,11 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
22432241 };
22442242
22452243 var referenced_by = if (sema.func) |some| some.owner_decl else sema.owner_decl_index;
2246 var reference_stack = std.ArrayList(Module.ErrorMsg.Trace).init(sema.gpa);
2244 var reference_stack = std.ArrayList(Module.ErrorMsg.Trace).init(gpa);
22472245 defer reference_stack.deinit();
22482246
22492247 // Avoid infinite loops.
2250 var seen = std.AutoHashMap(Module.Decl.Index, void).init(sema.gpa);
2248 var seen = std.AutoHashMap(Module.Decl.Index, void).init(gpa);
22512249 defer seen.deinit();
22522250
22532251 var cur_reference_trace: u32 = 0;
......@@ -2288,7 +2286,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
22882286 if (gop.found_existing) {
22892287 // If there are multiple errors for the same Decl, prefer the first one added.
22902288 sema.err = null;
2291 err_msg.destroy(mod.gpa);
2289 err_msg.destroy(gpa);
22922290 } else {
22932291 sema.err = err_msg;
22942292 gop.value_ptr.* = err_msg;
src/ThreadPool.zig deleted-152
......@@ -1,152 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const ThreadPool = @This();
4const WaitGroup = @import("WaitGroup.zig");
5
6mutex: std.Thread.Mutex = .{},
7cond: std.Thread.Condition = .{},
8run_queue: RunQueue = .{},
9is_running: bool = true,
10allocator: std.mem.Allocator,
11threads: []std.Thread,
12
13const RunQueue = std.SinglyLinkedList(Runnable);
14const Runnable = struct {
15 runFn: RunProto,
16};
17
18const RunProto = *const fn (*Runnable) void;
19
20pub fn init(pool: *ThreadPool, allocator: std.mem.Allocator) !void {
21 pool.* = .{
22 .allocator = allocator,
23 .threads = &[_]std.Thread{},
24 };
25
26 if (builtin.single_threaded) {
27 return;
28 }
29
30 const thread_count = std.math.max(1, std.Thread.getCpuCount() catch 1);
31 pool.threads = try allocator.alloc(std.Thread, thread_count);
32 errdefer allocator.free(pool.threads);
33
34 // kill and join any threads we spawned previously on error.
35 var spawned: usize = 0;
36 errdefer pool.join(spawned);
37
38 for (pool.threads) |*thread| {
39 thread.* = try std.Thread.spawn(.{}, worker, .{pool});
40 spawned += 1;
41 }
42}
43
44pub fn deinit(pool: *ThreadPool) void {
45 pool.join(pool.threads.len); // kill and join all threads.
46 pool.* = undefined;
47}
48
49fn join(pool: *ThreadPool, spawned: usize) void {
50 if (builtin.single_threaded) {
51 return;
52 }
53
54 {
55 pool.mutex.lock();
56 defer pool.mutex.unlock();
57
58 // ensure future worker threads exit the dequeue loop
59 pool.is_running = false;
60 }
61
62 // wake up any sleeping threads (this can be done outside the mutex)
63 // then wait for all the threads we know are spawned to complete.
64 pool.cond.broadcast();
65 for (pool.threads[0..spawned]) |thread| {
66 thread.join();
67 }
68
69 pool.allocator.free(pool.threads);
70}
71
72pub fn spawn(pool: *ThreadPool, comptime func: anytype, args: anytype) !void {
73 if (builtin.single_threaded) {
74 @call(.auto, func, args);
75 return;
76 }
77
78 const Args = @TypeOf(args);
79 const Closure = struct {
80 arguments: Args,
81 pool: *ThreadPool,
82 run_node: RunQueue.Node = .{ .data = .{ .runFn = runFn } },
83
84 fn runFn(runnable: *Runnable) void {
85 const run_node = @fieldParentPtr(RunQueue.Node, "data", runnable);
86 const closure = @fieldParentPtr(@This(), "run_node", run_node);
87 @call(.auto, func, closure.arguments);
88
89 // The thread pool's allocator is protected by the mutex.
90 const mutex = &closure.pool.mutex;
91 mutex.lock();
92 defer mutex.unlock();
93
94 closure.pool.allocator.destroy(closure);
95 }
96 };
97
98 {
99 pool.mutex.lock();
100 defer pool.mutex.unlock();
101
102 const closure = try pool.allocator.create(Closure);
103 closure.* = .{
104 .arguments = args,
105 .pool = pool,
106 };
107
108 pool.run_queue.prepend(&closure.run_node);
109 }
110
111 // Notify waiting threads outside the lock to try and keep the critical section small.
112 pool.cond.signal();
113}
114
115fn worker(pool: *ThreadPool) void {
116 pool.mutex.lock();
117 defer pool.mutex.unlock();
118
119 while (true) {
120 while (pool.run_queue.popFirst()) |run_node| {
121 // Temporarily unlock the mutex in order to execute the run_node
122 pool.mutex.unlock();
123 defer pool.mutex.lock();
124
125 const runFn = run_node.data.runFn;
126 runFn(&run_node.data);
127 }
128
129 // Stop executing instead of waiting if the thread pool is no longer running.
130 if (pool.is_running) {
131 pool.cond.wait(&pool.mutex);
132 } else {
133 break;
134 }
135 }
136}
137
138pub fn waitAndWork(pool: *ThreadPool, wait_group: *WaitGroup) void {
139 while (!wait_group.isDone()) {
140 if (blk: {
141 pool.mutex.lock();
142 defer pool.mutex.unlock();
143 break :blk pool.run_queue.popFirst();
144 }) |run_node| {
145 run_node.data.runFn(&run_node.data);
146 continue;
147 }
148
149 wait_group.wait();
150 return;
151 }
152}
src/WaitGroup.zig deleted-46
......@@ -1,46 +0,0 @@
1const std = @import("std");
2const Atomic = std.atomic.Atomic;
3const assert = std.debug.assert;
4const WaitGroup = @This();
5
6const is_waiting: usize = 1 << 0;
7const one_pending: usize = 1 << 1;
8
9state: Atomic(usize) = Atomic(usize).init(0),
10event: std.Thread.ResetEvent = .{},
11
12pub fn start(self: *WaitGroup) void {
13 const state = self.state.fetchAdd(one_pending, .Monotonic);
14 assert((state / one_pending) < (std.math.maxInt(usize) / one_pending));
15}
16
17pub fn finish(self: *WaitGroup) void {
18 const state = self.state.fetchSub(one_pending, .Release);
19 assert((state / one_pending) > 0);
20
21 if (state == (one_pending | is_waiting)) {
22 self.state.fence(.Acquire);
23 self.event.set();
24 }
25}
26
27pub fn wait(self: *WaitGroup) void {
28 var state = self.state.fetchAdd(is_waiting, .Acquire);
29 assert(state & is_waiting == 0);
30
31 if ((state / one_pending) > 0) {
32 self.event.wait();
33 }
34}
35
36pub fn reset(self: *WaitGroup) void {
37 self.state.store(0, .Monotonic);
38 self.event.reset();
39}
40
41pub fn isDone(wg: *WaitGroup) bool {
42 const state = wg.state.load(.Acquire);
43 assert(state & is_waiting == 0);
44
45 return (state / one_pending) == 0;
46}
src/Zir.zig+6
......@@ -3594,6 +3594,12 @@ pub const Inst = struct {
35943594 /// 0 or a payload index of a `Block`, each is a payload
35953595 /// index of another `Item`.
35963596 notes: u32,
3597
3598 pub fn notesLen(item: Item, zir: Zir) u32 {
3599 if (item.notes == 0) return 0;
3600 const block = zir.extraData(Block, item.notes);
3601 return block.data.body_len;
3602 }
35973603 };
35983604 };
35993605
src/glibc.zig+11-8
......@@ -161,7 +161,7 @@ pub const CRTFile = enum {
161161 libc_nonshared_a,
162162};
163163
164pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
164pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
165165 if (!build_options.have_llvm) {
166166 return error.ZigCompilerNotBuiltWithLLVMExtensions;
167167 }
......@@ -196,7 +196,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
196196 "-DASSEMBLER",
197197 "-Wa,--noexecstack",
198198 });
199 return comp.build_crt_file("crti", .Obj, &[1]Compilation.CSourceFile{
199 return comp.build_crt_file("crti", .Obj, .@"glibc crti.o", prog_node, &.{
200200 .{
201201 .src_path = try start_asm_path(comp, arena, "crti.S"),
202202 .cache_exempt_flags = args.items,
......@@ -215,7 +215,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
215215 "-DASSEMBLER",
216216 "-Wa,--noexecstack",
217217 });
218 return comp.build_crt_file("crtn", .Obj, &[1]Compilation.CSourceFile{
218 return comp.build_crt_file("crtn", .Obj, .@"glibc crtn.o", prog_node, &.{
219219 .{
220220 .src_path = try start_asm_path(comp, arena, "crtn.S"),
221221 .cache_exempt_flags = args.items,
......@@ -265,7 +265,9 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
265265 .cache_exempt_flags = args.items,
266266 };
267267 };
268 return comp.build_crt_file("Scrt1", .Obj, &[_]Compilation.CSourceFile{ start_o, abi_note_o });
268 return comp.build_crt_file("Scrt1", .Obj, .@"glibc Scrt1.o", prog_node, &.{
269 start_o, abi_note_o,
270 });
269271 },
270272 .libc_nonshared_a => {
271273 const s = path.sep_str;
......@@ -366,7 +368,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
366368 files_index += 1;
367369 }
368370 const files = files_buf[0..files_index];
369 return comp.build_crt_file("c_nonshared", .Lib, files);
371 return comp.build_crt_file("c_nonshared", .Lib, .@"glibc libc_nonshared.a", prog_node, files);
370372 },
371373 }
372374}
......@@ -639,7 +641,7 @@ pub const BuiltSharedObjects = struct {
639641
640642const all_map_basename = "all.map";
641643
642pub fn buildSharedObjects(comp: *Compilation) !void {
644pub fn buildSharedObjects(comp: *Compilation, prog_node: *std.Progress.Node) !void {
643645 const tracy = trace(@src());
644646 defer tracy.end();
645647
......@@ -1023,7 +1025,7 @@ pub fn buildSharedObjects(comp: *Compilation) !void {
10231025 const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;
10241026 try o_directory.handle.writeFile(asm_file_basename, stubs_asm.items);
10251027
1026 try buildSharedLib(comp, arena, comp.global_cache_directory, o_directory, asm_file_basename, lib);
1028 try buildSharedLib(comp, arena, comp.global_cache_directory, o_directory, asm_file_basename, lib, prog_node);
10271029 }
10281030
10291031 man.writeManifest() catch |err| {
......@@ -1046,6 +1048,7 @@ fn buildSharedLib(
10461048 bin_directory: Compilation.Directory,
10471049 asm_file_basename: []const u8,
10481050 lib: Lib,
1051 prog_node: *std.Progress.Node,
10491052) !void {
10501053 const tracy = trace(@src());
10511054 defer tracy.end();
......@@ -1105,7 +1108,7 @@ fn buildSharedLib(
11051108 });
11061109 defer sub_compilation.destroy();
11071110
1108 try sub_compilation.updateSubCompilation();
1111 try comp.updateSubCompilation(sub_compilation, .@"glibc shared object", prog_node);
11091112}
11101113
11111114// Return true if glibc has crti/crtn sources for that architecture.
src/libcxx.zig+4-4
......@@ -96,7 +96,7 @@ const libcxx_files = [_][]const u8{
9696 "src/verbose_abort.cpp",
9797};
9898
99pub fn buildLibCXX(comp: *Compilation) !void {
99pub fn buildLibCXX(comp: *Compilation, prog_node: *std.Progress.Node) !void {
100100 if (!build_options.have_llvm) {
101101 return error.ZigCompilerNotBuiltWithLLVMExtensions;
102102 }
......@@ -258,7 +258,7 @@ pub fn buildLibCXX(comp: *Compilation) !void {
258258 });
259259 defer sub_compilation.destroy();
260260
261 try sub_compilation.updateSubCompilation();
261 try comp.updateSubCompilation(sub_compilation, .libcxx, prog_node);
262262
263263 assert(comp.libcxx_static_lib == null);
264264 comp.libcxx_static_lib = Compilation.CRTFile{
......@@ -269,7 +269,7 @@ pub fn buildLibCXX(comp: *Compilation) !void {
269269 };
270270}
271271
272pub fn buildLibCXXABI(comp: *Compilation) !void {
272pub fn buildLibCXXABI(comp: *Compilation, prog_node: *std.Progress.Node) !void {
273273 if (!build_options.have_llvm) {
274274 return error.ZigCompilerNotBuiltWithLLVMExtensions;
275275 }
......@@ -418,7 +418,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void {
418418 });
419419 defer sub_compilation.destroy();
420420
421 try sub_compilation.updateSubCompilation();
421 try comp.updateSubCompilation(sub_compilation, .libcxxabi, prog_node);
422422
423423 assert(comp.libcxxabi_static_lib == null);
424424 comp.libcxxabi_static_lib = Compilation.CRTFile{
src/libtsan.zig+2-2
......@@ -5,7 +5,7 @@ const Compilation = @import("Compilation.zig");
55const build_options = @import("build_options");
66const trace = @import("tracy.zig").trace;
77
8pub fn buildTsan(comp: *Compilation) !void {
8pub fn buildTsan(comp: *Compilation, prog_node: *std.Progress.Node) !void {
99 if (!build_options.have_llvm) {
1010 return error.ZigCompilerNotBuiltWithLLVMExtensions;
1111 }
......@@ -235,7 +235,7 @@ pub fn buildTsan(comp: *Compilation) !void {
235235 });
236236 defer sub_compilation.destroy();
237237
238 try sub_compilation.updateSubCompilation();
238 try comp.updateSubCompilation(sub_compilation, .libtsan, prog_node);
239239
240240 assert(comp.tsan_static_lib == null);
241241 comp.tsan_static_lib = Compilation.CRTFile{
src/libunwind.zig+2-2
......@@ -7,7 +7,7 @@ const Compilation = @import("Compilation.zig");
77const build_options = @import("build_options");
88const trace = @import("tracy.zig").trace;
99
10pub fn buildStaticLib(comp: *Compilation) !void {
10pub fn buildStaticLib(comp: *Compilation, prog_node: *std.Progress.Node) !void {
1111 if (!build_options.have_llvm) {
1212 return error.ZigCompilerNotBuiltWithLLVMExtensions;
1313 }
......@@ -130,7 +130,7 @@ pub fn buildStaticLib(comp: *Compilation) !void {
130130 });
131131 defer sub_compilation.destroy();
132132
133 try sub_compilation.updateSubCompilation();
133 try comp.updateSubCompilation(sub_compilation, .libunwind, prog_node);
134134
135135 assert(comp.libunwind_static_lib == null);
136136
src/link.zig+40-3
......@@ -264,6 +264,8 @@ pub const File = struct {
264264 /// of this linking operation.
265265 lock: ?Cache.Lock = null,
266266
267 child_pid: ?std.ChildProcess.Id = null,
268
267269 /// Attempts incremental linking, if the file already exists. If
268270 /// incremental linking fails, falls back to truncating the file and
269271 /// rewriting it. A malicious file is detected as incremental link failure
......@@ -376,6 +378,26 @@ pub const File = struct {
376378 if (build_options.only_c) unreachable;
377379 if (base.file != null) return;
378380 const emit = base.options.emit orelse return;
381 if (base.child_pid) |pid| {
382 // If we try to open the output file in write mode while it is running,
383 // it will return ETXTBSY. So instead, we copy the file, atomically rename it
384 // over top of the exe path, and then proceed normally. This changes the inode,
385 // avoiding the error.
386 const tmp_sub_path = try std.fmt.allocPrint(base.allocator, "{s}-{x}", .{
387 emit.sub_path, std.crypto.random.int(u32),
388 });
389 try emit.directory.handle.copyFile(emit.sub_path, emit.directory.handle, tmp_sub_path, .{});
390 try emit.directory.handle.rename(tmp_sub_path, emit.sub_path);
391 switch (builtin.os.tag) {
392 .linux => {
393 switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0, 0))) {
394 .SUCCESS => {},
395 else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}),
396 }
397 },
398 else => return error.HotSwapUnavailableOnHostOperatingSystem,
399 }
400 }
379401 base.file = try emit.directory.handle.createFile(emit.sub_path, .{
380402 .truncate = false,
381403 .read = true,
......@@ -424,6 +446,18 @@ pub const File = struct {
424446 }
425447 f.close();
426448 base.file = null;
449
450 if (base.child_pid) |pid| {
451 switch (builtin.os.tag) {
452 .linux => {
453 switch (std.os.errno(std.os.linux.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0, 0))) {
454 .SUCCESS => {},
455 else => |errno| log.warn("ptrace failure: {s}", .{@tagName(errno)}),
456 }
457 },
458 else => return error.HotSwapUnavailableOnHostOperatingSystem,
459 }
460 }
427461 },
428462 .c, .spirv, .nvptx => {},
429463 }
......@@ -462,6 +496,7 @@ pub const File = struct {
462496 NetNameDeleted,
463497 DeviceBusy,
464498 InvalidArgument,
499 HotSwapUnavailableOnHostOperatingSystem,
465500 };
466501
467502 /// Called from within the CodeGen to lower a local variable instantion as an unnamed
......@@ -1053,9 +1088,11 @@ pub const File = struct {
10531088 log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)});
10541089 };
10551090
1056 man.writeManifest() catch |err| {
1057 log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)});
1058 };
1091 if (man.have_exclusive_lock) {
1092 man.writeManifest() catch |err| {
1093 log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)});
1094 };
1095 }
10591096
10601097 base.lock = man.toOwnedLock();
10611098 }
src/link/Elf.zig+50-5
......@@ -467,7 +467,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
467467 .p_paddr = entry_addr,
468468 .p_memsz = file_size,
469469 .p_align = p_align,
470 .p_flags = elf.PF_X | elf.PF_R,
470 .p_flags = elf.PF_X | elf.PF_R | elf.PF_W,
471471 });
472472 self.entry_addr = null;
473473 self.phdr_table_dirty = true;
......@@ -493,7 +493,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
493493 .p_paddr = got_addr,
494494 .p_memsz = file_size,
495495 .p_align = p_align,
496 .p_flags = elf.PF_R,
496 .p_flags = elf.PF_R | elf.PF_W,
497497 });
498498 self.phdr_table_dirty = true;
499499 }
......@@ -516,7 +516,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
516516 .p_paddr = rodata_addr,
517517 .p_memsz = file_size,
518518 .p_align = p_align,
519 .p_flags = elf.PF_R,
519 .p_flags = elf.PF_R | elf.PF_W,
520520 });
521521 self.phdr_table_dirty = true;
522522 }
......@@ -2166,7 +2166,7 @@ fn allocateAtom(self: *Elf, atom_index: Atom.Index, new_block_size: u64, alignme
21662166 // First we look for an appropriately sized free list node.
21672167 // The list is unordered. We'll just take the first thing that works.
21682168 const vaddr = blk: {
2169 var i: usize = 0;
2169 var i: usize = if (self.base.child_pid == null) 0 else free_list.items.len;
21702170 while (i < free_list.items.len) {
21712171 const big_atom_index = free_list.items[i];
21722172 const big_atom = self.getAtom(big_atom_index);
......@@ -2397,7 +2397,7 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s
23972397 const atom = self.getAtom(atom_index);
23982398
23992399 const shdr_index = decl_metadata.shdr;
2400 if (atom.getSymbol(self).st_size != 0) {
2400 if (atom.getSymbol(self).st_size != 0 and self.base.child_pid == null) {
24012401 const local_sym = atom.getSymbolPtr(self);
24022402 local_sym.st_name = try self.shstrtab.insert(gpa, decl_name);
24032403 local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits;
......@@ -2451,6 +2451,28 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s
24512451 const phdr_index = self.sections.items(.phdr_index)[shdr_index];
24522452 const section_offset = local_sym.st_value - self.program_headers.items[phdr_index].p_vaddr;
24532453 const file_offset = self.sections.items(.shdr)[shdr_index].sh_offset + section_offset;
2454
2455 if (self.base.child_pid) |pid| {
2456 switch (builtin.os.tag) {
2457 .linux => {
2458 var code_vec: [1]std.os.iovec_const = .{.{
2459 .iov_base = code.ptr,
2460 .iov_len = code.len,
2461 }};
2462 var remote_vec: [1]std.os.iovec_const = .{.{
2463 .iov_base = @intToPtr([*]u8, @intCast(usize, local_sym.st_value)),
2464 .iov_len = code.len,
2465 }};
2466 const rc = std.os.linux.process_vm_writev(pid, &code_vec, &remote_vec, 0);
2467 switch (std.os.errno(rc)) {
2468 .SUCCESS => assert(rc == code.len),
2469 else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}),
2470 }
2471 },
2472 else => return error.HotSwapUnavailableOnHostOperatingSystem,
2473 }
2474 }
2475
24542476 try self.base.file.?.pwriteAll(code, file_offset);
24552477
24562478 return local_sym;
......@@ -2820,6 +2842,8 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
28202842 const endian = self.base.options.target.cpu.arch.endian();
28212843 const shdr = &self.sections.items(.shdr)[self.got_section_index.?];
28222844 const off = shdr.sh_offset + @as(u64, entry_size) * index;
2845 const phdr = &self.program_headers.items[self.phdr_got_index.?];
2846 const vaddr = phdr.p_vaddr + @as(u64, entry_size) * index;
28232847 switch (entry_size) {
28242848 2 => {
28252849 var buf: [2]u8 = undefined;
......@@ -2835,6 +2859,27 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
28352859 var buf: [8]u8 = undefined;
28362860 mem.writeInt(u64, &buf, self.offset_table.items[index], endian);
28372861 try self.base.file.?.pwriteAll(&buf, off);
2862
2863 if (self.base.child_pid) |pid| {
2864 switch (builtin.os.tag) {
2865 .linux => {
2866 var local_vec: [1]std.os.iovec_const = .{.{
2867 .iov_base = &buf,
2868 .iov_len = buf.len,
2869 }};
2870 var remote_vec: [1]std.os.iovec_const = .{.{
2871 .iov_base = @intToPtr([*]u8, @intCast(usize, vaddr)),
2872 .iov_len = buf.len,
2873 }};
2874 const rc = std.os.linux.process_vm_writev(pid, &local_vec, &remote_vec, 0);
2875 switch (std.os.errno(rc)) {
2876 .SUCCESS => assert(rc == buf.len),
2877 else => |errno| log.warn("process_vm_writev failure: {s}", .{@tagName(errno)}),
2878 }
2879 },
2880 else => return error.HotSwapUnavailableOnHostOperatingSystem,
2881 }
2882 }
28382883 },
28392884 else => unreachable,
28402885 }
src/link/MachO/CodeSignature.zig+2-2
......@@ -7,12 +7,12 @@ const log = std.log.scoped(.link);
77const macho = std.macho;
88const mem = std.mem;
99const testing = std.testing;
10const ThreadPool = std.Thread.Pool;
11const WaitGroup = std.Thread.WaitGroup;
1012
1113const Allocator = mem.Allocator;
1214const Compilation = @import("../../Compilation.zig");
1315const Sha256 = std.crypto.hash.sha2.Sha256;
14const ThreadPool = @import("../../ThreadPool.zig");
15const WaitGroup = @import("../../WaitGroup.zig");
1616
1717const hash_size = Sha256.digest_length;
1818
src/main.zig+495-337
......@@ -9,6 +9,8 @@ const Allocator = mem.Allocator;
99const ArrayList = std.ArrayList;
1010const Ast = std.zig.Ast;
1111const warn = std.log.warn;
12const ThreadPool = std.Thread.Pool;
13const cleanExit = std.process.cleanExit;
1214
1315const tracy = @import("tracy.zig");
1416const Compilation = @import("Compilation.zig");
......@@ -22,8 +24,10 @@ const translate_c = @import("translate_c.zig");
2224const clang = @import("clang.zig");
2325const Cache = std.Build.Cache;
2426const target_util = @import("target.zig");
25const ThreadPool = @import("ThreadPool.zig");
2627const crash_report = @import("crash_report.zig");
28const Module = @import("Module.zig");
29const AstGen = @import("AstGen.zig");
30const Server = std.zig.Server;
2731
2832pub const std_options = struct {
2933 pub const wasiCwd = wasi_cwd;
......@@ -361,7 +365,6 @@ const usage_build_generic =
361365 \\
362366 \\General Options:
363367 \\ -h, --help Print this help and exit
364 \\ --watch Enable compiler REPL
365368 \\ --color [auto|off|on] Enable or disable colored error messages
366369 \\ -femit-bin[=path] (default) Output machine code
367370 \\ -fno-emit-bin Do not output machine code
......@@ -666,6 +669,16 @@ const ArgMode = union(enum) {
666669 run,
667670};
668671
672/// Avoid dragging networking into zig2.c because it adds dependencies on some
673/// linker symbols that are annoying to satisfy while bootstrapping.
674const Ip4Address = if (build_options.omit_pkg_fetching_code) void else std.net.Ip4Address;
675
676const Listen = union(enum) {
677 none,
678 ip4: Ip4Address,
679 stdio,
680};
681
669682fn buildOutputType(
670683 gpa: Allocator,
671684 arena: Allocator,
......@@ -686,7 +699,7 @@ fn buildOutputType(
686699 var formatted_panics: ?bool = null;
687700 var function_sections = false;
688701 var no_builtin = false;
689 var watch = false;
702 var listen: Listen = .none;
690703 var debug_compile_errors = false;
691704 var verbose_link = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_LINK");
692705 var verbose_cc = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_CC");
......@@ -1144,6 +1157,23 @@ fn buildOutputType(
11441157 } else {
11451158 try log_scopes.append(gpa, args_iter.nextOrFatal());
11461159 }
1160 } else if (mem.eql(u8, arg, "--listen")) {
1161 const next_arg = args_iter.nextOrFatal();
1162 if (mem.eql(u8, next_arg, "-")) {
1163 listen = .stdio;
1164 } else {
1165 if (build_options.omit_pkg_fetching_code) unreachable;
1166 // example: --listen 127.0.0.1:9000
1167 var it = std.mem.split(u8, next_arg, ":");
1168 const host = it.next().?;
1169 const port_text = it.next() orelse "14735";
1170 const port = std.fmt.parseInt(u16, port_text, 10) catch |err|
1171 fatal("invalid port number: '{s}': {s}", .{ port_text, @errorName(err) });
1172 listen = .{ .ip4 = std.net.Ip4Address.parse(host, port) catch |err|
1173 fatal("invalid host: '{s}': {s}", .{ host, @errorName(err) }) };
1174 }
1175 } else if (mem.eql(u8, arg, "--listen=-")) {
1176 listen = .stdio;
11471177 } else if (mem.eql(u8, arg, "--debug-link-snapshot")) {
11481178 if (!build_options.enable_link_snapshots) {
11491179 std.log.warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{});
......@@ -1172,8 +1202,6 @@ fn buildOutputType(
11721202 test_evented_io = true;
11731203 } else if (mem.eql(u8, arg, "--test-no-exec")) {
11741204 test_no_exec = true;
1175 } else if (mem.eql(u8, arg, "--watch")) {
1176 watch = true;
11771205 } else if (mem.eql(u8, arg, "-ftime-report")) {
11781206 time_report = true;
11791207 } else if (mem.eql(u8, arg, "-fstack-report")) {
......@@ -2999,7 +3027,7 @@ fn buildOutputType(
29993027 defer zig_lib_directory.handle.close();
30003028
30013029 var thread_pool: ThreadPool = undefined;
3002 try thread_pool.init(gpa);
3030 try thread_pool.init(.{ .allocator = gpa });
30033031 defer thread_pool.deinit();
30043032
30053033 var libc_installation: ?LibCInstallation = null;
......@@ -3259,8 +3287,52 @@ fn buildOutputType(
32593287 if (show_builtin) {
32603288 return std.io.getStdOut().writeAll(try comp.generateBuiltinZigSource(arena));
32613289 }
3290 switch (listen) {
3291 .none => {},
3292 .stdio => {
3293 if (build_options.only_c) unreachable;
3294 try serve(
3295 comp,
3296 std.io.getStdIn(),
3297 std.io.getStdOut(),
3298 test_exec_args.items,
3299 self_exe_path,
3300 arg_mode,
3301 all_args,
3302 runtime_args_start,
3303 );
3304 return cleanExit();
3305 },
3306 .ip4 => |ip4_addr| {
3307 if (build_options.omit_pkg_fetching_code) unreachable;
3308
3309 var server = std.net.StreamServer.init(.{
3310 .reuse_address = true,
3311 });
3312 defer server.deinit();
3313
3314 try server.listen(.{ .in = ip4_addr });
3315
3316 while (true) {
3317 const conn = try server.accept();
3318 defer conn.stream.close();
3319
3320 try serve(
3321 comp,
3322 .{ .handle = conn.stream.handle },
3323 .{ .handle = conn.stream.handle },
3324 test_exec_args.items,
3325 self_exe_path,
3326 arg_mode,
3327 all_args,
3328 runtime_args_start,
3329 );
3330 }
3331 },
3332 }
3333
32623334 if (arg_mode == .translate_c) {
3263 return cmdTranslateC(comp, arena, have_enable_cache);
3335 return cmdTranslateC(comp, arena, null);
32643336 }
32653337
32663338 const hook: AfterUpdateHook = blk: {
......@@ -3276,7 +3348,7 @@ fn buildOutputType(
32763348 };
32773349
32783350 updateModule(gpa, comp, hook) catch |err| switch (err) {
3279 error.SemanticAnalyzeFail => if (!watch) process.exit(1),
3351 error.SemanticAnalyzeFail => if (listen == .none) process.exit(1),
32803352 else => |e| return e,
32813353 };
32823354 if (build_options.only_c) return cleanExit();
......@@ -3332,7 +3404,6 @@ fn buildOutputType(
33323404 self_exe_path.?,
33333405 arg_mode,
33343406 target_info,
3335 watch,
33363407 &comp_destroyed,
33373408 all_args,
33383409 runtime_args_start,
......@@ -3340,109 +3411,215 @@ fn buildOutputType(
33403411 );
33413412 }
33423413
3343 const stdin = std.io.getStdIn().reader();
3344 const stderr = std.io.getStdErr().writer();
3345 var repl_buf: [1024]u8 = undefined;
3414 // Skip resource deallocation in release builds; let the OS do it.
3415 return cleanExit();
3416}
3417
3418fn serve(
3419 comp: *Compilation,
3420 in: fs.File,
3421 out: fs.File,
3422 test_exec_args: []const ?[]const u8,
3423 self_exe_path: ?[]const u8,
3424 arg_mode: ArgMode,
3425 all_args: []const []const u8,
3426 runtime_args_start: ?usize,
3427) !void {
3428 const gpa = comp.gpa;
33463429
3347 const ReplCmd = enum {
3348 update,
3349 help,
3350 run,
3351 update_and_run,
3430 var server = try Server.init(.{
3431 .gpa = gpa,
3432 .in = in,
3433 .out = out,
3434 .zig_version = build_options.version,
3435 });
3436 defer server.deinit();
3437
3438 var child_pid: ?std.ChildProcess.Id = null;
3439
3440 var progress: std.Progress = .{
3441 .terminal = null,
3442 .root = .{
3443 .context = undefined,
3444 .parent = null,
3445 .name = "",
3446 .unprotected_estimated_total_items = 0,
3447 .unprotected_completed_items = 0,
3448 },
3449 .columns_written = 0,
3450 .prev_refresh_timestamp = 0,
3451 .timer = null,
3452 .done = false,
33523453 };
3454 const main_progress_node = &progress.root;
3455 main_progress_node.context = &progress;
33533456
3354 var last_cmd: ReplCmd = .help;
3457 while (true) {
3458 const hdr = try server.receiveMessage();
33553459
3356 while (watch) {
3357 try stderr.print("(zig) ", .{});
3358 try comp.makeBinFileExecutable();
3359 if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| {
3360 try stderr.print("\nUnable to parse command: {s}\n", .{@errorName(err)});
3361 continue;
3362 }) |line| {
3363 const actual_line = mem.trimRight(u8, line, "\r\n ");
3364 const cmd: ReplCmd = blk: {
3365 if (mem.eql(u8, actual_line, "update")) {
3366 break :blk .update;
3367 } else if (mem.eql(u8, actual_line, "exit")) {
3368 break;
3369 } else if (mem.eql(u8, actual_line, "help")) {
3370 break :blk .help;
3371 } else if (mem.eql(u8, actual_line, "run")) {
3372 break :blk .run;
3373 } else if (mem.eql(u8, actual_line, "update-and-run")) {
3374 break :blk .update_and_run;
3375 } else if (actual_line.len == 0) {
3376 break :blk last_cmd;
3377 } else {
3378 try stderr.print("unknown command: {s}\n", .{actual_line});
3460 switch (hdr.tag) {
3461 .exit => {
3462 return cleanExit();
3463 },
3464 .update => {
3465 assert(main_progress_node.recently_updated_child == null);
3466 tracy.frameMark();
3467
3468 if (arg_mode == .translate_c) {
3469 var arena_instance = std.heap.ArenaAllocator.init(gpa);
3470 defer arena_instance.deinit();
3471 const arena = arena_instance.allocator();
3472 var output: TranslateCOutput = undefined;
3473 try cmdTranslateC(comp, arena, &output);
3474 try server.serveEmitBinPath(output.path, .{
3475 .flags = .{ .cache_hit = output.cache_hit },
3476 });
33793477 continue;
33803478 }
3381 };
3382 last_cmd = cmd;
3383 switch (cmd) {
3384 .update => {
3385 tracy.frameMark();
3386 if (output_mode == .Exe) {
3387 try comp.makeBinFileWritable();
3479
3480 if (comp.bin_file.options.output_mode == .Exe) {
3481 try comp.makeBinFileWritable();
3482 }
3483
3484 {
3485 var reset: std.Thread.ResetEvent = .{};
3486
3487 var progress_thread = try std.Thread.spawn(.{}, progressThread, .{
3488 &progress, &server, &reset,
3489 });
3490 defer {
3491 reset.set();
3492 progress_thread.join();
33883493 }
3389 updateModule(gpa, comp, hook) catch |err| switch (err) {
3390 error.SemanticAnalyzeFail => continue,
3391 else => |e| return e,
3392 };
3393 },
3394 .help => {
3395 try stderr.writeAll(repl_help);
3396 },
3397 .run => {
3398 tracy.frameMark();
3399 try runOrTest(
3400 comp,
3401 gpa,
3402 arena,
3403 test_exec_args.items,
3404 self_exe_path.?,
3405 arg_mode,
3406 target_info,
3407 watch,
3408 &comp_destroyed,
3409 all_args,
3410 runtime_args_start,
3411 link_libc,
3412 );
3413 },
3414 .update_and_run => {
3415 tracy.frameMark();
3416 if (output_mode == .Exe) {
3494
3495 try comp.update(main_progress_node);
3496 }
3497
3498 try comp.makeBinFileExecutable();
3499 try serveUpdateResults(&server, comp);
3500 },
3501 .run => {
3502 if (child_pid != null) {
3503 @panic("TODO block until the child exits");
3504 }
3505 @panic("TODO call runOrTest");
3506 //try runOrTest(
3507 // comp,
3508 // gpa,
3509 // arena,
3510 // test_exec_args,
3511 // self_exe_path.?,
3512 // arg_mode,
3513 // target_info,
3514 // true,
3515 // &comp_destroyed,
3516 // all_args,
3517 // runtime_args_start,
3518 // link_libc,
3519 //);
3520 },
3521 .hot_update => {
3522 tracy.frameMark();
3523 assert(main_progress_node.recently_updated_child == null);
3524 if (child_pid) |pid| {
3525 try comp.hotCodeSwap(main_progress_node, pid);
3526 try serveUpdateResults(&server, comp);
3527 } else {
3528 if (comp.bin_file.options.output_mode == .Exe) {
34173529 try comp.makeBinFileWritable();
34183530 }
3419 updateModule(gpa, comp, hook) catch |err| switch (err) {
3420 error.SemanticAnalyzeFail => continue,
3421 else => |e| return e,
3422 };
3531 try comp.update(main_progress_node);
34233532 try comp.makeBinFileExecutable();
3424 try runOrTest(
3533 try serveUpdateResults(&server, comp);
3534
3535 child_pid = try runOrTestHotSwap(
34253536 comp,
34263537 gpa,
3427 arena,
3428 test_exec_args.items,
3538 test_exec_args,
34293539 self_exe_path.?,
34303540 arg_mode,
3431 target_info,
3432 watch,
3433 &comp_destroyed,
34343541 all_args,
34353542 runtime_args_start,
3436 link_libc,
34373543 );
3438 },
3544 }
3545 },
3546 else => {
3547 fatal("unrecognized message from client: 0x{x}", .{@enumToInt(hdr.tag)});
3548 },
3549 }
3550 }
3551}
3552
3553fn progressThread(progress: *std.Progress, server: *const Server, reset: *std.Thread.ResetEvent) void {
3554 while (true) {
3555 if (reset.timedWait(500 * std.time.ns_per_ms)) |_| {
3556 // The Compilation update has completed.
3557 return;
3558 } else |err| switch (err) {
3559 error.Timeout => {},
3560 }
3561
3562 var buf: std.BoundedArray(u8, 160) = .{};
3563
3564 {
3565 progress.update_mutex.lock();
3566 defer progress.update_mutex.unlock();
3567
3568 var need_ellipse = false;
3569 var maybe_node: ?*std.Progress.Node = &progress.root;
3570 while (maybe_node) |node| {
3571 if (need_ellipse) {
3572 buf.appendSlice("... ") catch {};
3573 }
3574 need_ellipse = false;
3575 const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .Monotonic);
3576 const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .Monotonic);
3577 const current_item = completed_items + 1;
3578 if (node.name.len != 0 or eti > 0) {
3579 if (node.name.len != 0) {
3580 buf.appendSlice(node.name) catch {};
3581 need_ellipse = true;
3582 }
3583 if (eti > 0) {
3584 if (need_ellipse) buf.appendSlice(" ") catch {};
3585 buf.writer().print("[{d}/{d}] ", .{ current_item, eti }) catch {};
3586 need_ellipse = false;
3587 } else if (completed_items != 0) {
3588 if (need_ellipse) buf.appendSlice(" ") catch {};
3589 buf.writer().print("[{d}] ", .{current_item}) catch {};
3590 need_ellipse = false;
3591 }
3592 }
3593 maybe_node = @atomicLoad(?*std.Progress.Node, &node.recently_updated_child, .Acquire);
34393594 }
3440 } else {
3441 break;
34423595 }
3596
3597 const progress_string = buf.slice();
3598
3599 server.serveMessage(.{
3600 .tag = .progress,
3601 .bytes_len = @intCast(u32, progress_string.len),
3602 }, &.{
3603 progress_string,
3604 }) catch |err| {
3605 fatal("unable to write to client: {s}", .{@errorName(err)});
3606 };
3607 }
3608}
3609
3610fn serveUpdateResults(s: *Server, comp: *Compilation) !void {
3611 const gpa = comp.gpa;
3612 var error_bundle = try comp.getAllErrorsAlloc();
3613 defer error_bundle.deinit(gpa);
3614 if (error_bundle.errorMessageCount() > 0) {
3615 try s.serveErrorBundle(error_bundle);
3616 } else if (comp.bin_file.options.emit) |emit| {
3617 const full_path = try emit.directory.join(gpa, &.{emit.sub_path});
3618 defer gpa.free(full_path);
3619 try s.serveEmitBinPath(full_path, .{
3620 .flags = .{ .cache_hit = comp.last_update_was_cache_hit },
3621 });
34433622 }
3444 // Skip resource deallocation in release builds; let the OS do it.
3445 return cleanExit();
34463623}
34473624
34483625const ModuleDepIterator = struct {
......@@ -3530,7 +3707,6 @@ fn runOrTest(
35303707 self_exe_path: []const u8,
35313708 arg_mode: ArgMode,
35323709 target_info: std.zig.system.NativeTargetInfo,
3533 watch: bool,
35343710 comp_destroyed: *bool,
35353711 all_args: []const []const u8,
35363712 runtime_args_start: ?usize,
......@@ -3561,7 +3737,7 @@ fn runOrTest(
35613737
35623738 // We do not execve for tests because if the test fails we want to print
35633739 // the error message and invocation below.
3564 if (std.process.can_execv and arg_mode == .run and !watch) {
3740 if (std.process.can_execv and arg_mode == .run) {
35653741 // execv releases the locks; no need to destroy the Compilation here.
35663742 const err = std.process.execve(gpa, argv.items, &env_map);
35673743 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
......@@ -3574,12 +3750,10 @@ fn runOrTest(
35743750 child.stdout_behavior = .Inherit;
35753751 child.stderr_behavior = .Inherit;
35763752
3577 if (!watch) {
3578 // Here we release all the locks associated with the Compilation so
3579 // that whatever this child process wants to do won't deadlock.
3580 comp.destroy();
3581 comp_destroyed.* = true;
3582 }
3753 // Here we release all the locks associated with the Compilation so
3754 // that whatever this child process wants to do won't deadlock.
3755 comp.destroy();
3756 comp_destroyed.* = true;
35833757
35843758 const term = child.spawnAndWait() catch |err| {
35853759 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
......@@ -3591,19 +3765,13 @@ fn runOrTest(
35913765 switch (term) {
35923766 .Exited => |code| {
35933767 if (code == 0) {
3594 if (!watch) return cleanExit();
3595 } else if (watch) {
3596 warn("process exited with code {d}", .{code});
3768 return cleanExit();
35973769 } else {
35983770 process.exit(code);
35993771 }
36003772 },
36013773 else => {
3602 if (watch) {
3603 warn("process aborted abnormally", .{});
3604 } else {
3605 process.exit(1);
3606 }
3774 process.exit(1);
36073775 },
36083776 }
36093777 },
......@@ -3611,7 +3779,7 @@ fn runOrTest(
36113779 switch (term) {
36123780 .Exited => |code| {
36133781 if (code == 0) {
3614 if (!watch) return cleanExit();
3782 return cleanExit();
36153783 } else {
36163784 const cmd = try std.mem.join(arena, " ", argv.items);
36173785 fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd });
......@@ -3631,6 +3799,62 @@ fn runOrTest(
36313799 }
36323800}
36333801
3802fn runOrTestHotSwap(
3803 comp: *Compilation,
3804 gpa: Allocator,
3805 test_exec_args: []const ?[]const u8,
3806 self_exe_path: []const u8,
3807 arg_mode: ArgMode,
3808 all_args: []const []const u8,
3809 runtime_args_start: ?usize,
3810) !std.ChildProcess.Id {
3811 const exe_emit = comp.bin_file.options.emit.?;
3812 // A naive `directory.join` here will indeed get the correct path to the binary,
3813 // however, in the case of cwd, we actually want `./foo` so that the path can be executed.
3814 const exe_path = try fs.path.join(gpa, &[_][]const u8{
3815 exe_emit.directory.path orelse ".", exe_emit.sub_path,
3816 });
3817 defer gpa.free(exe_path);
3818
3819 var argv = std.ArrayList([]const u8).init(gpa);
3820 defer argv.deinit();
3821
3822 if (test_exec_args.len == 0) {
3823 // when testing pass the zig_exe_path to argv
3824 if (arg_mode == .zig_test)
3825 try argv.appendSlice(&[_][]const u8{
3826 exe_path, self_exe_path,
3827 })
3828 // when running just pass the current exe
3829 else
3830 try argv.appendSlice(&[_][]const u8{
3831 exe_path,
3832 });
3833 } else {
3834 for (test_exec_args) |arg| {
3835 if (arg) |a| {
3836 try argv.append(a);
3837 } else {
3838 try argv.appendSlice(&[_][]const u8{
3839 exe_path, self_exe_path,
3840 });
3841 }
3842 }
3843 }
3844 if (runtime_args_start) |i| {
3845 try argv.appendSlice(all_args[i..]);
3846 }
3847 var child = std.ChildProcess.init(argv.items, gpa);
3848
3849 child.stdin_behavior = .Inherit;
3850 child.stdout_behavior = .Inherit;
3851 child.stderr_behavior = .Inherit;
3852
3853 try child.spawn();
3854
3855 return child.id;
3856}
3857
36343858const AfterUpdateHook = union(enum) {
36353859 none,
36363860 print_emit_bin_dir_path,
......@@ -3638,24 +3862,30 @@ const AfterUpdateHook = union(enum) {
36383862};
36393863
36403864fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void {
3641 try comp.update();
3865 {
3866 // If the terminal is dumb, we dont want to show the user all the output.
3867 var progress: std.Progress = .{ .dont_print_on_dumb = true };
3868 const main_progress_node = progress.start("", 0);
3869 defer main_progress_node.end();
3870 switch (comp.color) {
3871 .off => {
3872 progress.terminal = null;
3873 },
3874 .on => {
3875 progress.terminal = std.io.getStdErr();
3876 progress.supports_ansi_escape_codes = true;
3877 },
3878 .auto => {},
3879 }
3880
3881 try comp.update(main_progress_node);
3882 }
36423883
36433884 var errors = try comp.getAllErrorsAlloc();
36443885 defer errors.deinit(comp.gpa);
36453886
3646 if (errors.list.len != 0) {
3647 const ttyconf: std.debug.TTY.Config = switch (comp.color) {
3648 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
3649 .on => .escape_codes,
3650 .off => .no_color,
3651 };
3652 for (errors.list) |full_err_msg| {
3653 full_err_msg.renderToStdErr(ttyconf);
3654 }
3655 const log_text = comp.getCompileLogOutput();
3656 if (log_text.len != 0) {
3657 std.debug.print("\nCompile Log Output:\n{s}", .{log_text});
3658 }
3887 if (errors.errorMessageCount() > 0) {
3888 errors.renderToStdErr(renderOptions(comp.color));
36593889 return error.SemanticAnalyzeFail;
36603890 } else switch (hook) {
36613891 .none => {},
......@@ -3697,7 +3927,12 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void
36973927 }
36983928}
36993929
3700fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void {
3930const TranslateCOutput = struct {
3931 path: []const u8,
3932 cache_hit: bool,
3933};
3934
3935fn cmdTranslateC(comp: *Compilation, arena: Allocator, fancy_output: ?*TranslateCOutput) !void {
37013936 if (!build_options.have_llvm)
37023937 fatal("cannot translate-c: compiler built without LLVM extensions", .{});
37033938
......@@ -3708,14 +3943,16 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void
37083943
37093944 var man: Cache.Manifest = comp.obtainCObjectCacheManifest();
37103945 man.want_shared_lock = false;
3711 defer if (enable_cache) man.deinit();
3946 defer man.deinit();
37123947
37133948 man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects
37143949 Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err| {
37153950 fatal("unable to process '{s}': {s}", .{ c_source_file.src_path, @errorName(err) });
37163951 };
37173952
3953 if (fancy_output) |p| p.cache_hit = true;
37183954 const digest = if (try man.hit()) man.final() else digest: {
3955 if (fancy_output) |p| p.cache_hit = false;
37193956 var argv = std.ArrayList([]const u8).init(arena);
37203957 try argv.append(""); // argv[0] is program name, actual args start at [1]
37213958
......@@ -3766,6 +4003,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void
37664003 error.OutOfMemory => return error.OutOfMemory,
37674004 error.ASTUnitFailure => fatal("clang API returned errors but due to a clang bug, it is not exposing the errors for zig to see. For more details: https://github.com/ziglang/zig/issues/4455", .{}),
37684005 error.SemanticAnalyzeFail => {
4006 // TODO convert these to zig errors
37694007 for (clang_errors) |clang_err| {
37704008 std.debug.print("{s}:{d}:{d}: {s}\n", .{
37714009 if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)",
......@@ -3810,12 +4048,11 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void
38104048 break :digest digest;
38114049 };
38124050
3813 if (enable_cache) {
4051 if (fancy_output) |p| {
38144052 const full_zig_path = try comp.local_cache_directory.join(arena, &[_][]const u8{
38154053 "o", &digest, translated_zig_basename,
38164054 });
3817 try io.getStdOut().writer().print("{s}\n", .{full_zig_path});
3818 return cleanExit();
4055 p.path = full_zig_path;
38194056 } else {
38204057 const out_zig_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename });
38214058 const zig_file = comp.local_cache_directory.handle.openFile(out_zig_path, .{}) catch |err| {
......@@ -4009,6 +4246,8 @@ pub const usage_build =
40094246 \\Options:
40104247 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
40114248 \\ -fno-reference-trace Disable reference trace
4249 \\ -fsummary Print the build summary, even on success
4250 \\ -fno-summary Omit the build summary, even on failure
40124251 \\ --build-file [file] Override path to build.zig
40134252 \\ --cache-dir [path] Override path to local Zig cache directory
40144253 \\ --global-cache-dir [path] Override path to global Zig cache directory
......@@ -4021,7 +4260,6 @@ pub const usage_build =
40214260
40224261pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
40234262 var color: Color = .auto;
4024 var prominent_compile_errors: bool = false;
40254263
40264264 // We want to release all the locks before executing the child process, so we make a nice
40274265 // big block here to ensure the cleanup gets run when we extract out our argv.
......@@ -4082,8 +4320,6 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
40824320 i += 1;
40834321 override_global_cache_dir = args[i];
40844322 continue;
4085 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
4086 prominent_compile_errors = true;
40874323 } else if (mem.eql(u8, arg, "-freference-trace")) {
40884324 try child_argv.append(arg);
40894325 reference_trace = 256;
......@@ -4201,7 +4437,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
42014437 .basename = exe_basename,
42024438 };
42034439 var thread_pool: ThreadPool = undefined;
4204 try thread_pool.init(gpa);
4440 try thread_pool.init(.{ .allocator = gpa });
42054441 defer thread_pool.deinit();
42064442
42074443 var cleanup_build_runner_dir: ?fs.Dir = null;
......@@ -4251,9 +4487,13 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
42514487 var all_modules: Package.AllModules = .{};
42524488 defer all_modules.deinit(gpa);
42534489
4490 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
4491 try wip_errors.init(gpa);
4492 defer wip_errors.deinit();
4493
42544494 // Here we borrow main package's table and will replace it with a fresh
42554495 // one after this process completes.
4256 build_pkg.fetchAndAddDependencies(
4496 const fetch_result = build_pkg.fetchAndAddDependencies(
42574497 &main_pkg,
42584498 arena,
42594499 &thread_pool,
......@@ -4264,12 +4504,16 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
42644504 &dependencies_source,
42654505 &build_roots_source,
42664506 "",
4267 color,
4507 &wip_errors,
42684508 &all_modules,
4269 ) catch |err| switch (err) {
4270 error.PackageFetchFailed => process.exit(1),
4271 else => |e| return e,
4272 };
4509 );
4510 if (wip_errors.root_list.items.len > 0) {
4511 var errors = try wip_errors.toOwnedBundle("");
4512 defer errors.deinit(gpa);
4513 errors.renderToStdErr(renderOptions(color));
4514 process.exit(1);
4515 }
4516 try fetch_result;
42734517
42744518 try dependencies_source.appendSlice("};\npub const build_root = struct {\n");
42754519 try dependencies_source.appendSlice(build_roots_source.items);
......@@ -4312,7 +4556,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
43124556 defer comp.destroy();
43134557
43144558 updateModule(gpa, comp, .none) catch |err| switch (err) {
4315 error.SemanticAnalyzeFail => process.exit(1),
4559 error.SemanticAnalyzeFail => process.exit(2),
43164560 else => |e| return e,
43174561 };
43184562 try comp.makeBinFileExecutable();
......@@ -4336,13 +4580,13 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
43364580 switch (term) {
43374581 .Exited => |code| {
43384582 if (code == 0) return cleanExit();
4583 // Indicates that the build runner has reported compile errors
4584 // and this parent process does not need to report any further
4585 // diagnostics.
4586 if (code == 2) process.exit(2);
43394587
4340 if (prominent_compile_errors) {
4341 fatal("the build command failed with exit code {d}", .{code});
4342 } else {
4343 const cmd = try std.mem.join(arena, " ", child_argv);
4344 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
4345 }
4588 const cmd = try std.mem.join(arena, " ", child_argv);
4589 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
43464590 },
43474591 else => {
43484592 const cmd = try std.mem.join(arena, " ", child_argv);
......@@ -4356,7 +4600,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
43564600}
43574601
43584602fn readSourceFileToEndAlloc(
4359 allocator: mem.Allocator,
4603 allocator: Allocator,
43604604 input: *const fs.File,
43614605 size_hint: ?usize,
43624606) ![:0]u8 {
......@@ -4500,12 +4744,7 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
45004744 };
45014745 defer tree.deinit(gpa);
45024746
4503 try printErrsMsgToStdErr(gpa, arena, tree, "<stdin>", color);
4504 var has_ast_error = false;
45054747 if (check_ast_flag) {
4506 const Module = @import("Module.zig");
4507 const AstGen = @import("AstGen.zig");
4508
45094748 var file: Module.File = .{
45104749 .status = .never_loaded,
45114750 .source_loaded = true,
......@@ -4528,25 +4767,18 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
45284767 defer file.zir.deinit(gpa);
45294768
45304769 if (file.zir.hasCompileErrors()) {
4531 var arena_instance = std.heap.ArenaAllocator.init(gpa);
4532 defer arena_instance.deinit();
4533 var errors = std.ArrayList(Compilation.AllErrors.Message).init(gpa);
4534 defer errors.deinit();
4535
4536 try Compilation.AllErrors.addZir(arena_instance.allocator(), &errors, &file);
4537 const ttyconf: std.debug.TTY.Config = switch (color) {
4538 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
4539 .on => .escape_codes,
4540 .off => .no_color,
4541 };
4542 for (errors.items) |full_err_msg| {
4543 full_err_msg.renderToStdErr(ttyconf);
4544 }
4545 has_ast_error = true;
4770 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
4771 try wip_errors.init(gpa);
4772 defer wip_errors.deinit();
4773 try Compilation.addZirErrorMessages(&wip_errors, &file);
4774 var error_bundle = try wip_errors.toOwnedBundle("");
4775 defer error_bundle.deinit(gpa);
4776 error_bundle.renderToStdErr(renderOptions(color));
4777 process.exit(2);
45464778 }
4547 }
4548 if (tree.errors.len != 0 or has_ast_error) {
4549 process.exit(1);
4779 } else if (tree.errors.len != 0) {
4780 try printAstErrorsToStderr(gpa, tree, "<stdin>", color);
4781 process.exit(2);
45504782 }
45514783 const formatted = try tree.render(gpa);
45524784 defer gpa.free(formatted);
......@@ -4688,12 +4920,13 @@ fn fmtPathFile(
46884920 if (stat.kind == .Directory)
46894921 return error.IsDir;
46904922
4923 const gpa = fmt.gpa;
46914924 const source_code = try readSourceFileToEndAlloc(
4692 fmt.gpa,
4925 gpa,
46934926 &source_file,
46944927 std.math.cast(usize, stat.size) orelse return error.FileTooBig,
46954928 );
4696 defer fmt.gpa.free(source_code);
4929 defer gpa.free(source_code);
46974930
46984931 source_file.close();
46994932 file_closed = true;
......@@ -4701,19 +4934,16 @@ fn fmtPathFile(
47014934 // Add to set after no longer possible to get error.IsDir.
47024935 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
47034936
4704 var tree = try Ast.parse(fmt.gpa, source_code, .zig);
4705 defer tree.deinit(fmt.gpa);
4937 var tree = try Ast.parse(gpa, source_code, .zig);
4938 defer tree.deinit(gpa);
47064939
4707 try printErrsMsgToStdErr(fmt.gpa, fmt.arena, tree, file_path, fmt.color);
47084940 if (tree.errors.len != 0) {
4941 try printAstErrorsToStderr(gpa, tree, file_path, fmt.color);
47094942 fmt.any_error = true;
47104943 return;
47114944 }
47124945
47134946 if (fmt.check_ast) {
4714 const Module = @import("Module.zig");
4715 const AstGen = @import("AstGen.zig");
4716
47174947 var file: Module.File = .{
47184948 .status = .never_loaded,
47194949 .source_loaded = true,
......@@ -4732,31 +4962,24 @@ fn fmtPathFile(
47324962 .root_decl = .none,
47334963 };
47344964
4735 file.pkg = try Package.create(fmt.gpa, null, file.sub_file_path);
4736 defer file.pkg.destroy(fmt.gpa);
4965 file.pkg = try Package.create(gpa, null, file.sub_file_path);
4966 defer file.pkg.destroy(gpa);
47374967
47384968 if (stat.size > max_src_size)
47394969 return error.FileTooBig;
47404970
4741 file.zir = try AstGen.generate(fmt.gpa, file.tree);
4971 file.zir = try AstGen.generate(gpa, file.tree);
47424972 file.zir_loaded = true;
4743 defer file.zir.deinit(fmt.gpa);
4973 defer file.zir.deinit(gpa);
47444974
47454975 if (file.zir.hasCompileErrors()) {
4746 var arena_instance = std.heap.ArenaAllocator.init(fmt.gpa);
4747 defer arena_instance.deinit();
4748 var errors = std.ArrayList(Compilation.AllErrors.Message).init(fmt.gpa);
4749 defer errors.deinit();
4750
4751 try Compilation.AllErrors.addZir(arena_instance.allocator(), &errors, &file);
4752 const ttyconf: std.debug.TTY.Config = switch (fmt.color) {
4753 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
4754 .on => .escape_codes,
4755 .off => .no_color,
4756 };
4757 for (errors.items) |full_err_msg| {
4758 full_err_msg.renderToStdErr(ttyconf);
4759 }
4976 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
4977 try wip_errors.init(gpa);
4978 defer wip_errors.deinit();
4979 try Compilation.addZirErrorMessages(&wip_errors, &file);
4980 var error_bundle = try wip_errors.toOwnedBundle("");
4981 defer error_bundle.deinit(gpa);
4982 error_bundle.renderToStdErr(renderOptions(fmt.color));
47604983 fmt.any_error = true;
47614984 }
47624985 }
......@@ -4784,100 +5007,50 @@ fn fmtPathFile(
47845007 }
47855008}
47865009
4787pub fn printErrsMsgToStdErr(
4788 gpa: mem.Allocator,
4789 arena: mem.Allocator,
5010fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {
5011 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5012 try wip_errors.init(gpa);
5013 defer wip_errors.deinit();
5014
5015 try putAstErrorsIntoBundle(gpa, tree, path, &wip_errors);
5016
5017 var error_bundle = try wip_errors.toOwnedBundle("");
5018 defer error_bundle.deinit(gpa);
5019 error_bundle.renderToStdErr(renderOptions(color));
5020}
5021
5022pub fn putAstErrorsIntoBundle(
5023 gpa: Allocator,
47905024 tree: Ast,
47915025 path: []const u8,
4792 color: Color,
5026 wip_errors: *std.zig.ErrorBundle.Wip,
47935027) !void {
4794 const parse_errors: []const Ast.Error = tree.errors;
4795 var i: usize = 0;
4796 while (i < parse_errors.len) : (i += 1) {
4797 const parse_error = parse_errors[i];
4798 const lok_token = parse_error.token;
4799 const token_tags = tree.tokens.items(.tag);
4800 const start_loc = tree.tokenLocation(0, lok_token);
4801 const source_line = tree.source[start_loc.line_start..start_loc.line_end];
4802
4803 var text_buf = std.ArrayList(u8).init(gpa);
4804 defer text_buf.deinit();
4805 const writer = text_buf.writer();
4806 try tree.renderError(parse_error, writer);
4807 const text = try arena.dupe(u8, text_buf.items);
4808
4809 var notes_buffer: [2]Compilation.AllErrors.Message = undefined;
4810 var notes_len: usize = 0;
4811
4812 if (token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)] == .invalid) {
4813 const bad_off = @intCast(u32, tree.tokenSlice(parse_error.token + @boolToInt(parse_error.token_is_prev)).len);
4814 const byte_offset = @intCast(u32, start_loc.line_start) + @intCast(u32, start_loc.column) + bad_off;
4815 notes_buffer[notes_len] = .{
4816 .src = .{
4817 .src_path = path,
4818 .msg = try std.fmt.allocPrint(arena, "invalid byte: '{'}'", .{
4819 std.zig.fmtEscapes(tree.source[byte_offset..][0..1]),
4820 }),
4821 .span = .{ .start = byte_offset, .end = byte_offset + 1, .main = byte_offset },
4822 .line = @intCast(u32, start_loc.line),
4823 .column = @intCast(u32, start_loc.column) + bad_off,
4824 .source_line = source_line,
4825 },
4826 };
4827 notes_len += 1;
4828 }
4829
4830 for (parse_errors[i + 1 ..]) |note| {
4831 if (!note.is_note) break;
4832
4833 text_buf.items.len = 0;
4834 try tree.renderError(note, writer);
4835 const note_loc = tree.tokenLocation(0, note.token);
4836 const byte_offset = @intCast(u32, note_loc.line_start);
4837 notes_buffer[notes_len] = .{
4838 .src = .{
4839 .src_path = path,
4840 .msg = try arena.dupe(u8, text_buf.items),
4841 .span = .{
4842 .start = byte_offset,
4843 .end = byte_offset + @intCast(u32, tree.tokenSlice(note.token).len),
4844 .main = byte_offset,
4845 },
4846 .line = @intCast(u32, note_loc.line),
4847 .column = @intCast(u32, note_loc.column),
4848 .source_line = tree.source[note_loc.line_start..note_loc.line_end],
4849 },
4850 };
4851 i += 1;
4852 notes_len += 1;
4853 }
5028 var file: Module.File = .{
5029 .status = .never_loaded,
5030 .source_loaded = true,
5031 .zir_loaded = false,
5032 .sub_file_path = path,
5033 .source = tree.source,
5034 .stat = .{
5035 .size = 0,
5036 .inode = 0,
5037 .mtime = 0,
5038 },
5039 .tree = tree,
5040 .tree_loaded = true,
5041 .zir = undefined,
5042 .pkg = undefined,
5043 .root_decl = .none,
5044 };
48545045
4855 const extra_offset = tree.errorOffset(parse_error);
4856 const byte_offset = @intCast(u32, start_loc.line_start) + extra_offset;
4857 const message: Compilation.AllErrors.Message = .{
4858 .src = .{
4859 .src_path = path,
4860 .msg = text,
4861 .span = .{
4862 .start = byte_offset,
4863 .end = byte_offset + @intCast(u32, tree.tokenSlice(lok_token).len),
4864 .main = byte_offset,
4865 },
4866 .line = @intCast(u32, start_loc.line),
4867 .column = @intCast(u32, start_loc.column) + extra_offset,
4868 .source_line = source_line,
4869 .notes = notes_buffer[0..notes_len],
4870 },
4871 };
5046 file.pkg = try Package.create(gpa, null, path);
5047 defer file.pkg.destroy(gpa);
48725048
4873 const ttyconf: std.debug.TTY.Config = switch (color) {
4874 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
4875 .on => .escape_codes,
4876 .off => .no_color,
4877 };
5049 file.zir = try AstGen.generate(gpa, file.tree);
5050 file.zir_loaded = true;
5051 defer file.zir.deinit(gpa);
48785052
4879 message.renderToStdErr(ttyconf);
4880 }
5053 try Compilation.addZirErrorMessages(wip_errors, &file);
48815054}
48825055
48835056pub const info_zen =
......@@ -5325,19 +5498,6 @@ fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.Nat
53255498 return std.zig.system.NativeTargetInfo.detect(cross_target);
53265499}
53275500
5328/// Indicate that we are now terminating with a successful exit code.
5329/// In debug builds, this is a no-op, so that the calling code's
5330/// cleanup mechanisms are tested and so that external tools that
5331/// check for resource leaks can be accurate. In release builds, this
5332/// calls exit(0), and does not return.
5333pub fn cleanExit() void {
5334 if (builtin.mode == .Debug) {
5335 return;
5336 } else {
5337 process.exit(0);
5338 }
5339}
5340
53415501const usage_ast_check =
53425502 \\Usage: zig ast-check [file]
53435503 \\
......@@ -5360,8 +5520,6 @@ pub fn cmdAstCheck(
53605520 arena: Allocator,
53615521 args: []const []const u8,
53625522) !void {
5363 const Module = @import("Module.zig");
5364 const AstGen = @import("AstGen.zig");
53655523 const Zir = @import("Zir.zig");
53665524
53675525 var color: Color = .auto;
......@@ -5451,26 +5609,18 @@ pub fn cmdAstCheck(
54515609 file.tree_loaded = true;
54525610 defer file.tree.deinit(gpa);
54535611
5454 try printErrsMsgToStdErr(gpa, arena, file.tree, file.sub_file_path, color);
5455 if (file.tree.errors.len != 0) {
5456 process.exit(1);
5457 }
5458
54595612 file.zir = try AstGen.generate(gpa, file.tree);
54605613 file.zir_loaded = true;
54615614 defer file.zir.deinit(gpa);
54625615
54635616 if (file.zir.hasCompileErrors()) {
5464 var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);
5465 try Compilation.AllErrors.addZir(arena, &errors, &file);
5466 const ttyconf: std.debug.TTY.Config = switch (color) {
5467 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
5468 .on => .escape_codes,
5469 .off => .no_color,
5470 };
5471 for (errors.items) |full_err_msg| {
5472 full_err_msg.renderToStdErr(ttyconf);
5473 }
5617 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5618 try wip_errors.init(gpa);
5619 defer wip_errors.deinit();
5620 try Compilation.addZirErrorMessages(&wip_errors, &file);
5621 var error_bundle = try wip_errors.toOwnedBundle("");
5622 defer error_bundle.deinit(gpa);
5623 error_bundle.renderToStdErr(renderOptions(color));
54745624 process.exit(1);
54755625 }
54765626
......@@ -5528,8 +5678,7 @@ pub fn cmdChangelist(
55285678 arena: Allocator,
55295679 args: []const []const u8,
55305680) !void {
5531 const Module = @import("Module.zig");
5532 const AstGen = @import("AstGen.zig");
5681 const color: Color = .auto;
55335682 const Zir = @import("Zir.zig");
55345683
55355684 const old_source_file = args[0];
......@@ -5577,22 +5726,18 @@ pub fn cmdChangelist(
55775726 file.tree_loaded = true;
55785727 defer file.tree.deinit(gpa);
55795728
5580 try printErrsMsgToStdErr(gpa, arena, file.tree, old_source_file, .auto);
5581 if (file.tree.errors.len != 0) {
5582 process.exit(1);
5583 }
5584
55855729 file.zir = try AstGen.generate(gpa, file.tree);
55865730 file.zir_loaded = true;
55875731 defer file.zir.deinit(gpa);
55885732
55895733 if (file.zir.hasCompileErrors()) {
5590 var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);
5591 try Compilation.AllErrors.addZir(arena, &errors, &file);
5592 const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr());
5593 for (errors.items) |full_err_msg| {
5594 full_err_msg.renderToStdErr(ttyconf);
5595 }
5734 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5735 try wip_errors.init(gpa);
5736 defer wip_errors.deinit();
5737 try Compilation.addZirErrorMessages(&wip_errors, &file);
5738 var error_bundle = try wip_errors.toOwnedBundle("");
5739 defer error_bundle.deinit(gpa);
5740 error_bundle.renderToStdErr(renderOptions(color));
55965741 process.exit(1);
55975742 }
55985743
......@@ -5614,11 +5759,6 @@ pub fn cmdChangelist(
56145759 var new_tree = try Ast.parse(gpa, new_source, .zig);
56155760 defer new_tree.deinit(gpa);
56165761
5617 try printErrsMsgToStdErr(gpa, arena, new_tree, new_source_file, .auto);
5618 if (new_tree.errors.len != 0) {
5619 process.exit(1);
5620 }
5621
56225762 var old_zir = file.zir;
56235763 defer old_zir.deinit(gpa);
56245764 file.zir_loaded = false;
......@@ -5626,12 +5766,13 @@ pub fn cmdChangelist(
56265766 file.zir_loaded = true;
56275767
56285768 if (file.zir.hasCompileErrors()) {
5629 var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);
5630 try Compilation.AllErrors.addZir(arena, &errors, &file);
5631 const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr());
5632 for (errors.items) |full_err_msg| {
5633 full_err_msg.renderToStdErr(ttyconf);
5634 }
5769 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5770 try wip_errors.init(gpa);
5771 defer wip_errors.deinit();
5772 try Compilation.addZirErrorMessages(&wip_errors, &file);
5773 var error_bundle = try wip_errors.toOwnedBundle("");
5774 defer error_bundle.deinit(gpa);
5775 error_bundle.renderToStdErr(renderOptions(color));
56355776 process.exit(1);
56365777 }
56375778
......@@ -5892,3 +6033,20 @@ const ClangSearchSanitizer = struct {
58926033 iframework: bool = false,
58936034 };
58946035};
6036
6037fn get_tty_conf(color: Color) std.debug.TTY.Config {
6038 return switch (color) {
6039 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
6040 .on => .escape_codes,
6041 .off => .no_color,
6042 };
6043}
6044
6045fn renderOptions(color: Color) std.zig.ErrorBundle.RenderOptions {
6046 const ttyconf = get_tty_conf(color);
6047 return .{
6048 .ttyconf = ttyconf,
6049 .include_source_line = ttyconf != .no_color,
6050 .include_reference_trace = ttyconf != .no_color,
6051 };
6052}
src/mingw.zig+7-7
......@@ -19,7 +19,7 @@ pub const CRTFile = enum {
1919 uuid_lib,
2020};
2121
22pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
22pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
2323 if (!build_options.have_llvm) {
2424 return error.ZigCompilerNotBuiltWithLLVMExtensions;
2525 }
......@@ -41,7 +41,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
4141 //"-D_UNICODE",
4242 //"-DWPRFLAG=1",
4343 });
44 return comp.build_crt_file("crt2", .Obj, &[1]Compilation.CSourceFile{
44 return comp.build_crt_file("crt2", .Obj, .@"mingw-w64 crt2.o", prog_node, &.{
4545 .{
4646 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
4747 "libc", "mingw", "crt", "crtexe.c",
......@@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
6060 "-U__CRTDLL__",
6161 "-D__MSVCRT__",
6262 });
63 return comp.build_crt_file("dllcrt2", .Obj, &[1]Compilation.CSourceFile{
63 return comp.build_crt_file("dllcrt2", .Obj, .@"mingw-w64 dllcrt2.o", prog_node, &.{
6464 .{
6565 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
6666 "libc", "mingw", "crt", "crtdll.c",
......@@ -100,7 +100,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
100100 .extra_flags = args.items,
101101 };
102102 }
103 return comp.build_crt_file("mingw32", .Lib, &c_source_files);
103 return comp.build_crt_file("mingw32", .Lib, .@"mingw-w64 mingw32.lib", prog_node, &c_source_files);
104104 },
105105
106106 .msvcrt_os_lib => {
......@@ -148,7 +148,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
148148 };
149149 }
150150 }
151 return comp.build_crt_file("msvcrt-os", .Lib, c_source_files.items);
151 return comp.build_crt_file("msvcrt-os", .Lib, .@"mingw-w64 msvcrt-os.lib", prog_node, c_source_files.items);
152152 },
153153
154154 .mingwex_lib => {
......@@ -211,7 +211,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
211211 } else {
212212 @panic("unsupported arch");
213213 }
214 return comp.build_crt_file("mingwex", .Lib, c_source_files.items);
214 return comp.build_crt_file("mingwex", .Lib, .@"mingw-w64 mingwex.lib", prog_node, c_source_files.items);
215215 },
216216
217217 .uuid_lib => {
......@@ -244,7 +244,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
244244 .extra_flags = extra_flags,
245245 };
246246 }
247 return comp.build_crt_file("uuid", .Lib, &c_source_files);
247 return comp.build_crt_file("uuid", .Lib, .@"mingw-w64 uuid.lib", prog_node, &c_source_files);
248248 },
249249 }
250250}
src/musl.zig+8-8
......@@ -17,7 +17,7 @@ pub const CRTFile = enum {
1717 libc_so,
1818};
1919
20pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
20pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
2121 if (!build_options.have_llvm) {
2222 return error.ZigCompilerNotBuiltWithLLVMExtensions;
2323 }
......@@ -33,7 +33,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
3333 try args.appendSlice(&[_][]const u8{
3434 "-Qunused-arguments",
3535 });
36 return comp.build_crt_file("crti", .Obj, &[1]Compilation.CSourceFile{
36 return comp.build_crt_file("crti", .Obj, .@"musl crti.o", prog_node, &.{
3737 .{
3838 .src_path = try start_asm_path(comp, arena, "crti.s"),
3939 .extra_flags = args.items,
......@@ -46,7 +46,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
4646 try args.appendSlice(&[_][]const u8{
4747 "-Qunused-arguments",
4848 });
49 return comp.build_crt_file("crtn", .Obj, &[1]Compilation.CSourceFile{
49 return comp.build_crt_file("crtn", .Obj, .@"musl crtn.o", prog_node, &.{
5050 .{
5151 .src_path = try start_asm_path(comp, arena, "crtn.s"),
5252 .extra_flags = args.items,
......@@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
6060 "-fno-stack-protector",
6161 "-DCRT",
6262 });
63 return comp.build_crt_file("crt1", .Obj, &[1]Compilation.CSourceFile{
63 return comp.build_crt_file("crt1", .Obj, .@"musl crt1.o", prog_node, &.{
6464 .{
6565 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
6666 "libc", "musl", "crt", "crt1.c",
......@@ -77,7 +77,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
7777 "-fno-stack-protector",
7878 "-DCRT",
7979 });
80 return comp.build_crt_file("rcrt1", .Obj, &[1]Compilation.CSourceFile{
80 return comp.build_crt_file("rcrt1", .Obj, .@"musl rcrt1.o", prog_node, &.{
8181 .{
8282 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
8383 "libc", "musl", "crt", "rcrt1.c",
......@@ -94,7 +94,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
9494 "-fno-stack-protector",
9595 "-DCRT",
9696 });
97 return comp.build_crt_file("Scrt1", .Obj, &[1]Compilation.CSourceFile{
97 return comp.build_crt_file("Scrt1", .Obj, .@"musl Scrt1.o", prog_node, &.{
9898 .{
9999 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
100100 "libc", "musl", "crt", "Scrt1.c",
......@@ -187,7 +187,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
187187 .extra_flags = args.items,
188188 };
189189 }
190 return comp.build_crt_file("c", .Lib, c_source_files.items);
190 return comp.build_crt_file("c", .Lib, .@"musl libc.a", prog_node, c_source_files.items);
191191 },
192192 .libc_so => {
193193 const target = comp.getTarget();
......@@ -241,7 +241,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
241241 });
242242 defer sub_compilation.destroy();
243243
244 try sub_compilation.updateSubCompilation();
244 try comp.updateSubCompilation(sub_compilation, .@"musl libc.so", prog_node);
245245
246246 try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1);
247247
src/objcopy.zig+45-5
......@@ -4,22 +4,25 @@ const fs = std.fs;
44const elf = std.elf;
55const Allocator = std.mem.Allocator;
66const File = std.fs.File;
7const assert = std.debug.assert;
8
79const main = @import("main.zig");
810const fatal = main.fatal;
9const cleanExit = main.cleanExit;
11const Server = std.zig.Server;
12const build_options = @import("build_options");
1013
1114pub fn cmdObjCopy(
1215 gpa: Allocator,
1316 arena: Allocator,
1417 args: []const []const u8,
1518) !void {
16 _ = gpa;
1719 var i: usize = 0;
1820 var opt_out_fmt: ?std.Target.ObjectFormat = null;
1921 var opt_input: ?[]const u8 = null;
2022 var opt_output: ?[]const u8 = null;
2123 var only_section: ?[]const u8 = null;
2224 var pad_to: ?u64 = null;
25 var listen = false;
2326 while (i < args.len) : (i += 1) {
2427 const arg = args[i];
2528 if (!mem.startsWith(u8, arg, "-")) {
......@@ -54,6 +57,8 @@ pub fn cmdObjCopy(
5457 i += 1;
5558 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
5659 only_section = args[i];
60 } else if (mem.eql(u8, arg, "--listen=-")) {
61 listen = true;
5762 } else if (mem.startsWith(u8, arg, "--only-section=")) {
5863 only_section = arg["--output-target=".len..];
5964 } else if (mem.eql(u8, arg, "--pad-to")) {
......@@ -102,10 +107,45 @@ pub fn cmdObjCopy(
102107 .only_section = only_section,
103108 .pad_to = pad_to,
104109 });
105 return cleanExit();
106110 },
107111 else => fatal("unsupported output object format: {s}", .{@tagName(out_fmt)}),
108112 }
113
114 if (listen) {
115 var server = try Server.init(.{
116 .gpa = gpa,
117 .in = std.io.getStdIn(),
118 .out = std.io.getStdOut(),
119 .zig_version = build_options.version,
120 });
121 defer server.deinit();
122
123 var seen_update = false;
124 while (true) {
125 const hdr = try server.receiveMessage();
126 switch (hdr.tag) {
127 .exit => {
128 return std.process.cleanExit();
129 },
130 .update => {
131 if (seen_update) {
132 std.debug.print("zig objcopy only supports 1 update for now\n", .{});
133 std.process.exit(1);
134 }
135 seen_update = true;
136
137 try server.serveEmitBinPath(output, .{
138 .flags = .{ .cache_hit = false },
139 });
140 },
141 else => {
142 std.debug.print("unsupported message: {s}", .{@tagName(hdr.tag)});
143 std.process.exit(1);
144 },
145 }
146 }
147 }
148 return std.process.cleanExit();
109149}
110150
111151const usage =
......@@ -417,7 +457,7 @@ const HexWriter = struct {
417457 }
418458
419459 fn Address(address: u32) Record {
420 std.debug.assert(address > 0xFFFF);
460 assert(address > 0xFFFF);
421461 const segment = @intCast(u16, address / 0x10000);
422462 if (address > 0xFFFFF) {
423463 return Record{
......@@ -460,7 +500,7 @@ const HexWriter = struct {
460500 const BUFSIZE = 1 + (1 + 2 + 1 + MAX_PAYLOAD_LEN + 1) * 2 + linesep.len;
461501 var outbuf: [BUFSIZE]u8 = undefined;
462502 const payload_bytes = self.getPayloadBytes();
463 std.debug.assert(payload_bytes.len <= MAX_PAYLOAD_LEN);
503 assert(payload_bytes.len <= MAX_PAYLOAD_LEN);
464504
465505 const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3s}{4X:0>2}" ++ linesep, .{
466506 @intCast(u8, payload_bytes.len),
src/test.zig deleted-1984
......@@ -1,1984 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const Allocator = std.mem.Allocator;
4const CrossTarget = std.zig.CrossTarget;
5const print = std.debug.print;
6const assert = std.debug.assert;
7
8const link = @import("link.zig");
9const Compilation = @import("Compilation.zig");
10const Package = @import("Package.zig");
11const introspect = @import("introspect.zig");
12const build_options = @import("build_options");
13const ThreadPool = @import("ThreadPool.zig");
14const WaitGroup = @import("WaitGroup.zig");
15const zig_h = link.File.C.zig_h;
16
17const enable_qemu: bool = build_options.enable_qemu;
18const enable_wine: bool = build_options.enable_wine;
19const enable_wasmtime: bool = build_options.enable_wasmtime;
20const enable_darling: bool = build_options.enable_darling;
21const enable_rosetta: bool = build_options.enable_rosetta;
22const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir;
23const skip_stage1 = true;
24
25const hr = "=" ** 80;
26
27test {
28 const use_gpa = build_options.force_gpa or !builtin.link_libc;
29 const gpa = gpa: {
30 if (use_gpa) {
31 break :gpa std.testing.allocator;
32 }
33 // We would prefer to use raw libc allocator here, but cannot
34 // use it if it won't support the alignment we need.
35 if (@alignOf(std.c.max_align_t) < @alignOf(i128)) {
36 break :gpa std.heap.c_allocator;
37 }
38 break :gpa std.heap.raw_c_allocator;
39 };
40
41 var arena_allocator = std.heap.ArenaAllocator.init(gpa);
42 defer arena_allocator.deinit();
43 const arena = arena_allocator.allocator();
44
45 var ctx = TestContext.init(gpa, arena);
46 defer ctx.deinit();
47
48 {
49 const dir_path = try std.fs.path.join(arena, &.{
50 std.fs.path.dirname(@src().file).?, "..", "test", "cases",
51 });
52
53 var dir = try std.fs.cwd().openIterableDir(dir_path, .{});
54 defer dir.close();
55
56 ctx.addTestCasesFromDir(dir);
57 }
58
59 try @import("../test/cases.zig").addCases(&ctx);
60
61 try ctx.run();
62}
63
64const ErrorMsg = union(enum) {
65 src: struct {
66 src_path: []const u8,
67 msg: []const u8,
68 // maxint means match anything
69 // this is a workaround for stage1 compiler bug I ran into when making it ?u32
70 line: u32,
71 // maxint means match anything
72 // this is a workaround for stage1 compiler bug I ran into when making it ?u32
73 column: u32,
74 kind: Kind,
75 count: u32,
76 },
77 plain: struct {
78 msg: []const u8,
79 kind: Kind,
80 count: u32,
81 },
82
83 const Kind = enum {
84 @"error",
85 note,
86 };
87
88 fn init(other: Compilation.AllErrors.Message, kind: Kind) ErrorMsg {
89 switch (other) {
90 .src => |src| return .{
91 .src = .{
92 .src_path = src.src_path,
93 .msg = src.msg,
94 .line = @intCast(u32, src.line),
95 .column = @intCast(u32, src.column),
96 .kind = kind,
97 .count = src.count,
98 },
99 },
100 .plain => |plain| return .{
101 .plain = .{
102 .msg = plain.msg,
103 .kind = kind,
104 .count = plain.count,
105 },
106 },
107 }
108 }
109
110 pub fn format(
111 self: ErrorMsg,
112 comptime fmt: []const u8,
113 options: std.fmt.FormatOptions,
114 writer: anytype,
115 ) !void {
116 _ = fmt;
117 _ = options;
118 switch (self) {
119 .src => |src| {
120 if (!std.mem.eql(u8, src.src_path, "?") or
121 src.line != std.math.maxInt(u32) or
122 src.column != std.math.maxInt(u32))
123 {
124 try writer.print("{s}:", .{src.src_path});
125 if (src.line != std.math.maxInt(u32)) {
126 try writer.print("{d}:", .{src.line + 1});
127 } else {
128 try writer.writeAll("?:");
129 }
130 if (src.column != std.math.maxInt(u32)) {
131 try writer.print("{d}: ", .{src.column + 1});
132 } else {
133 try writer.writeAll("?: ");
134 }
135 }
136 try writer.print("{s}: {s}", .{ @tagName(src.kind), src.msg });
137 if (src.count != 1) {
138 try writer.print(" ({d} times)", .{src.count});
139 }
140 },
141 .plain => |plain| {
142 try writer.print("{s}: {s}", .{ @tagName(plain.kind), plain.msg });
143 if (plain.count != 1) {
144 try writer.print(" ({d} times)", .{plain.count});
145 }
146 },
147 }
148 }
149};
150
151/// Default config values for known test manifest key-value pairings.
152/// Currently handled defaults are:
153/// * backend
154/// * target
155/// * output_mode
156/// * is_test
157const TestManifestConfigDefaults = struct {
158 /// Asserts if the key doesn't exist - yep, it's an oversight alright.
159 fn get(@"type": TestManifest.Type, key: []const u8) []const u8 {
160 if (std.mem.eql(u8, key, "backend")) {
161 return "stage2";
162 } else if (std.mem.eql(u8, key, "target")) {
163 comptime {
164 var defaults: []const u8 = "";
165 // TODO should we only return "mainstream" targets by default here?
166 // TODO we should also specify ABIs explicitly as the backends are
167 // getting more and more complete
168 // Linux
169 inline for (&[_][]const u8{ "x86_64", "arm", "aarch64" }) |arch| {
170 defaults = defaults ++ arch ++ "-linux" ++ ",";
171 }
172 // macOS
173 inline for (&[_][]const u8{ "x86_64", "aarch64" }) |arch| {
174 defaults = defaults ++ arch ++ "-macos" ++ ",";
175 }
176 // Windows
177 defaults = defaults ++ "x86_64-windows" ++ ",";
178 // Wasm
179 defaults = defaults ++ "wasm32-wasi";
180 return defaults;
181 }
182 } else if (std.mem.eql(u8, key, "output_mode")) {
183 return switch (@"type") {
184 .@"error" => "Obj",
185 .run => "Exe",
186 .cli => @panic("TODO test harness for CLI tests"),
187 };
188 } else if (std.mem.eql(u8, key, "is_test")) {
189 return "0";
190 } else unreachable;
191 }
192};
193
194/// Manifest syntax example:
195/// (see https://github.com/ziglang/zig/issues/11288)
196///
197/// error
198/// backend=stage1,stage2
199/// output_mode=exe
200///
201/// :3:19: error: foo
202///
203/// run
204/// target=x86_64-linux,aarch64-macos
205///
206/// I am expected stdout! Hello!
207///
208/// cli
209///
210/// build test
211const TestManifest = struct {
212 type: Type,
213 config_map: std.StringHashMap([]const u8),
214 trailing_bytes: []const u8 = "",
215
216 const Type = enum {
217 @"error",
218 run,
219 cli,
220 };
221
222 const TrailingIterator = struct {
223 inner: std.mem.TokenIterator(u8),
224
225 fn next(self: *TrailingIterator) ?[]const u8 {
226 const next_inner = self.inner.next() orelse return null;
227 return std.mem.trim(u8, next_inner[2..], " \t");
228 }
229 };
230
231 fn ConfigValueIterator(comptime T: type) type {
232 return struct {
233 inner: std.mem.SplitIterator(u8),
234
235 fn next(self: *@This()) !?T {
236 const next_raw = self.inner.next() orelse return null;
237 const parseFn = getDefaultParser(T);
238 return try parseFn(next_raw);
239 }
240 };
241 }
242
243 fn parse(arena: Allocator, bytes: []const u8) !TestManifest {
244 // The manifest is the last contiguous block of comments in the file
245 // We scan for the beginning by searching backward for the first non-empty line that does not start with "//"
246 var start: ?usize = null;
247 var end: usize = bytes.len;
248 if (bytes.len > 0) {
249 var cursor: usize = bytes.len - 1;
250 while (true) {
251 // Move to beginning of line
252 while (cursor > 0 and bytes[cursor - 1] != '\n') cursor -= 1;
253
254 if (std.mem.startsWith(u8, bytes[cursor..], "//")) {
255 start = cursor; // Contiguous comment line, include in manifest
256 } else {
257 if (start != null) break; // Encountered non-comment line, end of manifest
258
259 // We ignore all-whitespace lines following the comment block, but anything else
260 // means that there is no manifest present.
261 if (std.mem.trim(u8, bytes[cursor..end], " \r\n\t").len == 0) {
262 end = cursor;
263 } else break; // If it's not whitespace, there is no manifest
264 }
265
266 // Move to previous line
267 if (cursor != 0) cursor -= 1 else break;
268 }
269 }
270
271 const actual_start = start orelse return error.MissingTestManifest;
272 const manifest_bytes = bytes[actual_start..end];
273
274 var it = std.mem.tokenize(u8, manifest_bytes, "\r\n");
275
276 // First line is the test type
277 const tt: Type = blk: {
278 const line = it.next() orelse return error.MissingTestCaseType;
279 const raw = std.mem.trim(u8, line[2..], " \t");
280 if (std.mem.eql(u8, raw, "error")) {
281 break :blk .@"error";
282 } else if (std.mem.eql(u8, raw, "run")) {
283 break :blk .run;
284 } else if (std.mem.eql(u8, raw, "cli")) {
285 break :blk .cli;
286 } else {
287 std.log.warn("unknown test case type requested: {s}", .{raw});
288 return error.UnknownTestCaseType;
289 }
290 };
291
292 var manifest: TestManifest = .{
293 .type = tt,
294 .config_map = std.StringHashMap([]const u8).init(arena),
295 };
296
297 // Any subsequent line until a blank comment line is key=value(s) pair
298 while (it.next()) |line| {
299 const trimmed = std.mem.trim(u8, line[2..], " \t");
300 if (trimmed.len == 0) break;
301
302 // Parse key=value(s)
303 var kv_it = std.mem.split(u8, trimmed, "=");
304 const key = kv_it.first();
305 try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig);
306 }
307
308 // Finally, trailing is expected output
309 manifest.trailing_bytes = manifest_bytes[it.index..];
310
311 return manifest;
312 }
313
314 fn getConfigForKey(
315 self: TestManifest,
316 key: []const u8,
317 comptime T: type,
318 ) ConfigValueIterator(T) {
319 const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key);
320 return ConfigValueIterator(T){
321 .inner = std.mem.split(u8, bytes, ","),
322 };
323 }
324
325 fn getConfigForKeyAlloc(
326 self: TestManifest,
327 allocator: Allocator,
328 key: []const u8,
329 comptime T: type,
330 ) ![]const T {
331 var out = std.ArrayList(T).init(allocator);
332 defer out.deinit();
333 var it = self.getConfigForKey(key, T);
334 while (try it.next()) |item| {
335 try out.append(item);
336 }
337 return try out.toOwnedSlice();
338 }
339
340 fn getConfigForKeyAssertSingle(self: TestManifest, key: []const u8, comptime T: type) !T {
341 var it = self.getConfigForKey(key, T);
342 const res = (try it.next()) orelse unreachable;
343 assert((try it.next()) == null);
344 return res;
345 }
346
347 fn trailing(self: TestManifest) TrailingIterator {
348 return .{
349 .inner = std.mem.tokenize(u8, self.trailing_bytes, "\r\n"),
350 };
351 }
352
353 fn trailingAlloc(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 {
354 var out = std.ArrayList([]const u8).init(allocator);
355 defer out.deinit();
356 var it = self.trailing();
357 while (it.next()) |line| {
358 try out.append(line);
359 }
360 return try out.toOwnedSlice();
361 }
362
363 fn ParseFn(comptime T: type) type {
364 return fn ([]const u8) anyerror!T;
365 }
366
367 fn getDefaultParser(comptime T: type) ParseFn(T) {
368 if (T == CrossTarget) return struct {
369 fn parse(str: []const u8) anyerror!T {
370 var opts = CrossTarget.ParseOptions{
371 .arch_os_abi = str,
372 };
373 return try CrossTarget.parse(opts);
374 }
375 }.parse;
376
377 switch (@typeInfo(T)) {
378 .Int => return struct {
379 fn parse(str: []const u8) anyerror!T {
380 return try std.fmt.parseInt(T, str, 0);
381 }
382 }.parse,
383 .Bool => return struct {
384 fn parse(str: []const u8) anyerror!T {
385 const as_int = try std.fmt.parseInt(u1, str, 0);
386 return as_int > 0;
387 }
388 }.parse,
389 .Enum => return struct {
390 fn parse(str: []const u8) anyerror!T {
391 return std.meta.stringToEnum(T, str) orelse {
392 std.log.err("unknown enum variant for {s}: {s}", .{ @typeName(T), str });
393 return error.UnknownEnumVariant;
394 };
395 }
396 }.parse,
397 .Struct => @compileError("no default parser for " ++ @typeName(T)),
398 else => @compileError("no default parser for " ++ @typeName(T)),
399 }
400 }
401};
402
403const TestStrategy = enum {
404 /// Execute tests as independent compilations, unless they are explicitly
405 /// incremental ("foo.0.zig", "foo.1.zig", etc.)
406 independent,
407 /// Execute all tests as incremental updates to a single compilation. Explicitly
408 /// incremental tests ("foo.0.zig", "foo.1.zig", etc.) still execute in order
409 incremental,
410};
411
412/// Iterates a set of filenames extracting batches that are either incremental
413/// ("foo.0.zig", "foo.1.zig", etc.) or independent ("foo.zig", "bar.zig", etc.).
414/// Assumes filenames are sorted.
415const TestIterator = struct {
416 start: usize = 0,
417 end: usize = 0,
418 filenames: []const []const u8,
419 /// reset on each call to `next`
420 index: usize = 0,
421
422 const Error = error{InvalidIncrementalTestIndex};
423
424 fn next(it: *TestIterator) Error!?[]const []const u8 {
425 try it.nextInner();
426 if (it.start == it.end) return null;
427 return it.filenames[it.start..it.end];
428 }
429
430 fn nextInner(it: *TestIterator) Error!void {
431 it.start = it.end;
432 if (it.end == it.filenames.len) return;
433 if (it.end + 1 == it.filenames.len) {
434 it.end += 1;
435 return;
436 }
437
438 const remaining = it.filenames[it.end..];
439 it.index = 0;
440 while (it.index < remaining.len - 1) : (it.index += 1) {
441 // First, check if this file is part of an incremental update sequence
442 // Split filename into "<base_name>.<index>.<file_ext>"
443 const prev_parts = getTestFileNameParts(remaining[it.index]);
444 const new_parts = getTestFileNameParts(remaining[it.index + 1]);
445
446 // If base_name and file_ext match, these files are in the same test sequence
447 // and the new one should be the incremented version of the previous test
448 if (std.mem.eql(u8, prev_parts.base_name, new_parts.base_name) and
449 std.mem.eql(u8, prev_parts.file_ext, new_parts.file_ext))
450 {
451 // This is "foo.X.zig" followed by "foo.Y.zig". Make sure that X = Y + 1
452 if (prev_parts.test_index == null)
453 return error.InvalidIncrementalTestIndex;
454 if (new_parts.test_index == null)
455 return error.InvalidIncrementalTestIndex;
456 if (new_parts.test_index.? != prev_parts.test_index.? + 1)
457 return error.InvalidIncrementalTestIndex;
458 } else {
459 // This is not the same test sequence, so the new file must be the first file
460 // in a new sequence ("*.0.zig") or an independent test file ("*.zig")
461 if (new_parts.test_index != null and new_parts.test_index.? != 0)
462 return error.InvalidIncrementalTestIndex;
463
464 it.end += it.index + 1;
465 break;
466 }
467 } else {
468 it.end += remaining.len;
469 }
470 }
471
472 /// In the event of an `error.InvalidIncrementalTestIndex`, this function can
473 /// be used to find the current filename that was being processed.
474 /// Asserts the iterator hasn't reached the end.
475 fn currentFilename(it: TestIterator) []const u8 {
476 assert(it.end != it.filenames.len);
477 const remaining = it.filenames[it.end..];
478 return remaining[it.index + 1];
479 }
480};
481
482/// For a filename in the format "<filename>.X.<ext>" or "<filename>.<ext>", returns
483/// "<filename>", "<ext>" and X parsed as a decimal number. If X is not present, or
484/// cannot be parsed as a decimal number, it is treated as part of <filename>
485fn getTestFileNameParts(name: []const u8) struct {
486 base_name: []const u8,
487 file_ext: []const u8,
488 test_index: ?usize,
489} {
490 const file_ext = std.fs.path.extension(name);
491 const trimmed = name[0 .. name.len - file_ext.len]; // Trim off ".<ext>"
492 const maybe_index = std.fs.path.extension(trimmed); // Extract ".X"
493
494 // Attempt to parse index
495 const index: ?usize = if (maybe_index.len > 0)
496 std.fmt.parseInt(usize, maybe_index[1..], 10) catch null
497 else
498 null;
499
500 // Adjust "<filename>" extent based on parsing success
501 const base_name_end = trimmed.len - if (index != null) maybe_index.len else 0;
502 return .{
503 .base_name = name[0..base_name_end],
504 .file_ext = if (file_ext.len > 0) file_ext[1..] else file_ext,
505 .test_index = index,
506 };
507}
508
509/// Sort test filenames in-place, so that incremental test cases ("foo.0.zig",
510/// "foo.1.zig", etc.) are contiguous and appear in numerical order.
511fn sortTestFilenames(filenames: [][]const u8) void {
512 const Context = struct {
513 pub fn lessThan(_: @This(), a: []const u8, b: []const u8) bool {
514 const a_parts = getTestFileNameParts(a);
515 const b_parts = getTestFileNameParts(b);
516
517 // Sort "<base_name>.X.<file_ext>" based on "<base_name>" and "<file_ext>" first
518 return switch (std.mem.order(u8, a_parts.base_name, b_parts.base_name)) {
519 .lt => true,
520 .gt => false,
521 .eq => switch (std.mem.order(u8, a_parts.file_ext, b_parts.file_ext)) {
522 .lt => true,
523 .gt => false,
524 .eq => {
525 // a and b differ only in their ".X" part
526
527 // Sort "<base_name>.<file_ext>" before any "<base_name>.X.<file_ext>"
528 if (a_parts.test_index) |a_index| {
529 if (b_parts.test_index) |b_index| {
530 // Make sure that incremental tests appear in linear order
531 return a_index < b_index;
532 } else {
533 return false;
534 }
535 } else {
536 return b_parts.test_index != null;
537 }
538 },
539 },
540 };
541 }
542 };
543 std.sort.sort([]const u8, filenames, Context{}, Context.lessThan);
544}
545
546pub const TestContext = struct {
547 gpa: Allocator,
548 arena: Allocator,
549 cases: std.ArrayList(Case),
550
551 pub const Update = struct {
552 /// The input to the current update. We simulate an incremental update
553 /// with the file's contents changed to this value each update.
554 ///
555 /// This value can change entirely between updates, which would be akin
556 /// to deleting the source file and creating a new one from scratch; or
557 /// you can keep it mostly consistent, with small changes, testing the
558 /// effects of the incremental compilation.
559 src: [:0]const u8,
560 name: []const u8,
561 case: union(enum) {
562 /// Check the main binary output file against an expected set of bytes.
563 /// This is most useful with, for example, `-ofmt=c`.
564 CompareObjectFile: []const u8,
565 /// An error update attempts to compile bad code, and ensures that it
566 /// fails to compile, and for the expected reasons.
567 /// A slice containing the expected errors *in sequential order*.
568 Error: []const ErrorMsg,
569 /// An execution update compiles and runs the input, testing the
570 /// stdout against the expected results
571 /// This is a slice containing the expected message.
572 Execution: []const u8,
573 /// A header update compiles the input with the equivalent of
574 /// `-femit-h` and tests the produced header against the
575 /// expected result
576 Header: []const u8,
577 },
578 };
579
580 pub const File = struct {
581 /// Contents of the importable file. Doesn't yet support incremental updates.
582 src: [:0]const u8,
583 path: []const u8,
584 };
585
586 pub const DepModule = struct {
587 name: []const u8,
588 path: []const u8,
589 };
590
591 pub const Backend = enum {
592 stage1,
593 stage2,
594 llvm,
595 };
596
597 /// A `Case` consists of a list of `Update`. The same `Compilation` is used for each
598 /// update, so each update's source is treated as a single file being
599 /// updated by the test harness and incrementally compiled.
600 pub const Case = struct {
601 /// The name of the test case. This is shown if a test fails, and
602 /// otherwise ignored.
603 name: []const u8,
604 /// The platform the test targets. For non-native platforms, an emulator
605 /// such as QEMU is required for tests to complete.
606 target: CrossTarget,
607 /// In order to be able to run e.g. Execution updates, this must be set
608 /// to Executable.
609 output_mode: std.builtin.OutputMode,
610 optimize_mode: std.builtin.Mode = .Debug,
611 updates: std.ArrayList(Update),
612 emit_h: bool = false,
613 is_test: bool = false,
614 expect_exact: bool = false,
615 backend: Backend = .stage2,
616 link_libc: bool = false,
617
618 files: std.ArrayList(File),
619 deps: std.ArrayList(DepModule),
620
621 result: anyerror!void = {},
622
623 pub fn addSourceFile(case: *Case, name: []const u8, src: [:0]const u8) void {
624 case.files.append(.{ .path = name, .src = src }) catch @panic("out of memory");
625 }
626
627 pub fn addDepModule(case: *Case, name: []const u8, path: []const u8) void {
628 case.deps.append(.{
629 .name = name,
630 .path = path,
631 }) catch @panic("out of memory");
632 }
633
634 /// Adds a subcase in which the module is updated with `src`, and a C
635 /// header is generated.
636 pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void {
637 self.emit_h = true;
638 self.updates.append(.{
639 .src = src,
640 .name = "update",
641 .case = .{ .Header = result },
642 }) catch @panic("out of memory");
643 }
644
645 /// Adds a subcase in which the module is updated with `src`, compiled,
646 /// run, and the output is tested against `result`.
647 pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {
648 self.updates.append(.{
649 .src = src,
650 .name = "update",
651 .case = .{ .Execution = result },
652 }) catch @panic("out of memory");
653 }
654
655 /// Adds a subcase in which the module is updated with `src`, compiled,
656 /// and the object file data is compared against `result`.
657 pub fn addCompareObjectFile(self: *Case, src: [:0]const u8, result: []const u8) void {
658 self.updates.append(.{
659 .src = src,
660 .name = "update",
661 .case = .{ .CompareObjectFile = result },
662 }) catch @panic("out of memory");
663 }
664
665 pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void {
666 return self.addErrorNamed("update", src, errors);
667 }
668
669 /// Adds a subcase in which the module is updated with `src`, which
670 /// should contain invalid input, and ensures that compilation fails
671 /// for the expected reasons, given in sequential order in `errors` in
672 /// the form `:line:column: error: message`.
673 pub fn addErrorNamed(
674 self: *Case,
675 name: []const u8,
676 src: [:0]const u8,
677 errors: []const []const u8,
678 ) void {
679 var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch @panic("out of memory");
680 for (errors, 0..) |err_msg_line, i| {
681 if (std.mem.startsWith(u8, err_msg_line, "error: ")) {
682 array[i] = .{
683 .plain = .{
684 .msg = err_msg_line["error: ".len..],
685 .kind = .@"error",
686 .count = 1,
687 },
688 };
689 continue;
690 } else if (std.mem.startsWith(u8, err_msg_line, "note: ")) {
691 array[i] = .{
692 .plain = .{
693 .msg = err_msg_line["note: ".len..],
694 .kind = .note,
695 .count = 1,
696 },
697 };
698 continue;
699 }
700 // example: "file.zig:1:2: error: bad thing happened"
701 var it = std.mem.split(u8, err_msg_line, ":");
702 const src_path = it.first();
703 const line_text = it.next() orelse @panic("missing line");
704 const col_text = it.next() orelse @panic("missing column");
705 const kind_text = it.next() orelse @panic("missing 'error'/'note'");
706 var msg = it.rest()[1..]; // skip over the space at end of "error: "
707
708 const line: ?u32 = if (std.mem.eql(u8, line_text, "?"))
709 null
710 else
711 std.fmt.parseInt(u32, line_text, 10) catch @panic("bad line number");
712 const column: ?u32 = if (std.mem.eql(u8, line_text, "?"))
713 null
714 else
715 std.fmt.parseInt(u32, col_text, 10) catch @panic("bad column number");
716 const kind: ErrorMsg.Kind = if (std.mem.eql(u8, kind_text, " error"))
717 .@"error"
718 else if (std.mem.eql(u8, kind_text, " note"))
719 .note
720 else
721 @panic("expected 'error'/'note'");
722
723 const line_0based: u32 = if (line) |n| blk: {
724 if (n == 0) {
725 print("{s}: line must be specified starting at one\n", .{self.name});
726 return;
727 }
728 break :blk n - 1;
729 } else std.math.maxInt(u32);
730
731 const column_0based: u32 = if (column) |n| blk: {
732 if (n == 0) {
733 print("{s}: line must be specified starting at one\n", .{self.name});
734 return;
735 }
736 break :blk n - 1;
737 } else std.math.maxInt(u32);
738
739 const suffix = " times)";
740 const count = if (std.mem.endsWith(u8, msg, suffix)) count: {
741 const lparen = std.mem.lastIndexOfScalar(u8, msg, '(').?;
742 const count = std.fmt.parseInt(u32, msg[lparen + 1 .. msg.len - suffix.len], 10) catch @panic("bad error note count number");
743 msg = msg[0 .. lparen - 1];
744 break :count count;
745 } else 1;
746
747 array[i] = .{
748 .src = .{
749 .src_path = src_path,
750 .msg = msg,
751 .line = line_0based,
752 .column = column_0based,
753 .kind = kind,
754 .count = count,
755 },
756 };
757 }
758 self.updates.append(.{
759 .src = src,
760 .name = name,
761 .case = .{ .Error = array },
762 }) catch @panic("out of memory");
763 }
764
765 /// Adds a subcase in which the module is updated with `src`, and
766 /// asserts that it compiles without issue
767 pub fn compiles(self: *Case, src: [:0]const u8) void {
768 self.addError(src, &[_][]const u8{});
769 }
770 };
771
772 pub fn addExe(
773 ctx: *TestContext,
774 name: []const u8,
775 target: CrossTarget,
776 ) *Case {
777 ctx.cases.append(Case{
778 .name = name,
779 .target = target,
780 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
781 .output_mode = .Exe,
782 .files = std.ArrayList(File).init(ctx.arena),
783 .deps = std.ArrayList(DepModule).init(ctx.arena),
784 }) catch @panic("out of memory");
785 return &ctx.cases.items[ctx.cases.items.len - 1];
786 }
787
788 /// Adds a test case for Zig input, producing an executable
789 pub fn exe(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
790 return ctx.addExe(name, target);
791 }
792
793 pub fn exeFromCompiledC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
794 const prefixed_name = std.fmt.allocPrint(ctx.arena, "CBE: {s}", .{name}) catch
795 @panic("out of memory");
796 var target_adjusted = target;
797 target_adjusted.ofmt = std.Target.ObjectFormat.c;
798 ctx.cases.append(Case{
799 .name = prefixed_name,
800 .target = target_adjusted,
801 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
802 .output_mode = .Exe,
803 .files = std.ArrayList(File).init(ctx.arena),
804 .deps = std.ArrayList(DepModule).init(ctx.arena),
805 .link_libc = true,
806 }) catch @panic("out of memory");
807 return &ctx.cases.items[ctx.cases.items.len - 1];
808 }
809
810 /// Adds a test case that uses the LLVM backend to emit an executable.
811 /// Currently this implies linking libc, because only then we can generate a testable executable.
812 pub fn exeUsingLlvmBackend(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
813 ctx.cases.append(Case{
814 .name = name,
815 .target = target,
816 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
817 .output_mode = .Exe,
818 .files = std.ArrayList(File).init(ctx.arena),
819 .deps = std.ArrayList(DepModule).init(ctx.arena),
820 .backend = .llvm,
821 .link_libc = true,
822 }) catch @panic("out of memory");
823 return &ctx.cases.items[ctx.cases.items.len - 1];
824 }
825
826 pub fn addObj(
827 ctx: *TestContext,
828 name: []const u8,
829 target: CrossTarget,
830 ) *Case {
831 ctx.cases.append(Case{
832 .name = name,
833 .target = target,
834 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
835 .output_mode = .Obj,
836 .files = std.ArrayList(File).init(ctx.arena),
837 .deps = std.ArrayList(DepModule).init(ctx.arena),
838 }) catch @panic("out of memory");
839 return &ctx.cases.items[ctx.cases.items.len - 1];
840 }
841
842 pub fn addTest(
843 ctx: *TestContext,
844 name: []const u8,
845 target: CrossTarget,
846 ) *Case {
847 ctx.cases.append(Case{
848 .name = name,
849 .target = target,
850 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
851 .output_mode = .Exe,
852 .is_test = true,
853 .files = std.ArrayList(File).init(ctx.arena),
854 .deps = std.ArrayList(DepModule).init(ctx.arena),
855 }) catch @panic("out of memory");
856 return &ctx.cases.items[ctx.cases.items.len - 1];
857 }
858
859 /// Adds a test case for Zig input, producing an object file.
860 pub fn obj(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
861 return ctx.addObj(name, target);
862 }
863
864 /// Adds a test case for ZIR input, producing an object file.
865 pub fn objZIR(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
866 return ctx.addObj(name, target, .ZIR);
867 }
868
869 /// Adds a test case for Zig or ZIR input, producing C code.
870 pub fn addC(ctx: *TestContext, name: []const u8, target: CrossTarget) *Case {
871 var target_adjusted = target;
872 target_adjusted.ofmt = std.Target.ObjectFormat.c;
873 ctx.cases.append(Case{
874 .name = name,
875 .target = target_adjusted,
876 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
877 .output_mode = .Obj,
878 .files = std.ArrayList(File).init(ctx.arena),
879 .deps = std.ArrayList(DepModule).init(ctx.arena),
880 }) catch @panic("out of memory");
881 return &ctx.cases.items[ctx.cases.items.len - 1];
882 }
883
884 pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
885 ctx.addC(name, target).addCompareObjectFile(src, zig_h ++ out);
886 }
887
888 pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
889 ctx.addC(name, target).addHeader(src, zig_h ++ out);
890 }
891
892 pub fn objErrStage1(
893 ctx: *TestContext,
894 name: []const u8,
895 src: [:0]const u8,
896 expected_errors: []const []const u8,
897 ) void {
898 const case = ctx.addObj(name, .{});
899 case.backend = .stage1;
900 case.addError(src, expected_errors);
901 }
902
903 pub fn testErrStage1(
904 ctx: *TestContext,
905 name: []const u8,
906 src: [:0]const u8,
907 expected_errors: []const []const u8,
908 ) void {
909 const case = ctx.addTest(name, .{});
910 case.backend = .stage1;
911 case.addError(src, expected_errors);
912 }
913
914 pub fn exeErrStage1(
915 ctx: *TestContext,
916 name: []const u8,
917 src: [:0]const u8,
918 expected_errors: []const []const u8,
919 ) void {
920 const case = ctx.addExe(name, .{});
921 case.backend = .stage1;
922 case.addError(src, expected_errors);
923 }
924
925 pub fn addCompareOutput(
926 ctx: *TestContext,
927 name: []const u8,
928 src: [:0]const u8,
929 expected_stdout: []const u8,
930 ) void {
931 ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout);
932 }
933
934 /// Adds a test case that compiles the Zig source given in `src`, executes
935 /// it, runs it, and tests the output against `expected_stdout`
936 pub fn compareOutput(
937 ctx: *TestContext,
938 name: []const u8,
939 src: [:0]const u8,
940 expected_stdout: []const u8,
941 ) void {
942 return ctx.addCompareOutput(name, src, expected_stdout);
943 }
944
945 /// Adds a test case that compiles the ZIR source given in `src`, executes
946 /// it, runs it, and tests the output against `expected_stdout`
947 pub fn compareOutputZIR(
948 ctx: *TestContext,
949 name: []const u8,
950 src: [:0]const u8,
951 expected_stdout: []const u8,
952 ) void {
953 ctx.addCompareOutput(name, .ZIR, src, expected_stdout);
954 }
955
956 pub fn addTransform(
957 ctx: *TestContext,
958 name: []const u8,
959 target: CrossTarget,
960 src: [:0]const u8,
961 result: [:0]const u8,
962 ) void {
963 ctx.addObj(name, target).addTransform(src, result);
964 }
965
966 /// Adds a test case that compiles the Zig given in `src` to ZIR and tests
967 /// the ZIR against `result`
968 pub fn transform(
969 ctx: *TestContext,
970 name: []const u8,
971 target: CrossTarget,
972 src: [:0]const u8,
973 result: [:0]const u8,
974 ) void {
975 ctx.addTransform(name, target, src, result);
976 }
977
978 pub fn addError(
979 ctx: *TestContext,
980 name: []const u8,
981 target: CrossTarget,
982 src: [:0]const u8,
983 expected_errors: []const []const u8,
984 ) void {
985 ctx.addObj(name, target).addError(src, expected_errors);
986 }
987
988 /// Adds a test case that ensures that the Zig given in `src` fails to
989 /// compile for the expected reasons, given in sequential order in
990 /// `expected_errors` in the form `:line:column: error: message`.
991 pub fn compileError(
992 ctx: *TestContext,
993 name: []const u8,
994 target: CrossTarget,
995 src: [:0]const u8,
996 expected_errors: []const []const u8,
997 ) void {
998 ctx.addError(name, target, src, expected_errors);
999 }
1000
1001 /// Adds a test case that ensures that the ZIR given in `src` fails to
1002 /// compile for the expected reasons, given in sequential order in
1003 /// `expected_errors` in the form `:line:column: error: message`.
1004 pub fn compileErrorZIR(
1005 ctx: *TestContext,
1006 name: []const u8,
1007 target: CrossTarget,
1008 src: [:0]const u8,
1009 expected_errors: []const []const u8,
1010 ) void {
1011 ctx.addError(name, target, .ZIR, src, expected_errors);
1012 }
1013
1014 pub fn addCompiles(
1015 ctx: *TestContext,
1016 name: []const u8,
1017 target: CrossTarget,
1018 src: [:0]const u8,
1019 ) void {
1020 ctx.addObj(name, target).compiles(src);
1021 }
1022
1023 /// Adds a test case that asserts that the Zig given in `src` compiles
1024 /// without any errors.
1025 pub fn compiles(
1026 ctx: *TestContext,
1027 name: []const u8,
1028 target: CrossTarget,
1029 src: [:0]const u8,
1030 ) void {
1031 ctx.addCompiles(name, target, src);
1032 }
1033
1034 /// Adds a test case that asserts that the ZIR given in `src` compiles
1035 /// without any errors.
1036 pub fn compilesZIR(
1037 ctx: *TestContext,
1038 name: []const u8,
1039 target: CrossTarget,
1040 src: [:0]const u8,
1041 ) void {
1042 ctx.addCompiles(name, target, .ZIR, src);
1043 }
1044
1045 /// Adds a test case that first ensures that the Zig given in `src` fails
1046 /// to compile for the reasons given in sequential order in
1047 /// `expected_errors` in the form `:line:column: error: message`, then
1048 /// asserts that fixing the source (updating with `fixed_src`) isn't broken
1049 /// by incremental compilation.
1050 pub fn incrementalFailure(
1051 ctx: *TestContext,
1052 name: []const u8,
1053 target: CrossTarget,
1054 src: [:0]const u8,
1055 expected_errors: []const []const u8,
1056 fixed_src: [:0]const u8,
1057 ) void {
1058 var case = ctx.addObj(name, target);
1059 case.addError(src, expected_errors);
1060 case.compiles(fixed_src);
1061 }
1062
1063 /// Adds a test case that first ensures that the ZIR given in `src` fails
1064 /// to compile for the reasons given in sequential order in
1065 /// `expected_errors` in the form `:line:column: error: message`, then
1066 /// asserts that fixing the source (updating with `fixed_src`) isn't broken
1067 /// by incremental compilation.
1068 pub fn incrementalFailureZIR(
1069 ctx: *TestContext,
1070 name: []const u8,
1071 target: CrossTarget,
1072 src: [:0]const u8,
1073 expected_errors: []const []const u8,
1074 fixed_src: [:0]const u8,
1075 ) void {
1076 var case = ctx.addObj(name, target, .ZIR);
1077 case.addError(src, expected_errors);
1078 case.compiles(fixed_src);
1079 }
1080
1081 /// Adds a test for each file in the provided directory.
1082 /// Testing strategy (TestStrategy) is inferred automatically from filenames.
1083 /// Recurses nested directories.
1084 ///
1085 /// Each file should include a test manifest as a contiguous block of comments at
1086 /// the end of the file. The first line should be the test type, followed by a set of
1087 /// key-value config values, followed by a blank line, then the expected output.
1088 pub fn addTestCasesFromDir(ctx: *TestContext, dir: std.fs.IterableDir) void {
1089 var current_file: []const u8 = "none";
1090 ctx.addTestCasesFromDirInner(dir, &current_file) catch |err| {
1091 std.debug.panic("test harness failed to process file '{s}': {s}\n", .{
1092 current_file, @errorName(err),
1093 });
1094 };
1095 }
1096
1097 fn addTestCasesFromDirInner(
1098 ctx: *TestContext,
1099 iterable_dir: std.fs.IterableDir,
1100 /// This is kept up to date with the currently being processed file so
1101 /// that if any errors occur the caller knows it happened during this file.
1102 current_file: *[]const u8,
1103 ) !void {
1104 var it = try iterable_dir.walk(ctx.arena);
1105 var filenames = std.ArrayList([]const u8).init(ctx.arena);
1106
1107 while (try it.next()) |entry| {
1108 if (entry.kind != .File) continue;
1109
1110 // Ignore stuff such as .swp files
1111 switch (Compilation.classifyFileExt(entry.basename)) {
1112 .unknown => continue,
1113 else => {},
1114 }
1115 try filenames.append(try ctx.arena.dupe(u8, entry.path));
1116 }
1117
1118 // Sort filenames, so that incremental tests are contiguous and in-order
1119 sortTestFilenames(filenames.items);
1120
1121 var test_it = TestIterator{ .filenames = filenames.items };
1122 while (test_it.next()) |maybe_batch| {
1123 const batch = maybe_batch orelse break;
1124 const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent;
1125 var cases = std.ArrayList(usize).init(ctx.arena);
1126
1127 for (batch) |filename| {
1128 current_file.* = filename;
1129
1130 const max_file_size = 10 * 1024 * 1024;
1131 const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0);
1132
1133 // Parse the manifest
1134 var manifest = try TestManifest.parse(ctx.arena, src);
1135
1136 if (cases.items.len == 0) {
1137 const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend);
1138 const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget);
1139 const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool);
1140 const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode);
1141
1142 const name_prefix = blk: {
1143 const ext_index = std.mem.lastIndexOfScalar(u8, current_file.*, '.') orelse
1144 return error.InvalidFilename;
1145 const index = std.mem.lastIndexOfScalar(u8, current_file.*[0..ext_index], '.') orelse ext_index;
1146 break :blk current_file.*[0..index];
1147 };
1148
1149 // Cross-product to get all possible test combinations
1150 for (backends) |backend| {
1151 for (targets) |target| {
1152 const name = try std.fmt.allocPrint(ctx.arena, "{s} ({s}, {s})", .{
1153 name_prefix,
1154 @tagName(backend),
1155 try target.zigTriple(ctx.arena),
1156 });
1157 const next = ctx.cases.items.len;
1158 try ctx.cases.append(.{
1159 .name = name,
1160 .target = target,
1161 .backend = backend,
1162 .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator),
1163 .is_test = is_test,
1164 .output_mode = output_mode,
1165 .link_libc = backend == .llvm,
1166 .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator),
1167 .deps = std.ArrayList(DepModule).init(ctx.cases.allocator),
1168 });
1169 try cases.append(next);
1170 }
1171 }
1172 }
1173
1174 for (cases.items) |case_index| {
1175 const case = &ctx.cases.items[case_index];
1176 switch (manifest.type) {
1177 .@"error" => {
1178 const errors = try manifest.trailingAlloc(ctx.arena);
1179 switch (strategy) {
1180 .independent => {
1181 case.addError(src, errors);
1182 },
1183 .incremental => {
1184 case.addErrorNamed("update", src, errors);
1185 },
1186 }
1187 },
1188 .run => {
1189 var output = std.ArrayList(u8).init(ctx.arena);
1190 var trailing_it = manifest.trailing();
1191 while (trailing_it.next()) |line| {
1192 try output.appendSlice(line);
1193 try output.append('\n');
1194 }
1195 if (output.items.len > 0) {
1196 try output.resize(output.items.len - 1);
1197 }
1198 case.addCompareOutput(src, try output.toOwnedSlice());
1199 },
1200 .cli => @panic("TODO cli tests"),
1201 }
1202 }
1203 }
1204 } else |err| {
1205 // make sure the current file is set to the file that produced an error
1206 current_file.* = test_it.currentFilename();
1207 return err;
1208 }
1209 }
1210
1211 fn init(gpa: Allocator, arena: Allocator) TestContext {
1212 return .{
1213 .gpa = gpa,
1214 .cases = std.ArrayList(Case).init(gpa),
1215 .arena = arena,
1216 };
1217 }
1218
1219 fn deinit(self: *TestContext) void {
1220 for (self.cases.items) |case| {
1221 for (case.updates.items) |u| {
1222 if (u.case == .Error) {
1223 case.updates.allocator.free(u.case.Error);
1224 }
1225 }
1226 case.updates.deinit();
1227 }
1228 self.cases.deinit();
1229 self.* = undefined;
1230 }
1231
1232 fn run(self: *TestContext) !void {
1233 const host = try std.zig.system.NativeTargetInfo.detect(.{});
1234 const zig_exe_path = try std.process.getEnvVarOwned(self.arena, "ZIG_EXE");
1235
1236 var progress = std.Progress{};
1237 const root_node = progress.start("compiler", self.cases.items.len);
1238 defer root_node.end();
1239
1240 var zig_lib_directory = try introspect.findZigLibDir(self.gpa);
1241 defer zig_lib_directory.handle.close();
1242 defer self.gpa.free(zig_lib_directory.path.?);
1243
1244 var aux_thread_pool: ThreadPool = undefined;
1245 try aux_thread_pool.init(self.gpa);
1246 defer aux_thread_pool.deinit();
1247
1248 // Use the same global cache dir for all the tests, such that we for example don't have to
1249 // rebuild musl libc for every case (when LLVM backend is enabled).
1250 var global_tmp = std.testing.tmpDir(.{});
1251 defer global_tmp.cleanup();
1252
1253 var cache_dir = try global_tmp.dir.makeOpenPath("zig-cache", .{});
1254 defer cache_dir.close();
1255 const tmp_dir_path = try std.fs.path.join(self.gpa, &[_][]const u8{ ".", "zig-cache", "tmp", &global_tmp.sub_path });
1256 defer self.gpa.free(tmp_dir_path);
1257
1258 const global_cache_directory: Compilation.Directory = .{
1259 .handle = cache_dir,
1260 .path = try std.fs.path.join(self.gpa, &[_][]const u8{ tmp_dir_path, "zig-cache" }),
1261 };
1262 defer self.gpa.free(global_cache_directory.path.?);
1263
1264 {
1265 for (self.cases.items) |*case| {
1266 if (build_options.skip_non_native) {
1267 if (case.target.getCpuArch() != builtin.cpu.arch)
1268 continue;
1269 if (case.target.getObjectFormat() != builtin.object_format)
1270 continue;
1271 }
1272
1273 // Skip tests that require LLVM backend when it is not available
1274 if (!build_options.have_llvm and case.backend == .llvm)
1275 continue;
1276
1277 if (skip_stage1 and case.backend == .stage1)
1278 continue;
1279
1280 if (build_options.test_filter) |test_filter| {
1281 if (std.mem.indexOf(u8, case.name, test_filter) == null) continue;
1282 }
1283
1284 var prg_node = root_node.start(case.name, case.updates.items.len);
1285 prg_node.activate();
1286 defer prg_node.end();
1287
1288 case.result = runOneCase(
1289 self.gpa,
1290 &prg_node,
1291 case.*,
1292 zig_lib_directory,
1293 zig_exe_path,
1294 &aux_thread_pool,
1295 global_cache_directory,
1296 host,
1297 );
1298 }
1299 }
1300
1301 var fail_count: usize = 0;
1302 for (self.cases.items) |*case| {
1303 case.result catch |err| {
1304 fail_count += 1;
1305 print("{s} failed: {s}\n", .{ case.name, @errorName(err) });
1306 };
1307 }
1308
1309 if (fail_count != 0) {
1310 print("{d} tests failed\n", .{fail_count});
1311 return error.TestFailed;
1312 }
1313 }
1314
1315 fn runOneCase(
1316 allocator: Allocator,
1317 root_node: *std.Progress.Node,
1318 case: Case,
1319 zig_lib_directory: Compilation.Directory,
1320 zig_exe_path: []const u8,
1321 thread_pool: *ThreadPool,
1322 global_cache_directory: Compilation.Directory,
1323 host: std.zig.system.NativeTargetInfo,
1324 ) !void {
1325 const target_info = try std.zig.system.NativeTargetInfo.detect(case.target);
1326 const target = target_info.target;
1327
1328 var arena_allocator = std.heap.ArenaAllocator.init(allocator);
1329 defer arena_allocator.deinit();
1330 const arena = arena_allocator.allocator();
1331
1332 var tmp = std.testing.tmpDir(.{});
1333 defer tmp.cleanup();
1334
1335 var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{});
1336 defer cache_dir.close();
1337
1338 const tmp_dir_path = try std.fs.path.join(
1339 arena,
1340 &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path },
1341 );
1342 const tmp_dir_path_plus_slash = try std.fmt.allocPrint(
1343 arena,
1344 "{s}" ++ std.fs.path.sep_str,
1345 .{tmp_dir_path},
1346 );
1347 const local_cache_path = try std.fs.path.join(
1348 arena,
1349 &[_][]const u8{ tmp_dir_path, "zig-cache" },
1350 );
1351
1352 for (case.files.items) |file| {
1353 try tmp.dir.writeFile(file.path, file.src);
1354 }
1355
1356 if (case.backend == .stage1) {
1357 // stage1 backend has limitations:
1358 // * leaks memory
1359 // * calls exit() when a compile error happens
1360 // * cannot handle updates
1361 // because of this we must spawn a child process rather than
1362 // using Compilation directly.
1363
1364 if (!std.process.can_spawn) {
1365 print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)});
1366 return; // Pass test.
1367 }
1368
1369 assert(case.updates.items.len == 1);
1370 const update = case.updates.items[0];
1371 try tmp.dir.writeFile(tmp_src_path, update.src);
1372
1373 var zig_args = std.ArrayList([]const u8).init(arena);
1374 try zig_args.append(zig_exe_path);
1375
1376 if (case.is_test) {
1377 try zig_args.append("test");
1378 } else if (update.case == .Execution) {
1379 try zig_args.append("run");
1380 } else switch (case.output_mode) {
1381 .Obj => try zig_args.append("build-obj"),
1382 .Exe => try zig_args.append("build-exe"),
1383 .Lib => try zig_args.append("build-lib"),
1384 }
1385
1386 try zig_args.append(try std.fs.path.join(arena, &.{ tmp_dir_path, tmp_src_path }));
1387
1388 try zig_args.append("--name");
1389 try zig_args.append("test");
1390
1391 try zig_args.append("--cache-dir");
1392 try zig_args.append(local_cache_path);
1393
1394 try zig_args.append("--global-cache-dir");
1395 try zig_args.append(global_cache_directory.path orelse ".");
1396
1397 if (!case.target.isNative()) {
1398 try zig_args.append("-target");
1399 try zig_args.append(try target.zigTriple(arena));
1400 }
1401
1402 try zig_args.append("-O");
1403 try zig_args.append(@tagName(case.optimize_mode));
1404
1405 // Prevent sub-process progress bar from interfering with the
1406 // one in this parent process.
1407 try zig_args.append("--color");
1408 try zig_args.append("off");
1409
1410 const result = try std.ChildProcess.exec(.{
1411 .allocator = arena,
1412 .argv = zig_args.items,
1413 });
1414 switch (update.case) {
1415 .Error => |case_error_list| {
1416 switch (result.term) {
1417 .Exited => |code| {
1418 if (code == 0) {
1419 dumpArgs(zig_args.items);
1420 return error.CompilationIncorrectlySucceeded;
1421 }
1422 },
1423 else => {
1424 std.debug.print("{s}", .{result.stderr});
1425 dumpArgs(zig_args.items);
1426 return error.CompilationCrashed;
1427 },
1428 }
1429 var ok = true;
1430 if (case.expect_exact) {
1431 var err_iter = std.mem.split(u8, result.stderr, "\n");
1432 var i: usize = 0;
1433 ok = while (err_iter.next()) |line| : (i += 1) {
1434 if (i >= case_error_list.len) break false;
1435 const expected = try std.mem.replaceOwned(
1436 u8,
1437 arena,
1438 try std.fmt.allocPrint(arena, "{s}", .{case_error_list[i]}),
1439 "${DIR}",
1440 tmp_dir_path_plus_slash,
1441 );
1442
1443 if (std.mem.indexOf(u8, line, expected) == null) break false;
1444 continue;
1445 } else true;
1446
1447 ok = ok and i == case_error_list.len;
1448
1449 if (!ok) {
1450 print("\n======== Expected these compile errors: ========\n", .{});
1451 for (case_error_list) |msg| {
1452 const expected = try std.fmt.allocPrint(arena, "{s}", .{msg});
1453 print("{s}\n", .{expected});
1454 }
1455 }
1456 } else {
1457 for (case_error_list) |msg| {
1458 const expected = try std.mem.replaceOwned(
1459 u8,
1460 arena,
1461 try std.fmt.allocPrint(arena, "{s}", .{msg}),
1462 "${DIR}",
1463 tmp_dir_path_plus_slash,
1464 );
1465 if (std.mem.indexOf(u8, result.stderr, expected) == null) {
1466 print(
1467 \\
1468 \\=========== Expected compile error: ============
1469 \\{s}
1470 \\
1471 , .{expected});
1472 ok = false;
1473 break;
1474 }
1475 }
1476 }
1477
1478 if (!ok) {
1479 print(
1480 \\================= Full output: =================
1481 \\{s}
1482 \\================================================
1483 \\
1484 , .{result.stderr});
1485 return error.TestFailed;
1486 }
1487 },
1488 .CompareObjectFile => @panic("TODO implement in the test harness"),
1489 .Execution => |expected_stdout| {
1490 switch (result.term) {
1491 .Exited => |code| {
1492 if (code != 0) {
1493 std.debug.print("{s}", .{result.stderr});
1494 dumpArgs(zig_args.items);
1495 return error.CompilationFailed;
1496 }
1497 },
1498 else => {
1499 std.debug.print("{s}", .{result.stderr});
1500 dumpArgs(zig_args.items);
1501 return error.CompilationCrashed;
1502 },
1503 }
1504 try std.testing.expectEqualStrings("", result.stderr);
1505 try std.testing.expectEqualStrings(expected_stdout, result.stdout);
1506 },
1507 .Header => @panic("TODO implement in the test harness"),
1508 }
1509 return;
1510 }
1511
1512 const zig_cache_directory: Compilation.Directory = .{
1513 .handle = cache_dir,
1514 .path = local_cache_path,
1515 };
1516
1517 var main_pkg: Package = .{
1518 .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir },
1519 .root_src_path = tmp_src_path,
1520 };
1521 defer {
1522 var it = main_pkg.table.iterator();
1523 while (it.next()) |kv| {
1524 allocator.free(kv.key_ptr.*);
1525 kv.value_ptr.*.destroy(allocator);
1526 }
1527 main_pkg.table.deinit(allocator);
1528 }
1529
1530 for (case.deps.items) |dep| {
1531 var pkg = try Package.create(
1532 allocator,
1533 tmp_dir_path,
1534 dep.path,
1535 );
1536 errdefer pkg.destroy(allocator);
1537 try main_pkg.add(allocator, dep.name, pkg);
1538 }
1539
1540 const bin_name = try std.zig.binNameAlloc(arena, .{
1541 .root_name = "test_case",
1542 .target = target,
1543 .output_mode = case.output_mode,
1544 });
1545
1546 const emit_directory: Compilation.Directory = .{
1547 .path = tmp_dir_path,
1548 .handle = tmp.dir,
1549 };
1550 const emit_bin: Compilation.EmitLoc = .{
1551 .directory = emit_directory,
1552 .basename = bin_name,
1553 };
1554 const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{
1555 .directory = emit_directory,
1556 .basename = "test_case.h",
1557 } else null;
1558 const use_llvm: bool = switch (case.backend) {
1559 .llvm => true,
1560 else => false,
1561 };
1562 const comp = try Compilation.create(allocator, .{
1563 .local_cache_directory = zig_cache_directory,
1564 .global_cache_directory = global_cache_directory,
1565 .zig_lib_directory = zig_lib_directory,
1566 .thread_pool = thread_pool,
1567 .root_name = "test_case",
1568 .target = target,
1569 // TODO: support tests for object file building, and library builds
1570 // and linking. This will require a rework to support multi-file
1571 // tests.
1572 .output_mode = case.output_mode,
1573 .is_test = case.is_test,
1574 .optimize_mode = case.optimize_mode,
1575 .emit_bin = emit_bin,
1576 .emit_h = emit_h,
1577 .main_pkg = &main_pkg,
1578 .keep_source_files_loaded = true,
1579 .is_native_os = case.target.isNativeOs(),
1580 .is_native_abi = case.target.isNativeAbi(),
1581 .dynamic_linker = target_info.dynamic_linker.get(),
1582 .link_libc = case.link_libc,
1583 .use_llvm = use_llvm,
1584 .self_exe_path = zig_exe_path,
1585 // TODO instead of turning off color, pass in a std.Progress.Node
1586 .color = .off,
1587 .reference_trace = 0,
1588 // TODO: force self-hosted linkers with stage2 backend to avoid LLD creeping in
1589 // until the auto-select mechanism deems them worthy
1590 .use_lld = switch (case.backend) {
1591 .stage2 => false,
1592 else => null,
1593 },
1594 });
1595 defer comp.destroy();
1596
1597 update: for (case.updates.items, 0..) |update, update_index| {
1598 var update_node = root_node.start(update.name, 3);
1599 update_node.activate();
1600 defer update_node.end();
1601
1602 var sync_node = update_node.start("write", 0);
1603 sync_node.activate();
1604 try tmp.dir.writeFile(tmp_src_path, update.src);
1605 sync_node.end();
1606
1607 var module_node = update_node.start("parse/analysis/codegen", 0);
1608 module_node.activate();
1609 module_node.context.refresh();
1610 try comp.makeBinFileWritable();
1611 try comp.update();
1612 module_node.end();
1613
1614 if (update.case != .Error) {
1615 var all_errors = try comp.getAllErrorsAlloc();
1616 defer all_errors.deinit(allocator);
1617 if (all_errors.list.len != 0) {
1618 print(
1619 "\nCase '{s}': unexpected errors at update_index={d}:\n{s}\n",
1620 .{ case.name, update_index, hr },
1621 );
1622 for (all_errors.list) |err_msg| {
1623 switch (err_msg) {
1624 .src => |src| {
1625 print("{s}:{d}:{d}: error: {s}\n{s}\n", .{
1626 src.src_path, src.line + 1, src.column + 1, src.msg, hr,
1627 });
1628 },
1629 .plain => |plain| {
1630 print("error: {s}\n{s}\n", .{ plain.msg, hr });
1631 },
1632 }
1633 }
1634 // TODO print generated C code
1635 return error.UnexpectedCompileErrors;
1636 }
1637 }
1638
1639 switch (update.case) {
1640 .Header => |expected_output| {
1641 var file = try tmp.dir.openFile("test_case.h", .{ .mode = .read_only });
1642 defer file.close();
1643 const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024);
1644
1645 try std.testing.expectEqualStrings(expected_output, out);
1646 },
1647 .CompareObjectFile => |expected_output| {
1648 var file = try tmp.dir.openFile(bin_name, .{ .mode = .read_only });
1649 defer file.close();
1650 const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024);
1651
1652 try std.testing.expectEqualStrings(expected_output, out);
1653 },
1654 .Error => |case_error_list| {
1655 var test_node = update_node.start("assert", 0);
1656 test_node.activate();
1657 defer test_node.end();
1658
1659 const handled_errors = try arena.alloc(bool, case_error_list.len);
1660 std.mem.set(bool, handled_errors, false);
1661
1662 var actual_errors = try comp.getAllErrorsAlloc();
1663 defer actual_errors.deinit(allocator);
1664
1665 var any_failed = false;
1666 var notes_to_check = std.ArrayList(*const Compilation.AllErrors.Message).init(allocator);
1667 defer notes_to_check.deinit();
1668
1669 for (actual_errors.list) |actual_error| {
1670 for (case_error_list, 0..) |case_msg, i| {
1671 if (handled_errors[i]) continue;
1672
1673 const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg;
1674 switch (actual_error) {
1675 .src => |actual_msg| {
1676 for (actual_msg.notes) |*note| {
1677 try notes_to_check.append(note);
1678 }
1679
1680 if (ex_tag != .src) continue;
1681
1682 const src_path_ok = case_msg.src.src_path.len == 0 or
1683 std.mem.eql(u8, case_msg.src.src_path, actual_msg.src_path);
1684
1685 const expected_msg = try std.mem.replaceOwned(
1686 u8,
1687 arena,
1688 case_msg.src.msg,
1689 "${DIR}",
1690 tmp_dir_path_plus_slash,
1691 );
1692
1693 var buf: [1024]u8 = undefined;
1694 const rendered_msg = blk: {
1695 var msg: Compilation.AllErrors.Message = actual_error;
1696 msg.src.src_path = case_msg.src.src_path;
1697 msg.src.notes = &.{};
1698 msg.src.source_line = null;
1699 var fib = std.io.fixedBufferStream(&buf);
1700 try msg.renderToWriter(.no_color, fib.writer(), "error", .Red, 0);
1701 var it = std.mem.split(u8, fib.getWritten(), "error: ");
1702 _ = it.first();
1703 const rendered = it.rest();
1704 break :blk rendered[0 .. rendered.len - 1]; // trim final newline
1705 };
1706
1707 if (src_path_ok and
1708 (case_msg.src.line == std.math.maxInt(u32) or
1709 actual_msg.line == case_msg.src.line) and
1710 (case_msg.src.column == std.math.maxInt(u32) or
1711 actual_msg.column == case_msg.src.column) and
1712 std.mem.eql(u8, expected_msg, rendered_msg) and
1713 case_msg.src.kind == .@"error" and
1714 actual_msg.count == case_msg.src.count)
1715 {
1716 handled_errors[i] = true;
1717 break;
1718 }
1719 },
1720 .plain => |plain| {
1721 if (ex_tag != .plain) continue;
1722
1723 if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and
1724 case_msg.plain.kind == .@"error" and
1725 case_msg.plain.count == plain.count)
1726 {
1727 handled_errors[i] = true;
1728 break;
1729 }
1730 },
1731 }
1732 } else {
1733 print(
1734 "\nUnexpected error:\n{s}\n{}\n{s}",
1735 .{ hr, ErrorMsg.init(actual_error, .@"error"), hr },
1736 );
1737 any_failed = true;
1738 }
1739 }
1740 while (notes_to_check.popOrNull()) |note| {
1741 for (case_error_list, 0..) |case_msg, i| {
1742 const ex_tag: std.meta.Tag(@TypeOf(case_msg)) = case_msg;
1743 switch (note.*) {
1744 .src => |actual_msg| {
1745 for (actual_msg.notes) |*sub_note| {
1746 try notes_to_check.append(sub_note);
1747 }
1748 if (ex_tag != .src) continue;
1749
1750 const expected_msg = try std.mem.replaceOwned(
1751 u8,
1752 arena,
1753 case_msg.src.msg,
1754 "${DIR}",
1755 tmp_dir_path_plus_slash,
1756 );
1757
1758 if ((case_msg.src.line == std.math.maxInt(u32) or
1759 actual_msg.line == case_msg.src.line) and
1760 (case_msg.src.column == std.math.maxInt(u32) or
1761 actual_msg.column == case_msg.src.column) and
1762 std.mem.eql(u8, expected_msg, actual_msg.msg) and
1763 case_msg.src.kind == .note and
1764 actual_msg.count == case_msg.src.count)
1765 {
1766 handled_errors[i] = true;
1767 break;
1768 }
1769 },
1770 .plain => |plain| {
1771 if (ex_tag != .plain) continue;
1772
1773 if (std.mem.eql(u8, case_msg.plain.msg, plain.msg) and
1774 case_msg.plain.kind == .note and
1775 case_msg.plain.count == plain.count)
1776 {
1777 handled_errors[i] = true;
1778 break;
1779 }
1780 },
1781 }
1782 } else {
1783 print(
1784 "\nUnexpected note:\n{s}\n{}\n{s}",
1785 .{ hr, ErrorMsg.init(note.*, .note), hr },
1786 );
1787 any_failed = true;
1788 }
1789 }
1790
1791 for (handled_errors, 0..) |handled, i| {
1792 if (!handled) {
1793 print(
1794 "\nExpected error not found:\n{s}\n{}\n{s}",
1795 .{ hr, case_error_list[i], hr },
1796 );
1797 any_failed = true;
1798 }
1799 }
1800
1801 if (any_failed) {
1802 print("\nupdate_index={d}\n", .{update_index});
1803 return error.WrongCompileErrors;
1804 }
1805 },
1806 .Execution => |expected_stdout| {
1807 if (!std.process.can_spawn) {
1808 print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)});
1809 continue :update; // Pass test.
1810 }
1811
1812 update_node.setEstimatedTotalItems(4);
1813
1814 var argv = std.ArrayList([]const u8).init(allocator);
1815 defer argv.deinit();
1816
1817 var exec_result = x: {
1818 var exec_node = update_node.start("execute", 0);
1819 exec_node.activate();
1820 defer exec_node.end();
1821
1822 // We go out of our way here to use the unique temporary directory name in
1823 // the exe_path so that it makes its way into the cache hash, avoiding
1824 // cache collisions from multiple threads doing `zig run` at the same time
1825 // on the same test_case.c input filename.
1826 const ss = std.fs.path.sep_str;
1827 const exe_path = try std.fmt.allocPrint(
1828 arena,
1829 ".." ++ ss ++ "{s}" ++ ss ++ "{s}",
1830 .{ &tmp.sub_path, bin_name },
1831 );
1832 if (case.target.ofmt != null and case.target.ofmt.? == .c) {
1833 if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) {
1834 // We wouldn't be able to run the compiled C code.
1835 continue :update; // Pass test.
1836 }
1837 try argv.appendSlice(&[_][]const u8{
1838 zig_exe_path,
1839 "run",
1840 "-cflags",
1841 "-std=c99",
1842 "-pedantic",
1843 "-Werror",
1844 "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875
1845 "--",
1846 "-lc",
1847 exe_path,
1848 });
1849 if (zig_lib_directory.path) |p| {
1850 try argv.appendSlice(&.{ "-I", p });
1851 }
1852 } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) {
1853 .native => {
1854 if (case.backend == .stage2 and case.target.getCpuArch() == .arm) {
1855 // https://github.com/ziglang/zig/issues/13623
1856 continue :update; // Pass test.
1857 }
1858 try argv.append(exe_path);
1859 },
1860 .bad_dl, .bad_os_or_cpu => continue :update, // Pass test.
1861
1862 .rosetta => if (enable_rosetta) {
1863 try argv.append(exe_path);
1864 } else {
1865 continue :update; // Rosetta not available, pass test.
1866 },
1867
1868 .qemu => |qemu_bin_name| if (enable_qemu) {
1869 const need_cross_glibc = target.isGnuLibC() and case.link_libc;
1870 const glibc_dir_arg: ?[]const u8 = if (need_cross_glibc)
1871 glibc_runtimes_dir orelse continue :update // glibc dir not available; pass test
1872 else
1873 null;
1874 try argv.append(qemu_bin_name);
1875 if (glibc_dir_arg) |dir| {
1876 const linux_triple = try target.linuxTriple(arena);
1877 const full_dir = try std.fs.path.join(arena, &[_][]const u8{
1878 dir,
1879 linux_triple,
1880 });
1881
1882 try argv.append("-L");
1883 try argv.append(full_dir);
1884 }
1885 try argv.append(exe_path);
1886 } else {
1887 continue :update; // QEMU not available; pass test.
1888 },
1889
1890 .wine => |wine_bin_name| if (enable_wine) {
1891 try argv.append(wine_bin_name);
1892 try argv.append(exe_path);
1893 } else {
1894 continue :update; // Wine not available; pass test.
1895 },
1896
1897 .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {
1898 try argv.append(wasmtime_bin_name);
1899 try argv.append("--dir=.");
1900 try argv.append(exe_path);
1901 } else {
1902 continue :update; // wasmtime not available; pass test.
1903 },
1904
1905 .darling => |darling_bin_name| if (enable_darling) {
1906 try argv.append(darling_bin_name);
1907 // Since we use relative to cwd here, we invoke darling with
1908 // "shell" subcommand.
1909 try argv.append("shell");
1910 try argv.append(exe_path);
1911 } else {
1912 continue :update; // Darling not available; pass test.
1913 },
1914 }
1915
1916 try comp.makeBinFileExecutable();
1917
1918 while (true) {
1919 break :x std.ChildProcess.exec(.{
1920 .allocator = allocator,
1921 .argv = argv.items,
1922 .cwd_dir = tmp.dir,
1923 .cwd = tmp_dir_path,
1924 }) catch |err| switch (err) {
1925 error.FileBusy => {
1926 // There is a fundamental design flaw in Unix systems with how
1927 // ETXTBSY interacts with fork+exec.
1928 // https://github.com/golang/go/issues/22315
1929 // https://bugs.openjdk.org/browse/JDK-8068370
1930 // Unfortunately, this could be a real error, but we can't
1931 // tell the difference here.
1932 continue;
1933 },
1934 else => {
1935 print("\n{s}.{d} The following command failed with {s}:\n", .{
1936 case.name, update_index, @errorName(err),
1937 });
1938 dumpArgs(argv.items);
1939 return error.ChildProcessExecution;
1940 },
1941 };
1942 }
1943 };
1944 var test_node = update_node.start("test", 0);
1945 test_node.activate();
1946 defer test_node.end();
1947 defer allocator.free(exec_result.stdout);
1948 defer allocator.free(exec_result.stderr);
1949 switch (exec_result.term) {
1950 .Exited => |code| {
1951 if (code != 0) {
1952 print("\n{s}\n{s}: execution exited with code {d}:\n", .{
1953 exec_result.stderr, case.name, code,
1954 });
1955 dumpArgs(argv.items);
1956 return error.ChildProcessExecution;
1957 }
1958 },
1959 else => {
1960 print("\n{s}\n{s}: execution crashed:\n", .{
1961 exec_result.stderr, case.name,
1962 });
1963 dumpArgs(argv.items);
1964 return error.ChildProcessExecution;
1965 },
1966 }
1967 try std.testing.expectEqualStrings(expected_stdout, exec_result.stdout);
1968 // We allow stderr to have garbage in it because wasmtime prints a
1969 // warning about --invoke even though we don't pass it.
1970 //std.testing.expectEqualStrings("", exec_result.stderr);
1971 },
1972 }
1973 }
1974 }
1975};
1976
1977fn dumpArgs(argv: []const []const u8) void {
1978 for (argv) |arg| {
1979 print("{s} ", .{arg});
1980 }
1981 print("\n", .{});
1982}
1983
1984const tmp_src_path = "tmp.zig";
src/wasi_libc.zig+8-8
......@@ -59,7 +59,7 @@ pub fn execModelCrtFileFullName(wasi_exec_model: std.builtin.WasiExecModel) []co
5959 };
6060}
6161
62pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
62pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
6363 if (!build_options.have_llvm) {
6464 return error.ZigCompilerNotBuiltWithLLVMExtensions;
6565 }
......@@ -74,7 +74,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
7474 var args = std.ArrayList([]const u8).init(arena);
7575 try addCCArgs(comp, arena, &args, false);
7676 try addLibcBottomHalfIncludes(comp, arena, &args);
77 return comp.build_crt_file("crt1-reactor", .Obj, &[1]Compilation.CSourceFile{
77 return comp.build_crt_file("crt1-reactor", .Obj, .@"wasi crt1-reactor.o", prog_node, &.{
7878 .{
7979 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
8080 "libc", try sanitize(arena, crt1_reactor_src_file),
......@@ -87,7 +87,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
8787 var args = std.ArrayList([]const u8).init(arena);
8888 try addCCArgs(comp, arena, &args, false);
8989 try addLibcBottomHalfIncludes(comp, arena, &args);
90 return comp.build_crt_file("crt1-command", .Obj, &[1]Compilation.CSourceFile{
90 return comp.build_crt_file("crt1-command", .Obj, .@"wasi crt1-command.o", prog_node, &.{
9191 .{
9292 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
9393 "libc", try sanitize(arena, crt1_command_src_file),
......@@ -145,7 +145,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
145145 }
146146 }
147147
148 try comp.build_crt_file("c", .Lib, libc_sources.items);
148 try comp.build_crt_file("c", .Lib, .@"wasi libc.a", prog_node, libc_sources.items);
149149 },
150150 .libwasi_emulated_process_clocks_a => {
151151 var args = std.ArrayList([]const u8).init(arena);
......@@ -161,7 +161,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
161161 .extra_flags = args.items,
162162 });
163163 }
164 try comp.build_crt_file("wasi-emulated-process-clocks", .Lib, emu_clocks_sources.items);
164 try comp.build_crt_file("wasi-emulated-process-clocks", .Lib, .@"libwasi-emulated-process-clocks.a", prog_node, emu_clocks_sources.items);
165165 },
166166 .libwasi_emulated_getpid_a => {
167167 var args = std.ArrayList([]const u8).init(arena);
......@@ -177,7 +177,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
177177 .extra_flags = args.items,
178178 });
179179 }
180 try comp.build_crt_file("wasi-emulated-getpid", .Lib, emu_getpid_sources.items);
180 try comp.build_crt_file("wasi-emulated-getpid", .Lib, .@"libwasi-emulated-getpid.a", prog_node, emu_getpid_sources.items);
181181 },
182182 .libwasi_emulated_mman_a => {
183183 var args = std.ArrayList([]const u8).init(arena);
......@@ -193,7 +193,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
193193 .extra_flags = args.items,
194194 });
195195 }
196 try comp.build_crt_file("wasi-emulated-mman", .Lib, emu_mman_sources.items);
196 try comp.build_crt_file("wasi-emulated-mman", .Lib, .@"libwasi-emulated-mman.a", prog_node, emu_mman_sources.items);
197197 },
198198 .libwasi_emulated_signal_a => {
199199 var emu_signal_sources = std.ArrayList(Compilation.CSourceFile).init(arena);
......@@ -228,7 +228,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
228228 }
229229 }
230230
231 try comp.build_crt_file("wasi-emulated-signal", .Lib, emu_signal_sources.items);
231 try comp.build_crt_file("wasi-emulated-signal", .Lib, .@"libwasi-emulated-signal.a", prog_node, emu_signal_sources.items);
232232 },
233233 }
234234}
test/behavior/array.zig+1
......@@ -84,6 +84,7 @@ test "array concat with tuple" {
8484}
8585
8686test "array init with concat" {
87 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
8788 const a = 'a';
8889 var i: [4]u8 = [2]u8{ a, 'b' } ++ [2]u8{ 'c', 'd' };
8990 try expect(std.mem.eql(u8, &i, "abcd"));
test/behavior/ptrcast.zig+2
......@@ -170,6 +170,7 @@ test "lower reinterpreted comptime field ptr" {
170170
171171test "reinterpret struct field at comptime" {
172172 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
173 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
173174
174175 const numNative = comptime Bytes.init(0x12345678);
175176 if (native_endian != .Little) {
......@@ -232,6 +233,7 @@ test "ptrcast of const integer has the correct object size" {
232233test "implicit optional pointer to optional anyopaque pointer" {
233234 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
234235 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
236 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
235237
236238 var buf: [4]u8 = "aoeu".*;
237239 var x: ?[*]u8 = &buf;
test/behavior/slice.zig+1
......@@ -227,6 +227,7 @@ fn sliceFromLenToLen(a_slice: []u8, start: usize, end: usize) []u8 {
227227
228228test "C pointer" {
229229 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
230 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
230231
231232 var buf: [*c]const u8 = "kjdhfkjdhfdkjhfkfjhdfkjdhfkdjhfdkjhf";
232233 var len: u32 = 10;
test/cases.zig+5-5
......@@ -1,8 +1,8 @@
11const std = @import("std");
2const TestContext = @import("../src/test.zig").TestContext;
2const Cases = @import("src/Cases.zig");
33
4pub fn addCases(ctx: *TestContext) !void {
5 try @import("compile_errors.zig").addCases(ctx);
6 try @import("stage2/cbe.zig").addCases(ctx);
7 try @import("stage2/nvptx.zig").addCases(ctx);
4pub fn addCases(cases: *Cases) !void {
5 try @import("compile_errors.zig").addCases(cases);
6 try @import("cbe.zig").addCases(cases);
7 try @import("nvptx.zig").addCases(cases);
88}
test/cases/compile_errors/access_inactive_union_field_comptime.zig+1
......@@ -21,3 +21,4 @@ pub export fn entry1() void {
2121// :9:15: error: access of union field 'a' while field 'b' is active
2222// :2:21: note: union declared here
2323// :14:16: error: access of union field 'a' while field 'b' is active
24// :2:21: note: union declared here
test/cases/compile_errors/bad_import.zig+1-1
......@@ -4,4 +4,4 @@ const bogus = @import("bogus-does-not-exist.zig",);
44// backend=stage2
55// target=native
66//
7// :1:23: error: unable to load '${DIR}bogus-does-not-exist.zig': FileNotFound
7// bogus-does-not-exist.zig': FileNotFound
test/cases/compile_errors/compileLog_of_tagged_enum_doesnt_crash_the_compiler.zig+4
......@@ -15,3 +15,7 @@ pub export fn entry() void {
1515// target=native
1616//
1717// :6:5: error: found compile log statement
18//
19// Compile Log Output:
20// @as(tmp.Bar, .{ .X = 123 })
21// @as(tmp.Bar, [runtime value])
test/cases/compile_errors/compile_log.zig+9
......@@ -17,3 +17,12 @@ export fn baz() void {
1717//
1818// :5:5: error: found compile log statement
1919// :11:5: note: also here
20//
21// Compile Log Output:
22// @as(*const [5:0]u8, "begin")
23// @as(*const [1:0]u8, "a"), @as(i32, 12), @as(*const [1:0]u8, "b"), @as([]const u8, "hi")
24// @as(*const [3:0]u8, "end")
25// @as(comptime_int, 4)
26// @as(*const [5:0]u8, "begin")
27// @as(*const [1:0]u8, "a"), @as(i32, [runtime value]), @as(*const [1:0]u8, "b"), @as([]const u8, [runtime value])
28// @as(*const [3:0]u8, "end")
test/cases/compile_errors/compile_log_a_pointer_to_an_opaque_value.zig+4-1
......@@ -1,5 +1,5 @@
11export fn entry() void {
2 @compileLog(@ptrCast(*const anyopaque, &entry));
2 @compileLog(@as(*align(1) const anyopaque, @ptrCast(*const anyopaque, &entry)));
33}
44
55// error
......@@ -7,3 +7,6 @@ export fn entry() void {
77// target=native
88//
99// :2:5: error: found compile log statement
10//
11// Compile Log Output:
12// @as(*const anyopaque, (function 'entry'))
test/cases/compile_errors/compile_log_statement_inside_function_which_must_be_comptime_evaluated.zig+3
......@@ -12,3 +12,6 @@ export fn entry() void {
1212// target=native
1313//
1414// :2:5: error: found compile log statement
15//
16// Compile Log Output:
17// @as(*const [3:0]u8, "i32\x00")
test/cases/compile_errors/compile_log_statement_warning_deduplication_in_generic_fn.zig+5
......@@ -13,3 +13,8 @@ fn inner(comptime n: usize) void {
1313//
1414// :7:39: error: found compile log statement
1515// :7:39: note: also here
16//
17// Compile Log Output:
18// @as(*const [4:0]u8, "!@#$")
19// @as(*const [4:0]u8, "!@#$")
20// @as(*const [4:0]u8, "!@#$")
test/cases/compile_errors/condition_comptime_reason_explained.zig+2
......@@ -45,4 +45,6 @@ pub export fn entry2() void {
4545// :22:13: error: unable to resolve comptime value
4646// :22:13: note: condition in comptime switch must be comptime-known
4747// :21:17: note: expression is evaluated at comptime because the function returns a comptime-only type 'tmp.S'
48// :2:12: note: struct requires comptime because of this field
49// :2:12: note: use '*const fn() void' for a function pointer type
4850// :32:19: note: called from here
test/cases/compile_errors/directly_embedding_opaque_type_in_struct_and_union.zig+1
......@@ -32,6 +32,7 @@ export fn d() void {
3232// :3:8: error: opaque types have unknown size and therefore cannot be directly embedded in structs
3333// :1:11: note: opaque declared here
3434// :7:10: error: opaque types have unknown size and therefore cannot be directly embedded in unions
35// :1:11: note: opaque declared here
3536// :19:18: error: opaque types have unknown size and therefore cannot be directly embedded in structs
3637// :18:22: note: opaque declared here
3738// :24:23: error: opaque types have unknown size and therefore cannot be directly embedded in structs
test/cases/compile_errors/extern_function_with_comptime_parameter.zig+1-1
......@@ -12,6 +12,6 @@ comptime { _ = entry2; }
1212// backend=stage2
1313// target=native
1414//
15// :1:15: error: comptime parameters not allowed in function with calling convention 'C'
1615// :5:30: error: comptime parameters not allowed in function with calling convention 'C'
1716// :6:30: error: generic parameters not allowed in function with calling convention 'C'
17// :1:15: error: comptime parameters not allowed in function with calling convention 'C'
test/cases/compile_errors/function_parameter_is_opaque.zig+1
......@@ -27,4 +27,5 @@ export fn entry4() void {
2727// :1:17: note: opaque declared here
2828// :8:28: error: parameter of type '@TypeOf(null)' not allowed
2929// :12:8: error: parameter of opaque type 'tmp.FooType' not allowed
30// :1:17: note: opaque declared here
3031// :17:8: error: parameter of type '@TypeOf(null)' not allowed
test/cases/compile_errors/helpful_return_type_error_message.zig+1-1
......@@ -24,9 +24,9 @@ export fn quux() u32 {
2424// :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set'
2525// :7:17: note: function cannot return an error
2626// :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32'
27// :10:17: note: function cannot return an error
2827// :11:15: note: cannot convert error union to payload type
2928// :11:15: note: consider using 'try', 'catch', or 'if'
29// :10:17: note: function cannot return an error
3030// :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32'
3131// :15:14: note: cannot convert error union to payload type
3232// :15:14: note: consider using 'try', 'catch', or 'if'
test/cases/compile_errors/implicit_semicolon-block_expr.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-block_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-comptime_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = comptime {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-comptime_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 comptime ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-defer.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 defer ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-for_expression.zig+3
......@@ -3,7 +3,10 @@ export fn entry() void {
33 var good = {};
44 _ = for(foo()) |_| {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
9fn foo() void {}
710
811// error
912// backend=stage2
test/cases/compile_errors/implicit_semicolon-for_statement.zig+3
......@@ -3,7 +3,10 @@ export fn entry() void {
33 var good = {};
44 for(foo()) |_| ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
9fn foo() void {}
710
811// error
912// backend=stage2
test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = if(true) {} else if(true) {} else {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 if(true) ({}) else if(true) ({}) else ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = if(true) {} else if(true) {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 if(true) ({}) else if(true) ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = if(true) {} else {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if-else_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 if(true) ({}) else ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = if(true) {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-if_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 if(true) ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-test_expression.zig+3
......@@ -3,7 +3,10 @@ export fn entry() void {
33 var good = {};
44 _ = if (foo()) |_| {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
9fn foo() void {}
710
811// error
912// backend=stage2
test/cases/compile_errors/implicit_semicolon-test_statement.zig+3
......@@ -3,7 +3,10 @@ export fn entry() void {
33 var good = {};
44 if (foo()) |_| ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
9fn foo() void {}
710
811// error
912// backend=stage2
test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = while(true):({}) {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 while(true):({}) ({})
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-while_expression.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 _ = while(true) {}
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/implicit_semicolon-while_statement.zig+2
......@@ -3,6 +3,8 @@ export fn entry() void {
33 var good = {};
44 while(true) 1
55 var bad = {};
6 _ = good;
7 _ = bad;
68}
79
810// error
test/cases/compile_errors/invalid_member_of_builtin_enum.zig+1-1
......@@ -9,4 +9,4 @@ export fn entry() void {
99// target=native
1010//
1111// :3:38: error: enum 'builtin.OptimizeMode' has no member named 'x86'
12// :?:18: note: enum declared here
12// : note: enum declared here
test/cases/compile_errors/invalid_store_to_comptime_field.zig+1-1
......@@ -73,11 +73,11 @@ pub export fn entry8() void {
7373//
7474// :6:19: error: value stored in comptime field does not match the default value of the field
7575// :14:19: error: value stored in comptime field does not match the default value of the field
76// :53:16: error: value stored in comptime field does not match the default value of the field
7776// :19:38: error: value stored in comptime field does not match the default value of the field
7877// :31:19: error: value stored in comptime field does not match the default value of the field
7978// :25:29: note: default value set here
8079// :41:16: error: value stored in comptime field does not match the default value of the field
8180// :45:12: error: value stored in comptime field does not match the default value of the field
81// :53:16: error: value stored in comptime field does not match the default value of the field
8282// :66:43: error: value stored in comptime field does not match the default value of the field
8383// :59:35: error: value stored in comptime field does not match the default value of the field
test/cases/compile_errors/invalid_struct_field.zig+1
......@@ -25,5 +25,6 @@ export fn e() void {
2525// :4:7: error: no field named 'foo' in struct 'tmp.A'
2626// :1:11: note: struct declared here
2727// :10:17: error: no field named 'bar' in struct 'tmp.A'
28// :1:11: note: struct declared here
2829// :18:45: error: no field named 'f' in struct 'tmp.e.B'
2930// :14:15: note: struct declared here
test/cases/compile_errors/missing_main_fn_in_executable.zig+4-2
......@@ -5,5 +5,7 @@
55// target=x86_64-linux
66// output_mode=Exe
77//
8// :?:?: error: root struct of file 'tmp' has no member named 'main'
9// :?:?: note: called from here
8// : error: root struct of file 'tmp' has no member named 'main'
9// : note: called from here
10// : note: called from here
11// : note: called from here
test/cases/compile_errors/private_main_fn.zig+4-2
......@@ -5,6 +5,8 @@ fn main() void {}
55// target=x86_64-linux
66// output_mode=Exe
77//
8// :?:?: error: 'main' is not marked 'pub'
8// : error: 'main' is not marked 'pub'
99// :1:1: note: declared here
10// :?:?: note: called from here
10// : note: called from here
11// : note: called from here
12// : note: called from here
test/cases/compile_errors/runtime_index_into_comptime_type_slice.zig+3-2
......@@ -15,5 +15,6 @@ export fn entry() void {
1515// target=native
1616//
1717// :9:51: error: values of type '[]const builtin.Type.StructField' must be comptime-known, but index value is runtime-known
18// :?:21: note: struct requires comptime because of this field
19// :?:21: note: types are not available at runtime
18// : note: struct requires comptime because of this field
19// : note: types are not available at runtime
20// : struct requires comptime because of this field
test/cases/compile_errors/struct_type_mismatch_in_arg.zig+1-1
......@@ -13,6 +13,6 @@ comptime {
1313// target=native
1414//
1515// :7:16: error: expected type 'tmp.Foo', found 'tmp.Bar'
16// :1:13: note: struct declared here
1716// :2:13: note: struct declared here
17// :1:13: note: struct declared here
1818// :4:18: note: parameter type declared here
test/cases/compile_errors/undefined_as_field_type_is_rejected.zig+8-4
......@@ -1,9 +1,13 @@
1export fn a() void {
2 b();
1const Foo = struct {
2 a: undefined,
3};
4export fn entry1() void {
5 const foo: Foo = undefined;
6 _ = foo;
37}
48
59// error
6// backend=stage2
10// backend=stage1
711// target=native
812//
9// :2:5: error: use of undeclared identifier 'b'
13// tmp.zig:2:8: error: use of undefined value here causes undefined behavior
test/cases/compile_errors/union_init_with_none_or_multiple_fields.zig+2-1
......@@ -28,10 +28,11 @@ export fn u2m() void {
2828// target=native
2929//
3030// :9:1: error: union initializer must initialize one field
31// :1:12: note: union declared here
3132// :14:20: error: cannot initialize multiple union fields at once, unions can only have one active field
3233// :14:31: note: additional initializer here
34// :1:12: note: union declared here
3335// :18:21: error: union initializer must initialize one field
3436// :22:20: error: cannot initialize multiple union fields at once, unions can only have one active field
3537// :22:31: note: additional initializer here
36// :1:12: note: union declared here
3738// :5:12: note: union declared here
test/cases/compile_log.0.zig+5
......@@ -15,3 +15,8 @@ fn x() void {}
1515// error
1616//
1717// :6:23: error: expected type 'usize', found 'bool'
18//
19// Compile Log Output:
20// @as(bool, true), @as(comptime_int, 20), @as(u32, [runtime value]), @as(fn() void, (function 'x'))
21// @as(comptime_int, 1000)
22// @as(comptime_int, 1234)
test/cases/compile_log.1.zig+4
......@@ -14,3 +14,7 @@ fn x() void {}
1414//
1515// :9:5: error: found compile log statement
1616// :4:5: note: also here
17//
18// Compile Log Output:
19// @as(bool, true), @as(comptime_int, 20), @as(u32, [runtime value]), @as(fn() void, (function 'x'))
20// @as(comptime_int, 1000)
test/cases/f32_passed_to_variadic_fn.zig+2-1
......@@ -9,7 +9,8 @@ pub fn main() void {
99// run
1010// backend=llvm
1111// target=x86_64-linux-gnu
12// link_libc=1
1213//
1314// f64: 2.000000
1415// f32: 10.000000
15//
\ No newline at end of file
16//
test/cases/fn_typeinfo_passed_to_comptime_fn.zig+1
......@@ -14,4 +14,5 @@ fn foo(comptime info: std.builtin.Type) !void {
1414
1515// run
1616// is_test=1
17// backend=llvm
1718//
test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/address_spaces_pointer_access_chaining_array_pointer.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/address_spaces_pointer_access_chaining_complex.zig+1-1
......@@ -6,7 +6,7 @@ pub fn main() void {
66 _ = entry;
77}
88
9// error
9// compile
1010// output_mode=Exe
1111// backend=llvm
1212// target=x86_64-linux,x86_64-macos
test/cases/llvm/address_spaces_pointer_access_chaining_struct_pointer.zig+1-1
......@@ -6,7 +6,7 @@ pub fn main() void {
66 _ = entry;
77}
88
9// error
9// compile
1010// output_mode=Exe
1111// backend=stage2,llvm
1212// target=x86_64-linux,x86_64-macos
test/cases/llvm/dereferencing_though_multiple_pointers_with_address_spaces.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/hello_world.zig+1
......@@ -7,6 +7,7 @@ pub fn main() void {
77// run
88// backend=llvm
99// target=x86_64-linux,x86_64-macos
10// link_libc=1
1011//
1112// hello world!
1213//
test/cases/llvm/pointer_keeps_address_space.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/pointer_keeps_address_space_when_taking_address_of_dereference.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cases/llvm/pointer_to_explicit_generic_address_space_coerces_to_implicit_pointer.zig+1-1
......@@ -5,7 +5,7 @@ pub fn main() void {
55 _ = entry;
66}
77
8// error
8// compile
99// output_mode=Exe
1010// backend=stage2,llvm
1111// target=x86_64-linux,x86_64-macos
test/cbe.zig created+950
......@@ -0,0 +1,950 @@
1const std = @import("std");
2const Cases = @import("src/Cases.zig");
3
4// These tests should work with all platforms, but we're using linux_x64 for
5// now for consistency. Will be expanded eventually.
6const linux_x64 = std.zig.CrossTarget{
7 .cpu_arch = .x86_64,
8 .os_tag = .linux,
9};
10
11pub fn addCases(ctx: *Cases) !void {
12 {
13 var case = ctx.exeFromCompiledC("hello world with updates", .{});
14
15 // Regular old hello world
16 case.addCompareOutput(
17 \\extern fn puts(s: [*:0]const u8) c_int;
18 \\pub export fn main() c_int {
19 \\ _ = puts("hello world!");
20 \\ return 0;
21 \\}
22 , "hello world!" ++ std.cstr.line_sep);
23
24 // Now change the message only
25 case.addCompareOutput(
26 \\extern fn puts(s: [*:0]const u8) c_int;
27 \\pub export fn main() c_int {
28 \\ _ = puts("yo");
29 \\ return 0;
30 \\}
31 , "yo" ++ std.cstr.line_sep);
32
33 // Add an unused Decl
34 case.addCompareOutput(
35 \\extern fn puts(s: [*:0]const u8) c_int;
36 \\pub export fn main() c_int {
37 \\ _ = puts("yo!");
38 \\ return 0;
39 \\}
40 \\fn unused() void {}
41 , "yo!" ++ std.cstr.line_sep);
42
43 // Comptime return type and calling convention expected.
44 case.addError(
45 \\var x: i32 = 1234;
46 \\pub export fn main() x {
47 \\ return 0;
48 \\}
49 \\export fn foo() callconv(y) c_int {
50 \\ return 0;
51 \\}
52 \\var y: @import("std").builtin.CallingConvention = .C;
53 , &.{
54 ":2:22: error: expected type 'type', found 'i32'",
55 ":5:26: error: unable to resolve comptime value",
56 ":5:26: note: calling convention must be comptime-known",
57 });
58 }
59
60 {
61 var case = ctx.exeFromCompiledC("var args", .{});
62
63 case.addCompareOutput(
64 \\extern fn printf(format: [*:0]const u8, ...) c_int;
65 \\
66 \\pub export fn main() c_int {
67 \\ _ = printf("Hello, %s!\n", "world");
68 \\ return 0;
69 \\}
70 , "Hello, world!" ++ std.cstr.line_sep);
71 }
72
73 {
74 var case = ctx.exeFromCompiledC("intToError", .{});
75
76 case.addCompareOutput(
77 \\pub export fn main() c_int {
78 \\ // comptime checks
79 \\ const a = error.A;
80 \\ const b = error.B;
81 \\ const c = @intToError(2);
82 \\ const d = @intToError(1);
83 \\ if (!(c == b)) unreachable;
84 \\ if (!(a == d)) unreachable;
85 \\ // runtime checks
86 \\ var x = error.A;
87 \\ var y = error.B;
88 \\ var z = @intToError(2);
89 \\ var f = @intToError(1);
90 \\ if (!(y == z)) unreachable;
91 \\ if (!(x == f)) unreachable;
92 \\ return 0;
93 \\}
94 , "");
95 case.addError(
96 \\pub export fn main() c_int {
97 \\ _ = @intToError(0);
98 \\ return 0;
99 \\}
100 , &.{":2:21: error: integer value '0' represents no error"});
101 case.addError(
102 \\pub export fn main() c_int {
103 \\ _ = @intToError(3);
104 \\ return 0;
105 \\}
106 , &.{":2:21: error: integer value '3' represents no error"});
107 }
108
109 {
110 var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64);
111
112 // Exit with 0
113 case.addCompareOutput(
114 \\fn exitGood() noreturn {
115 \\ asm volatile ("syscall"
116 \\ :
117 \\ : [number] "{rax}" (231),
118 \\ [arg1] "{rdi}" (0)
119 \\ );
120 \\ unreachable;
121 \\}
122 \\
123 \\pub export fn main() c_int {
124 \\ exitGood();
125 \\}
126 , "");
127
128 // Pass a usize parameter to exit
129 case.addCompareOutput(
130 \\pub export fn main() c_int {
131 \\ exit(0);
132 \\}
133 \\
134 \\fn exit(code: usize) noreturn {
135 \\ asm volatile ("syscall"
136 \\ :
137 \\ : [number] "{rax}" (231),
138 \\ [arg1] "{rdi}" (code)
139 \\ );
140 \\ unreachable;
141 \\}
142 , "");
143
144 // Change the parameter to u8
145 case.addCompareOutput(
146 \\pub export fn main() c_int {
147 \\ exit(0);
148 \\}
149 \\
150 \\fn exit(code: u8) noreturn {
151 \\ asm volatile ("syscall"
152 \\ :
153 \\ : [number] "{rax}" (231),
154 \\ [arg1] "{rdi}" (code)
155 \\ );
156 \\ unreachable;
157 \\}
158 , "");
159
160 // Do some arithmetic at the exit callsite
161 case.addCompareOutput(
162 \\pub export fn main() c_int {
163 \\ exitMath(1);
164 \\}
165 \\
166 \\fn exitMath(a: u8) noreturn {
167 \\ exit(0 + a - a);
168 \\}
169 \\
170 \\fn exit(code: u8) noreturn {
171 \\ asm volatile ("syscall"
172 \\ :
173 \\ : [number] "{rax}" (231),
174 \\ [arg1] "{rdi}" (code)
175 \\ );
176 \\ unreachable;
177 \\}
178 \\
179 , "");
180
181 // Invert the arithmetic
182 case.addCompareOutput(
183 \\pub export fn main() c_int {
184 \\ exitMath(1);
185 \\}
186 \\
187 \\fn exitMath(a: u8) noreturn {
188 \\ exit(a + 0 - a);
189 \\}
190 \\
191 \\fn exit(code: u8) noreturn {
192 \\ asm volatile ("syscall"
193 \\ :
194 \\ : [number] "{rax}" (231),
195 \\ [arg1] "{rdi}" (code)
196 \\ );
197 \\ unreachable;
198 \\}
199 \\
200 , "");
201 }
202
203 {
204 var case = ctx.exeFromCompiledC("alloc and retptr", .{});
205
206 case.addCompareOutput(
207 \\fn add(a: i32, b: i32) i32 {
208 \\ return a + b;
209 \\}
210 \\
211 \\fn addIndirect(a: i32, b: i32) i32 {
212 \\ return add(a, b);
213 \\}
214 \\
215 \\pub export fn main() c_int {
216 \\ return addIndirect(1, 2) - 3;
217 \\}
218 , "");
219 }
220
221 {
222 var case = ctx.exeFromCompiledC("inferred local const and var", .{});
223
224 case.addCompareOutput(
225 \\fn add(a: i32, b: i32) i32 {
226 \\ return a + b;
227 \\}
228 \\
229 \\pub export fn main() c_int {
230 \\ const x = add(1, 2);
231 \\ var y = add(3, 0);
232 \\ y -= x;
233 \\ return y;
234 \\}
235 , "");
236 }
237 {
238 var case = ctx.exeFromCompiledC("control flow", .{});
239
240 // Simple while loop
241 case.addCompareOutput(
242 \\pub export fn main() c_int {
243 \\ var a: c_int = 0;
244 \\ while (a < 5) : (a+=1) {}
245 \\ return a - 5;
246 \\}
247 , "");
248 case.addCompareOutput(
249 \\pub export fn main() c_int {
250 \\ var a = true;
251 \\ while (!a) {}
252 \\ return 0;
253 \\}
254 , "");
255
256 // If expression
257 case.addCompareOutput(
258 \\pub export fn main() c_int {
259 \\ var cond: c_int = 0;
260 \\ var a: c_int = @as(c_int, if (cond == 0)
261 \\ 2
262 \\ else
263 \\ 3) + 9;
264 \\ return a - 11;
265 \\}
266 , "");
267
268 // If expression with breakpoint that does not get hit
269 case.addCompareOutput(
270 \\pub export fn main() c_int {
271 \\ var x: i32 = 1;
272 \\ if (x != 1) @breakpoint();
273 \\ return 0;
274 \\}
275 , "");
276
277 // Switch expression
278 case.addCompareOutput(
279 \\pub export fn main() c_int {
280 \\ var cond: c_int = 0;
281 \\ var a: c_int = switch (cond) {
282 \\ 1 => 1,
283 \\ 2 => 2,
284 \\ 99...300, 12 => 3,
285 \\ 0 => 4,
286 \\ else => 5,
287 \\ };
288 \\ return a - 4;
289 \\}
290 , "");
291
292 // Switch expression missing else case.
293 case.addError(
294 \\pub export fn main() c_int {
295 \\ var cond: c_int = 0;
296 \\ const a: c_int = switch (cond) {
297 \\ 1 => 1,
298 \\ 2 => 2,
299 \\ 3 => 3,
300 \\ 4 => 4,
301 \\ };
302 \\ return a - 4;
303 \\}
304 , &.{":3:22: error: switch must handle all possibilities"});
305
306 // Switch expression, has an unreachable prong.
307 case.addCompareOutput(
308 \\pub export fn main() c_int {
309 \\ var cond: c_int = 0;
310 \\ const a: c_int = switch (cond) {
311 \\ 1 => 1,
312 \\ 2 => 2,
313 \\ 99...300, 12 => 3,
314 \\ 0 => 4,
315 \\ 13 => unreachable,
316 \\ else => 5,
317 \\ };
318 \\ return a - 4;
319 \\}
320 , "");
321
322 // Switch expression, has an unreachable prong and prongs write
323 // to result locations.
324 case.addCompareOutput(
325 \\pub export fn main() c_int {
326 \\ var cond: c_int = 0;
327 \\ var a: c_int = switch (cond) {
328 \\ 1 => 1,
329 \\ 2 => 2,
330 \\ 99...300, 12 => 3,
331 \\ 0 => 4,
332 \\ 13 => unreachable,
333 \\ else => 5,
334 \\ };
335 \\ return a - 4;
336 \\}
337 , "");
338
339 // Integer switch expression has duplicate case value.
340 case.addError(
341 \\pub export fn main() c_int {
342 \\ var cond: c_int = 0;
343 \\ const a: c_int = switch (cond) {
344 \\ 1 => 1,
345 \\ 2 => 2,
346 \\ 96, 11...13, 97 => 3,
347 \\ 0 => 4,
348 \\ 90, 12 => 100,
349 \\ else => 5,
350 \\ };
351 \\ return a - 4;
352 \\}
353 , &.{
354 ":8:13: error: duplicate switch value",
355 ":6:15: note: previous value here",
356 });
357
358 // Boolean switch expression has duplicate case value.
359 case.addError(
360 \\pub export fn main() c_int {
361 \\ var a: bool = false;
362 \\ const b: c_int = switch (a) {
363 \\ false => 1,
364 \\ true => 2,
365 \\ false => 3,
366 \\ };
367 \\ _ = b;
368 \\}
369 , &.{
370 ":6:9: error: duplicate switch value",
371 });
372
373 // Sparse (no range capable) switch expression has duplicate case value.
374 case.addError(
375 \\pub export fn main() c_int {
376 \\ const A: type = i32;
377 \\ const b: c_int = switch (A) {
378 \\ i32 => 1,
379 \\ bool => 2,
380 \\ f64, i32 => 3,
381 \\ else => 4,
382 \\ };
383 \\ _ = b;
384 \\}
385 , &.{
386 ":6:14: error: duplicate switch value",
387 ":4:9: note: previous value here",
388 });
389
390 // Ranges not allowed for some kinds of switches.
391 case.addError(
392 \\pub export fn main() c_int {
393 \\ const A: type = i32;
394 \\ const b: c_int = switch (A) {
395 \\ i32 => 1,
396 \\ bool => 2,
397 \\ f16...f64 => 3,
398 \\ else => 4,
399 \\ };
400 \\ _ = b;
401 \\}
402 , &.{
403 ":3:30: error: ranges not allowed when switching on type 'type'",
404 ":6:12: note: range here",
405 });
406
407 // Switch expression has unreachable else prong.
408 case.addError(
409 \\pub export fn main() c_int {
410 \\ var a: u2 = 0;
411 \\ const b: i32 = switch (a) {
412 \\ 0 => 10,
413 \\ 1 => 20,
414 \\ 2 => 30,
415 \\ 3 => 40,
416 \\ else => 50,
417 \\ };
418 \\ _ = b;
419 \\}
420 , &.{
421 ":8:14: error: unreachable else prong; all cases already handled",
422 });
423 }
424 //{
425 // var case = ctx.exeFromCompiledC("optionals", .{});
426
427 // // Simple while loop
428 // case.addCompareOutput(
429 // \\pub export fn main() c_int {
430 // \\ var count: c_int = 0;
431 // \\ var opt_ptr: ?*c_int = &count;
432 // \\ while (opt_ptr) |_| : (count += 1) {
433 // \\ if (count == 4) opt_ptr = null;
434 // \\ }
435 // \\ return count - 5;
436 // \\}
437 // , "");
438
439 // // Same with non pointer optionals
440 // case.addCompareOutput(
441 // \\pub export fn main() c_int {
442 // \\ var count: c_int = 0;
443 // \\ var opt_ptr: ?c_int = count;
444 // \\ while (opt_ptr) |_| : (count += 1) {
445 // \\ if (count == 4) opt_ptr = null;
446 // \\ }
447 // \\ return count - 5;
448 // \\}
449 // , "");
450 //}
451
452 {
453 var case = ctx.exeFromCompiledC("errors", .{});
454 case.addCompareOutput(
455 \\pub export fn main() c_int {
456 \\ var e1 = error.Foo;
457 \\ var e2 = error.Bar;
458 \\ assert(e1 != e2);
459 \\ assert(e1 == error.Foo);
460 \\ assert(e2 == error.Bar);
461 \\ return 0;
462 \\}
463 \\fn assert(b: bool) void {
464 \\ if (!b) unreachable;
465 \\}
466 , "");
467 case.addCompareOutput(
468 \\pub export fn main() c_int {
469 \\ var e: anyerror!c_int = 0;
470 \\ const i = e catch 69;
471 \\ return i;
472 \\}
473 , "");
474 case.addCompareOutput(
475 \\pub export fn main() c_int {
476 \\ var e: anyerror!c_int = error.Foo;
477 \\ const i = e catch 69;
478 \\ return 69 - i;
479 \\}
480 , "");
481 case.addCompareOutput(
482 \\const E = error{e};
483 \\const S = struct { x: u32 };
484 \\fn f() E!u32 {
485 \\ const x = (try @as(E!S, S{ .x = 1 })).x;
486 \\ return x;
487 \\}
488 \\pub export fn main() c_int {
489 \\ const x = f() catch @as(u32, 0);
490 \\ if (x != 1) unreachable;
491 \\ return 0;
492 \\}
493 , "");
494 }
495
496 {
497 var case = ctx.exeFromCompiledC("structs", .{});
498 case.addError(
499 \\const Point = struct { x: i32, y: i32 };
500 \\pub export fn main() c_int {
501 \\ var p: Point = .{
502 \\ .y = 24,
503 \\ .x = 12,
504 \\ .y = 24,
505 \\ };
506 \\ return p.y - p.x - p.x;
507 \\}
508 , &.{
509 ":6:10: error: duplicate field",
510 ":4:10: note: other field here",
511 });
512 case.addError(
513 \\const Point = struct { x: i32, y: i32 };
514 \\pub export fn main() c_int {
515 \\ var p: Point = .{
516 \\ .y = 24,
517 \\ };
518 \\ return p.y - p.x - p.x;
519 \\}
520 , &.{
521 ":3:21: error: missing struct field: x",
522 ":1:15: note: struct 'tmp.Point' declared here",
523 });
524 case.addError(
525 \\const Point = struct { x: i32, y: i32 };
526 \\pub export fn main() c_int {
527 \\ var p: Point = .{
528 \\ .x = 12,
529 \\ .y = 24,
530 \\ .z = 48,
531 \\ };
532 \\ return p.y - p.x - p.x;
533 \\}
534 , &.{
535 ":6:10: error: no field named 'z' in struct 'tmp.Point'",
536 ":1:15: note: struct declared here",
537 });
538 case.addCompareOutput(
539 \\const Point = struct { x: i32, y: i32 };
540 \\pub export fn main() c_int {
541 \\ var p: Point = .{
542 \\ .x = 12,
543 \\ .y = 24,
544 \\ };
545 \\ return p.y - p.x - p.x;
546 \\}
547 , "");
548 case.addCompareOutput(
549 \\const Point = struct { x: i32, y: i32, z: i32, a: i32, b: i32 };
550 \\pub export fn main() c_int {
551 \\ var p: Point = .{
552 \\ .x = 18,
553 \\ .y = 24,
554 \\ .z = 1,
555 \\ .a = 2,
556 \\ .b = 3,
557 \\ };
558 \\ return p.y - p.x - p.z - p.a - p.b;
559 \\}
560 , "");
561 }
562
563 {
564 var case = ctx.exeFromCompiledC("unions", .{});
565
566 case.addError(
567 \\const U = union {
568 \\ a: u32,
569 \\ b
570 \\};
571 , &.{
572 ":3:5: error: union field missing type",
573 });
574
575 case.addError(
576 \\const E = enum { a, b };
577 \\const U = union(E) {
578 \\ a: u32 = 1,
579 \\ b: f32 = 2,
580 \\};
581 , &.{
582 ":2:11: error: explicitly valued tagged union requires inferred enum tag type",
583 ":3:14: note: tag value specified here",
584 });
585
586 case.addError(
587 \\const U = union(enum) {
588 \\ a: u32 = 1,
589 \\ b: f32 = 2,
590 \\};
591 , &.{
592 ":1:11: error: explicitly valued tagged union missing integer tag type",
593 ":2:14: note: tag value specified here",
594 });
595 }
596
597 {
598 var case = ctx.exeFromCompiledC("enums", .{});
599
600 case.addError(
601 \\const E1 = packed enum { a, b, c };
602 \\const E2 = extern enum { a, b, c };
603 \\export fn foo() void {
604 \\ _ = E1.a;
605 \\}
606 \\export fn bar() void {
607 \\ _ = E2.a;
608 \\}
609 , &.{
610 ":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
611 ":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
612 });
613
614 // comptime and types are caught in AstGen.
615 case.addError(
616 \\const E1 = enum {
617 \\ a,
618 \\ comptime b,
619 \\ c,
620 \\};
621 \\const E2 = enum {
622 \\ a,
623 \\ b: i32,
624 \\ c,
625 \\};
626 \\export fn foo() void {
627 \\ _ = E1.a;
628 \\}
629 \\export fn bar() void {
630 \\ _ = E2.a;
631 \\}
632 , &.{
633 ":3:5: error: enum fields cannot be marked comptime",
634 ":8:8: error: enum fields do not have types",
635 ":6:12: note: consider 'union(enum)' here to make it a tagged union",
636 });
637
638 // @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch
639 case.addCompareOutput(
640 \\const Number = enum { One, Two, Three };
641 \\
642 \\pub export fn main() c_int {
643 \\ var number1 = Number.One;
644 \\ var number2: Number = .Two;
645 \\ const number3 = @intToEnum(Number, 2);
646 \\ if (number1 == number2) return 1;
647 \\ if (number2 == number3) return 1;
648 \\ if (@enumToInt(number1) != 0) return 1;
649 \\ if (@enumToInt(number2) != 1) return 1;
650 \\ if (@enumToInt(number3) != 2) return 1;
651 \\ var x: Number = .Two;
652 \\ if (number2 != x) return 1;
653 \\ switch (x) {
654 \\ .One => return 1,
655 \\ .Two => return 0,
656 \\ number3 => return 2,
657 \\ }
658 \\}
659 , "");
660
661 // Specifying alignment is a parse error.
662 // This also tests going from a successful build to a parse error.
663 case.addError(
664 \\const E1 = enum {
665 \\ a,
666 \\ b align(4),
667 \\ c,
668 \\};
669 \\export fn foo() void {
670 \\ _ = E1.a;
671 \\}
672 , &.{
673 ":3:13: error: enum fields cannot be aligned",
674 });
675
676 // Redundant non-exhaustive enum mark.
677 // This also tests going from a parse error to an AstGen error.
678 case.addError(
679 \\const E1 = enum {
680 \\ a,
681 \\ _,
682 \\ b,
683 \\ c,
684 \\ _,
685 \\};
686 \\export fn foo() void {
687 \\ _ = E1.a;
688 \\}
689 , &.{
690 ":6:5: error: redundant non-exhaustive enum mark",
691 ":3:5: note: other mark here",
692 });
693
694 case.addError(
695 \\const E1 = enum {
696 \\ a,
697 \\ b,
698 \\ c,
699 \\ _ = 10,
700 \\};
701 \\export fn foo() void {
702 \\ _ = E1.a;
703 \\}
704 , &.{
705 ":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value",
706 });
707
708 case.addError(
709 \\const E1 = enum { a, b, _ };
710 \\export fn foo() void {
711 \\ _ = E1.a;
712 \\}
713 , &.{
714 ":1:12: error: non-exhaustive enum missing integer tag type",
715 ":1:25: note: marked non-exhaustive here",
716 });
717
718 case.addError(
719 \\const E1 = enum { a, b, c, b, d };
720 \\pub export fn main() c_int {
721 \\ _ = E1.a;
722 \\}
723 , &.{
724 ":1:28: error: duplicate enum field 'b'",
725 ":1:22: note: other field here",
726 });
727
728 case.addError(
729 \\pub export fn main() c_int {
730 \\ const a = true;
731 \\ _ = @enumToInt(a);
732 \\}
733 , &.{
734 ":3:20: error: expected enum or tagged union, found 'bool'",
735 });
736
737 case.addError(
738 \\pub export fn main() c_int {
739 \\ const a = 1;
740 \\ _ = @intToEnum(bool, a);
741 \\}
742 , &.{
743 ":3:20: error: expected enum, found 'bool'",
744 });
745
746 case.addError(
747 \\const E = enum { a, b, c };
748 \\pub export fn main() c_int {
749 \\ _ = @intToEnum(E, 3);
750 \\}
751 , &.{
752 ":3:9: error: enum 'tmp.E' has no tag with value '3'",
753 ":1:11: note: enum declared here",
754 });
755
756 case.addError(
757 \\const E = enum { a, b, c };
758 \\pub export fn main() c_int {
759 \\ var x: E = .a;
760 \\ switch (x) {
761 \\ .a => {},
762 \\ .c => {},
763 \\ }
764 \\}
765 , &.{
766 ":4:5: error: switch must handle all possibilities",
767 ":1:21: note: unhandled enumeration value: 'b'",
768 ":1:11: note: enum 'tmp.E' declared here",
769 });
770
771 case.addError(
772 \\const E = enum { a, b, c };
773 \\pub export fn main() c_int {
774 \\ var x: E = .a;
775 \\ switch (x) {
776 \\ .a => {},
777 \\ .b => {},
778 \\ .b => {},
779 \\ .c => {},
780 \\ }
781 \\}
782 , &.{
783 ":7:10: error: duplicate switch value",
784 ":6:10: note: previous value here",
785 });
786
787 case.addError(
788 \\const E = enum { a, b, c };
789 \\pub export fn main() c_int {
790 \\ var x: E = .a;
791 \\ switch (x) {
792 \\ .a => {},
793 \\ .b => {},
794 \\ .c => {},
795 \\ else => {},
796 \\ }
797 \\}
798 , &.{
799 ":8:14: error: unreachable else prong; all cases already handled",
800 });
801
802 case.addError(
803 \\const E = enum { a, b, c };
804 \\pub export fn main() c_int {
805 \\ var x: E = .a;
806 \\ switch (x) {
807 \\ .a => {},
808 \\ .b => {},
809 \\ _ => {},
810 \\ }
811 \\}
812 , &.{
813 ":4:5: error: '_' prong only allowed when switching on non-exhaustive enums",
814 ":7:11: note: '_' prong here",
815 });
816
817 case.addError(
818 \\const E = enum { a, b, c };
819 \\pub export fn main() c_int {
820 \\ _ = E.d;
821 \\}
822 , &.{
823 ":3:11: error: enum 'tmp.E' has no member named 'd'",
824 ":1:11: note: enum declared here",
825 });
826
827 case.addError(
828 \\const E = enum { a, b, c };
829 \\pub export fn main() c_int {
830 \\ var x: E = .d;
831 \\ _ = x;
832 \\}
833 , &.{
834 ":3:17: error: no field named 'd' in enum 'tmp.E'",
835 ":1:11: note: enum declared here",
836 });
837 }
838
839 {
840 var case = ctx.exeFromCompiledC("shift right and left", .{});
841 case.addCompareOutput(
842 \\pub export fn main() c_int {
843 \\ var i: u32 = 16;
844 \\ assert(i >> 1, 8);
845 \\ return 0;
846 \\}
847 \\fn assert(a: u32, b: u32) void {
848 \\ if (a != b) unreachable;
849 \\}
850 , "");
851
852 case.addCompareOutput(
853 \\pub export fn main() c_int {
854 \\ var i: u32 = 16;
855 \\ assert(i << 1, 32);
856 \\ return 0;
857 \\}
858 \\fn assert(a: u32, b: u32) void {
859 \\ if (a != b) unreachable;
860 \\}
861 , "");
862 }
863
864 {
865 var case = ctx.exeFromCompiledC("inferred error sets", .{});
866
867 case.addCompareOutput(
868 \\pub export fn main() c_int {
869 \\ if (foo()) |_| {
870 \\ @panic("test fail");
871 \\ } else |err| {
872 \\ if (err != error.ItBroke) {
873 \\ @panic("test fail");
874 \\ }
875 \\ }
876 \\ return 0;
877 \\}
878 \\fn foo() !void {
879 \\ return error.ItBroke;
880 \\}
881 , "");
882 }
883
884 {
885 // TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64)
886 var case = ctx.exeFromCompiledC("add and sub wrapping operations", .{});
887 case.addCompareOutput(
888 \\pub export fn main() c_int {
889 \\ // Addition
890 \\ if (!add_u3(1, 1, 2)) return 1;
891 \\ if (!add_u3(7, 1, 0)) return 1;
892 \\ if (!add_i3(1, 1, 2)) return 1;
893 \\ if (!add_i3(3, 2, -3)) return 1;
894 \\ if (!add_i3(-3, -2, 3)) return 1;
895 \\ if (!add_c_int(1, 1, 2)) return 1;
896 \\ // TODO enable these when stage2 supports std.math.maxInt
897 \\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1;
898 \\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1;
899 \\
900 \\ // Subtraction
901 \\ if (!sub_u3(2, 1, 1)) return 1;
902 \\ if (!sub_u3(0, 1, 7)) return 1;
903 \\ if (!sub_i3(2, 1, 1)) return 1;
904 \\ if (!sub_i3(3, -2, -3)) return 1;
905 \\ if (!sub_i3(-3, 2, 3)) return 1;
906 \\ if (!sub_c_int(2, 1, 1)) return 1;
907 \\ // TODO enable these when stage2 supports std.math.maxInt
908 \\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1;
909 \\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1;
910 \\
911 \\ return 0;
912 \\}
913 \\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool {
914 \\ return expected == lhs +% rhs;
915 \\}
916 \\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool {
917 \\ return expected == lhs +% rhs;
918 \\}
919 \\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
920 \\ return expected == lhs +% rhs;
921 \\}
922 \\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool {
923 \\ return expected == lhs -% rhs;
924 \\}
925 \\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool {
926 \\ return expected == lhs -% rhs;
927 \\}
928 \\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
929 \\ return expected == lhs -% rhs;
930 \\}
931 , "");
932 }
933
934 {
935 var case = ctx.exeFromCompiledC("rem", linux_x64);
936 case.addCompareOutput(
937 \\fn assert(ok: bool) void {
938 \\ if (!ok) unreachable;
939 \\}
940 \\fn rem(lhs: i32, rhs: i32, expected: i32) bool {
941 \\ return @rem(lhs, rhs) == expected;
942 \\}
943 \\pub export fn main() c_int {
944 \\ assert(rem(-5, 3, -2));
945 \\ assert(rem(5, 3, 2));
946 \\ return 0;
947 \\}
948 , "");
949 }
950}
test/cli.zig deleted-195
......@@ -1,195 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const testing = std.testing;
4const process = std.process;
5const fs = std.fs;
6const ChildProcess = std.ChildProcess;
7
8var a: std.mem.Allocator = undefined;
9
10pub fn main() !void {
11 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
12 defer _ = gpa.deinit();
13 var arena = std.heap.ArenaAllocator.init(gpa.allocator());
14 defer arena.deinit();
15
16 a = arena.allocator();
17 var arg_it = try process.argsWithAllocator(a);
18
19 // skip my own exe name
20 _ = arg_it.skip();
21
22 const zig_exe_rel = arg_it.next() orelse {
23 std.debug.print("Expected first argument to be path to zig compiler\n", .{});
24 return error.InvalidArgs;
25 };
26 const cache_root = arg_it.next() orelse {
27 std.debug.print("Expected second argument to be cache root directory path\n", .{});
28 return error.InvalidArgs;
29 };
30 const zig_exe = try fs.path.resolve(a, &[_][]const u8{zig_exe_rel});
31
32 const dir_path = try fs.path.join(a, &[_][]const u8{ cache_root, "clitest" });
33 defer fs.cwd().deleteTree(dir_path) catch {};
34
35 const TestFn = fn ([]const u8, []const u8) anyerror!void;
36 const Test = struct {
37 func: TestFn,
38 name: []const u8,
39 };
40 const tests = [_]Test{
41 .{ .func = testZigInitLib, .name = "zig init-lib" },
42 .{ .func = testZigInitExe, .name = "zig init-exe" },
43 .{ .func = testGodboltApi, .name = "godbolt API" },
44 .{ .func = testMissingOutputPath, .name = "missing output path" },
45 .{ .func = testZigFmt, .name = "zig fmt" },
46 };
47 inline for (tests) |t| {
48 try fs.cwd().deleteTree(dir_path);
49 try fs.cwd().makeDir(dir_path);
50 t.func(zig_exe, dir_path) catch |err| {
51 std.debug.print("test '{s}' failed: {s}\n", .{
52 t.name, @errorName(err),
53 });
54 return err;
55 };
56 }
57}
58
59fn printCmd(cwd: []const u8, argv: []const []const u8) void {
60 std.debug.print("cd {s} && ", .{cwd});
61 for (argv) |arg| {
62 std.debug.print("{s} ", .{arg});
63 }
64 std.debug.print("\n", .{});
65}
66
67fn exec(cwd: []const u8, expect_0: bool, argv: []const []const u8) !ChildProcess.ExecResult {
68 const max_output_size = 100 * 1024;
69 const result = ChildProcess.exec(.{
70 .allocator = a,
71 .argv = argv,
72 .cwd = cwd,
73 .max_output_bytes = max_output_size,
74 }) catch |err| {
75 std.debug.print("The following command failed:\n", .{});
76 printCmd(cwd, argv);
77 return err;
78 };
79 switch (result.term) {
80 .Exited => |code| {
81 if ((code != 0) == expect_0) {
82 std.debug.print("The following command exited with error code {}:\n", .{code});
83 printCmd(cwd, argv);
84 std.debug.print("stderr:\n{s}\n", .{result.stderr});
85 return error.CommandFailed;
86 }
87 },
88 else => {
89 std.debug.print("The following command terminated unexpectedly:\n", .{});
90 printCmd(cwd, argv);
91 std.debug.print("stderr:\n{s}\n", .{result.stderr});
92 return error.CommandFailed;
93 },
94 }
95 return result;
96}
97
98fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void {
99 _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-lib" });
100 const test_result = try exec(dir_path, true, &[_][]const u8{ zig_exe, "build", "test" });
101 try testing.expectStringEndsWith(test_result.stderr, "All 1 tests passed.\n");
102}
103
104fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void {
105 _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" });
106 const run_result = try exec(dir_path, true, &[_][]const u8{ zig_exe, "build", "run" });
107 try testing.expectEqualStrings("All your codebase are belong to us.\n", run_result.stderr);
108 try testing.expectEqualStrings("Run `zig build test` to run the tests.\n", run_result.stdout);
109}
110
111fn testGodboltApi(zig_exe: []const u8, dir_path: []const u8) anyerror!void {
112 if (builtin.os.tag != .linux or builtin.cpu.arch != .x86_64) return;
113
114 const example_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "example.zig" });
115 const example_s_path = try fs.path.join(a, &[_][]const u8{ dir_path, "example.s" });
116
117 try fs.cwd().writeFile(example_zig_path,
118 \\// Type your code here, or load an example.
119 \\export fn square(num: i32) i32 {
120 \\ return num * num;
121 \\}
122 \\extern fn zig_panic() noreturn;
123 \\pub fn panic(msg: []const u8, error_return_trace: ?*@import("std").builtin.StackTrace, _: ?usize) noreturn {
124 \\ _ = msg;
125 \\ _ = error_return_trace;
126 \\ zig_panic();
127 \\}
128 );
129
130 var args = std.ArrayList([]const u8).init(a);
131 try args.appendSlice(&[_][]const u8{
132 zig_exe, "build-obj",
133 "--cache-dir", dir_path,
134 "--name", "example",
135 "-fno-emit-bin", "-fno-emit-h",
136 "-fstrip", "-OReleaseFast",
137 example_zig_path,
138 });
139
140 const emit_asm_arg = try std.fmt.allocPrint(a, "-femit-asm={s}", .{example_s_path});
141 try args.append(emit_asm_arg);
142
143 _ = try exec(dir_path, true, args.items);
144
145 const out_asm = try std.fs.cwd().readFileAlloc(a, example_s_path, std.math.maxInt(usize));
146 try testing.expect(std.mem.indexOf(u8, out_asm, "square:") != null);
147 try testing.expect(std.mem.indexOf(u8, out_asm, "mov\teax, edi") != null);
148 try testing.expect(std.mem.indexOf(u8, out_asm, "imul\teax, edi") != null);
149}
150
151fn testMissingOutputPath(zig_exe: []const u8, dir_path: []const u8) !void {
152 _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" });
153 const output_path = try fs.path.join(a, &[_][]const u8{ "does", "not", "exist", "foo.exe" });
154 const output_arg = try std.fmt.allocPrint(a, "-femit-bin={s}", .{output_path});
155 const source_path = try fs.path.join(a, &[_][]const u8{ "src", "main.zig" });
156 const result = try exec(dir_path, false, &[_][]const u8{ zig_exe, "build-exe", source_path, output_arg });
157 const s = std.fs.path.sep_str;
158 const expected: []const u8 = "error: unable to open output directory 'does" ++ s ++ "not" ++ s ++ "exist': FileNotFound\n";
159 try testing.expectEqualStrings(expected, result.stderr);
160}
161
162fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void {
163 _ = try exec(dir_path, true, &[_][]const u8{ zig_exe, "init-exe" });
164
165 const unformatted_code = " // no reason for indent";
166
167 const fmt1_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt1.zig" });
168 try fs.cwd().writeFile(fmt1_zig_path, unformatted_code);
169
170 const run_result1 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", fmt1_zig_path });
171 // stderr should be file path + \n
172 try testing.expect(std.mem.startsWith(u8, run_result1.stdout, fmt1_zig_path));
173 try testing.expect(run_result1.stdout.len == fmt1_zig_path.len + 1 and run_result1.stdout[run_result1.stdout.len - 1] == '\n');
174
175 const fmt2_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt2.zig" });
176 try fs.cwd().writeFile(fmt2_zig_path, unformatted_code);
177
178 const run_result2 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path });
179 // running it on the dir, only the new file should be changed
180 try testing.expect(std.mem.startsWith(u8, run_result2.stdout, fmt2_zig_path));
181 try testing.expect(run_result2.stdout.len == fmt2_zig_path.len + 1 and run_result2.stdout[run_result2.stdout.len - 1] == '\n');
182
183 const run_result3 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path });
184 // both files have been formatted, nothing should change now
185 try testing.expect(run_result3.stdout.len == 0);
186
187 // Check UTF-16 decoding
188 const fmt4_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt4.zig" });
189 var unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";
190 try fs.cwd().writeFile(fmt4_zig_path, unformatted_code_utf16);
191
192 const run_result4 = try exec(dir_path, true, &[_][]const u8{ zig_exe, "fmt", dir_path });
193 try testing.expect(std.mem.startsWith(u8, run_result4.stdout, fmt4_zig_path));
194 try testing.expect(run_result4.stdout.len == fmt4_zig_path.len + 1 and run_result4.stdout[run_result4.stdout.len - 1] == '\n');
195}
test/compile_errors.zig+25-199
......@@ -1,146 +1,10 @@
11const std = @import("std");
22const builtin = @import("builtin");
3const TestContext = @import("../src/test.zig").TestContext;
4
5pub fn addCases(ctx: *TestContext) !void {
6 {
7 const case = ctx.obj("wrong same named struct", .{});
8 case.backend = .stage1;
9
10 case.addSourceFile("a.zig",
11 \\pub const Foo = struct {
12 \\ x: i32,
13 \\};
14 );
15
16 case.addSourceFile("b.zig",
17 \\pub const Foo = struct {
18 \\ z: f64,
19 \\};
20 );
21
22 case.addError(
23 \\const a = @import("a.zig");
24 \\const b = @import("b.zig");
25 \\
26 \\export fn entry() void {
27 \\ var a1: a.Foo = undefined;
28 \\ bar(&a1);
29 \\}
30 \\
31 \\fn bar(x: *b.Foo) void {_ = x;}
32 , &[_][]const u8{
33 "tmp.zig:6:10: error: expected type '*b.Foo', found '*a.Foo'",
34 "tmp.zig:6:10: note: pointer type child 'a.Foo' cannot cast into pointer type child 'b.Foo'",
35 "a.zig:1:17: note: a.Foo declared here",
36 "b.zig:1:17: note: b.Foo declared here",
37 });
38 }
39
40 {
41 const case = ctx.obj("multiple files with private function error", .{});
42 case.backend = .stage1;
43
44 case.addSourceFile("foo.zig",
45 \\fn privateFunction() void { }
46 );
47
48 case.addError(
49 \\const foo = @import("foo.zig",);
50 \\
51 \\export fn callPrivFunction() void {
52 \\ foo.privateFunction();
53 \\}
54 , &[_][]const u8{
55 "tmp.zig:4:8: error: 'privateFunction' is private",
56 "foo.zig:1:1: note: declared here",
57 });
58 }
59
60 {
61 const case = ctx.obj("multiple files with private member instance function (canonical invocation) error", .{});
62 case.backend = .stage1;
63
64 case.addSourceFile("foo.zig",
65 \\pub const Foo = struct {
66 \\ fn privateFunction(self: *Foo) void { _ = self; }
67 \\};
68 );
69
70 case.addError(
71 \\const Foo = @import("foo.zig",).Foo;
72 \\
73 \\export fn callPrivFunction() void {
74 \\ var foo = Foo{};
75 \\ Foo.privateFunction(foo);
76 \\}
77 , &[_][]const u8{
78 "tmp.zig:5:8: error: 'privateFunction' is private",
79 "foo.zig:2:5: note: declared here",
80 });
81 }
82
83 {
84 const case = ctx.obj("multiple files with private member instance function error", .{});
85 case.backend = .stage1;
86
87 case.addSourceFile("foo.zig",
88 \\pub const Foo = struct {
89 \\ fn privateFunction(self: *Foo) void { _ = self; }
90 \\};
91 );
92
93 case.addError(
94 \\const Foo = @import("foo.zig",).Foo;
95 \\
96 \\export fn callPrivFunction() void {
97 \\ var foo = Foo{};
98 \\ foo.privateFunction();
99 \\}
100 , &[_][]const u8{
101 "tmp.zig:5:8: error: 'privateFunction' is private",
102 "foo.zig:2:5: note: declared here",
103 });
104 }
105
106 {
107 const case = ctx.obj("export collision", .{});
108 case.backend = .stage1;
109
110 case.addSourceFile("foo.zig",
111 \\export fn bar() void {}
112 \\pub const baz = 1234;
113 );
114
115 case.addError(
116 \\const foo = @import("foo.zig",);
117 \\
118 \\export fn bar() usize {
119 \\ return foo.baz;
120 \\}
121 , &[_][]const u8{
122 "foo.zig:1:1: error: exported symbol collision: 'bar'",
123 "tmp.zig:3:1: note: other symbol here",
124 });
125 }
126
127 ctx.objErrStage1("non-printable invalid character", "\xff\xfe" ++
128 "fn foo() bool {\r\n" ++
129 " return true;\r\n" ++
130 "}\r\n", &[_][]const u8{
131 "tmp.zig:1:1: error: expected test, comptime, var decl, or container field, found 'invalid bytes'",
132 "tmp.zig:1:1: note: invalid byte: '\\xff'",
133 });
134
135 ctx.objErrStage1("non-printable invalid character with escape alternative", "fn foo() bool {\n" ++
136 "\treturn true;\n" ++
137 "}\n", &[_][]const u8{
138 "tmp.zig:2:1: error: invalid character: '\\t'",
139 });
3const Cases = @import("src/Cases.zig");
1404
5pub fn addCases(ctx: *Cases) !void {
1416 {
1427 const case = ctx.obj("multiline error messages", .{});
143 case.backend = .stage2;
1448
1459 case.addError(
14610 \\comptime {
......@@ -176,7 +40,6 @@ pub fn addCases(ctx: *TestContext) !void {
17640
17741 {
17842 const case = ctx.obj("isolated carriage return in multiline string literal", .{});
179 case.backend = .stage2;
18043
18144 case.addError("const foo = \\\\\test\r\r rogue carriage return\n;", &[_][]const u8{
18245 ":1:19: error: expected ';' after declaration",
......@@ -195,16 +58,6 @@ pub fn addCases(ctx: *TestContext) !void {
19558
19659 {
19760 const case = ctx.obj("argument causes error", .{});
198 case.backend = .stage2;
199
200 case.addSourceFile("b.zig",
201 \\pub const ElfDynLib = struct {
202 \\ pub fn lookup(self: *ElfDynLib, comptime T: type) ?T {
203 \\ _ = self;
204 \\ return undefined;
205 \\ }
206 \\};
207 );
20861
20962 case.addError(
21063 \\pub export fn entry() void {
......@@ -216,15 +69,18 @@ pub fn addCases(ctx: *TestContext) !void {
21669 ":3:12: note: argument to function being called at comptime must be comptime-known",
21770 ":2:55: note: expression is evaluated at comptime because the generic function was instantiated with a comptime-only return type",
21871 });
72 case.addSourceFile("b.zig",
73 \\pub const ElfDynLib = struct {
74 \\ pub fn lookup(self: *ElfDynLib, comptime T: type) ?T {
75 \\ _ = self;
76 \\ return undefined;
77 \\ }
78 \\};
79 );
21980 }
22081
22182 {
22283 const case = ctx.obj("astgen failure in file struct", .{});
223 case.backend = .stage2;
224
225 case.addSourceFile("b.zig",
226 \\+
227 );
22884
22985 case.addError(
23086 \\pub export fn entry() void {
......@@ -233,21 +89,13 @@ pub fn addCases(ctx: *TestContext) !void {
23389 , &[_][]const u8{
23490 ":1:1: error: expected type expression, found '+'",
23591 });
92 case.addSourceFile("b.zig",
93 \\+
94 );
23695 }
23796
23897 {
23998 const case = ctx.obj("invalid store to comptime field", .{});
240 case.backend = .stage2;
241
242 case.addSourceFile("a.zig",
243 \\pub const S = struct {
244 \\ comptime foo: u32 = 1,
245 \\ bar: u32,
246 \\ pub fn foo(x: @This()) void {
247 \\ _ = x;
248 \\ }
249 \\};
250 );
25199
252100 case.addError(
253101 \\const a = @import("a.zig");
......@@ -259,44 +107,19 @@ pub fn addCases(ctx: *TestContext) !void {
259107 ":4:23: error: value stored in comptime field does not match the default value of the field",
260108 ":2:25: note: default value set here",
261109 });
110 case.addSourceFile("a.zig",
111 \\pub const S = struct {
112 \\ comptime foo: u32 = 1,
113 \\ bar: u32,
114 \\ pub fn foo(x: @This()) void {
115 \\ _ = x;
116 \\ }
117 \\};
118 );
262119 }
263120
264 // TODO test this in stage2, but we won't even try in stage1
265 //ctx.objErrStage1("inline fn calls itself indirectly",
266 // \\export fn foo() void {
267 // \\ bar();
268 // \\}
269 // \\fn bar() callconv(.Inline) void {
270 // \\ baz();
271 // \\ quux();
272 // \\}
273 // \\fn baz() callconv(.Inline) void {
274 // \\ bar();
275 // \\ quux();
276 // \\}
277 // \\extern fn quux() void;
278 //, &[_][]const u8{
279 // "tmp.zig:4:1: error: unable to inline function",
280 //});
281
282 //ctx.objErrStage1("save reference to inline function",
283 // \\export fn foo() void {
284 // \\ quux(@ptrToInt(bar));
285 // \\}
286 // \\fn bar() callconv(.Inline) void { }
287 // \\extern fn quux(usize) void;
288 //, &[_][]const u8{
289 // "tmp.zig:4:1: error: unable to inline function",
290 //});
291
292121 {
293122 const case = ctx.obj("file in multiple modules", .{});
294 case.backend = .stage2;
295
296 case.addSourceFile("foo.zig",
297 \\const dummy = 0;
298 );
299
300123 case.addDepModule("foo", "foo.zig");
301124
302125 case.addError(
......@@ -309,5 +132,8 @@ pub fn addCases(ctx: *TestContext) !void {
309132 ":1:1: note: root of module root.foo",
310133 ":3:17: note: imported from module root",
311134 });
135 case.addSourceFile("foo.zig",
136 \\const dummy = 0;
137 );
312138 }
313139}
test/link.zig+172-213
......@@ -1,213 +1,172 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const tests = @import("tests.zig");
4
5pub fn addCases(cases: *tests.StandaloneContext) void {
6 cases.addBuildFile("test/link/bss/build.zig", .{
7 .build_modes = false, // we only guarantee zerofill for undefined in Debug
8 });
9
10 cases.addBuildFile("test/link/common_symbols/build.zig", .{
11 .build_modes = true,
12 });
13
14 cases.addBuildFile("test/link/common_symbols_alignment/build.zig", .{
15 .build_modes = true,
16 });
17
18 cases.addBuildFile("test/link/interdependent_static_c_libs/build.zig", .{
19 .build_modes = true,
20 });
21
22 cases.addBuildFile("test/link/static_lib_as_system_lib/build.zig", .{
23 .build_modes = true,
24 });
25
26 addWasmCases(cases);
27 addMachOCases(cases);
28}
29
30fn addWasmCases(cases: *tests.StandaloneContext) void {
31 cases.addBuildFile("test/link/wasm/archive/build.zig", .{
32 .build_modes = true,
33 .requires_stage2 = true,
34 });
35
36 cases.addBuildFile("test/link/wasm/basic-features/build.zig", .{
37 .requires_stage2 = true,
38 });
39
40 cases.addBuildFile("test/link/wasm/bss/build.zig", .{
41 .build_modes = false,
42 .requires_stage2 = true,
43 });
44
45 cases.addBuildFile("test/link/wasm/export/build.zig", .{
46 .build_modes = true,
47 .requires_stage2 = true,
48 });
49
50 // TODO: Fix open handle in wasm-linker refraining rename from working on Windows.
51 if (builtin.os.tag != .windows) {
52 cases.addBuildFile("test/link/wasm/export-data/build.zig", .{});
53 }
54
55 cases.addBuildFile("test/link/wasm/extern/build.zig", .{
56 .build_modes = true,
57 .requires_stage2 = true,
58 .use_emulation = true,
59 });
60
61 cases.addBuildFile("test/link/wasm/extern-mangle/build.zig", .{
62 .build_modes = true,
63 .requires_stage2 = true,
64 });
65
66 cases.addBuildFile("test/link/wasm/function-table/build.zig", .{
67 .build_modes = true,
68 .requires_stage2 = true,
69 });
70
71 cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{
72 .requires_stage2 = true,
73 });
74
75 cases.addBuildFile("test/link/wasm/producers/build.zig", .{
76 .build_modes = true,
77 .requires_stage2 = true,
78 });
79
80 cases.addBuildFile("test/link/wasm/segments/build.zig", .{
81 .build_modes = true,
82 .requires_stage2 = true,
83 });
84
85 cases.addBuildFile("test/link/wasm/stack_pointer/build.zig", .{
86 .build_modes = true,
87 .requires_stage2 = true,
88 });
89
90 cases.addBuildFile("test/link/wasm/type/build.zig", .{
91 .build_modes = true,
92 .requires_stage2 = true,
93 });
94}
95
96fn addMachOCases(cases: *tests.StandaloneContext) void {
97 cases.addBuildFile("test/link/macho/bugs/13056/build.zig", .{
98 .build_modes = true,
99 .requires_macos_sdk = true,
100 .requires_symlinks = true,
101 });
102
103 cases.addBuildFile("test/link/macho/bugs/13457/build.zig", .{
104 .build_modes = true,
105 .requires_symlinks = true,
106 });
107
108 cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{
109 .build_modes = false,
110 .requires_symlinks = true,
111 });
112
113 cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{
114 .build_modes = true,
115 .requires_macos_sdk = true,
116 .requires_symlinks = true,
117 });
118
119 cases.addBuildFile("test/link/macho/dylib/build.zig", .{
120 .build_modes = true,
121 .requires_symlinks = true,
122 });
123
124 cases.addBuildFile("test/link/macho/empty/build.zig", .{
125 .build_modes = true,
126 .requires_symlinks = true,
127 });
128
129 cases.addBuildFile("test/link/macho/entry/build.zig", .{
130 .build_modes = true,
131 .requires_symlinks = true,
132 });
133
134 cases.addBuildFile("test/link/macho/headerpad/build.zig", .{
135 .build_modes = true,
136 .requires_macos_sdk = true,
137 .requires_symlinks = true,
138 });
139
140 cases.addBuildFile("test/link/macho/linksection/build.zig", .{
141 .build_modes = true,
142 .requires_symlinks = true,
143 });
144
145 cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{
146 .build_modes = true,
147 .requires_macos_sdk = true,
148 .requires_symlinks = true,
149 });
150
151 cases.addBuildFile("test/link/macho/needed_library/build.zig", .{
152 .build_modes = true,
153 .requires_symlinks = true,
154 });
155
156 cases.addBuildFile("test/link/macho/objc/build.zig", .{
157 .build_modes = true,
158 .requires_macos_sdk = true,
159 .requires_symlinks = true,
160 });
161
162 cases.addBuildFile("test/link/macho/objcpp/build.zig", .{
163 .build_modes = true,
164 .requires_macos_sdk = true,
165 .requires_symlinks = true,
166 });
167
168 cases.addBuildFile("test/link/macho/pagezero/build.zig", .{
169 .build_modes = false,
170 .requires_symlinks = true,
171 });
172
173 cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{
174 .build_modes = true,
175 .requires_symlinks = true,
176 });
177
178 cases.addBuildFile("test/link/macho/stack_size/build.zig", .{
179 .build_modes = true,
180 .requires_symlinks = true,
181 });
182
183 cases.addBuildFile("test/link/macho/strict_validation/build.zig", .{
184 .build_modes = true,
185 .requires_symlinks = true,
186 });
187
188 cases.addBuildFile("test/link/macho/tls/build.zig", .{
189 .build_modes = true,
190 .requires_symlinks = true,
191 });
192
193 cases.addBuildFile("test/link/macho/unwind_info/build.zig", .{
194 .build_modes = true,
195 .requires_symlinks = true,
196 });
197
198 cases.addBuildFile("test/link/macho/uuid/build.zig", .{
199 .build_modes = false,
200 .requires_symlinks = true,
201 });
202
203 cases.addBuildFile("test/link/macho/weak_library/build.zig", .{
204 .build_modes = true,
205 .requires_symlinks = true,
206 });
207
208 cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{
209 .build_modes = true,
210 .requires_macos_sdk = true,
211 .requires_symlinks = true,
212 });
213}
1pub const Case = struct {
2 build_root: []const u8,
3 import: type,
4};
5
6pub const cases = [_]Case{
7 .{
8 .build_root = "test/link/bss",
9 .import = @import("link/bss/build.zig"),
10 },
11 .{
12 .build_root = "test/link/common_symbols",
13 .import = @import("link/common_symbols/build.zig"),
14 },
15 .{
16 .build_root = "test/link/common_symbols_alignment",
17 .import = @import("link/common_symbols_alignment/build.zig"),
18 },
19 .{
20 .build_root = "test/link/interdependent_static_c_libs",
21 .import = @import("link/interdependent_static_c_libs/build.zig"),
22 },
23
24 // WASM Cases
25 .{
26 .build_root = "test/link/wasm/archive",
27 .import = @import("link/wasm/archive/build.zig"),
28 },
29 .{
30 .build_root = "test/link/wasm/basic-features",
31 .import = @import("link/wasm/basic-features/build.zig"),
32 },
33 .{
34 .build_root = "test/link/wasm/bss",
35 .import = @import("link/wasm/bss/build.zig"),
36 },
37 .{
38 .build_root = "test/link/wasm/export",
39 .import = @import("link/wasm/export/build.zig"),
40 },
41 .{
42 .build_root = "test/link/wasm/export-data",
43 .import = @import("link/wasm/export-data/build.zig"),
44 },
45 .{
46 .build_root = "test/link/wasm/extern",
47 .import = @import("link/wasm/extern/build.zig"),
48 },
49 .{
50 .build_root = "test/link/wasm/extern-mangle",
51 .import = @import("link/wasm/extern-mangle/build.zig"),
52 },
53 .{
54 .build_root = "test/link/wasm/function-table",
55 .import = @import("link/wasm/function-table/build.zig"),
56 },
57 .{
58 .build_root = "test/link/wasm/infer-features",
59 .import = @import("link/wasm/infer-features/build.zig"),
60 },
61 .{
62 .build_root = "test/link/wasm/producers",
63 .import = @import("link/wasm/producers/build.zig"),
64 },
65 .{
66 .build_root = "test/link/wasm/segments",
67 .import = @import("link/wasm/segments/build.zig"),
68 },
69 .{
70 .build_root = "test/link/wasm/stack_pointer",
71 .import = @import("link/wasm/stack_pointer/build.zig"),
72 },
73 .{
74 .build_root = "test/link/wasm/type",
75 .import = @import("link/wasm/type/build.zig"),
76 },
77
78 // Mach-O Cases
79 .{
80 .build_root = "test/link/macho/bugs/13056",
81 .import = @import("link/macho/bugs/13056/build.zig"),
82 },
83 .{
84 .build_root = "test/link/macho/bugs/13457",
85 .import = @import("link/macho/bugs/13457/build.zig"),
86 },
87 .{
88 .build_root = "test/link/macho/dead_strip",
89 .import = @import("link/macho/dead_strip/build.zig"),
90 },
91 .{
92 .build_root = "test/link/macho/dead_strip_dylibs",
93 .import = @import("link/macho/dead_strip_dylibs/build.zig"),
94 },
95 .{
96 .build_root = "test/link/macho/dylib",
97 .import = @import("link/macho/dylib/build.zig"),
98 },
99 .{
100 .build_root = "test/link/macho/empty",
101 .import = @import("link/macho/empty/build.zig"),
102 },
103 .{
104 .build_root = "test/link/macho/entry",
105 .import = @import("link/macho/entry/build.zig"),
106 },
107 .{
108 .build_root = "test/link/macho/headerpad",
109 .import = @import("link/macho/headerpad/build.zig"),
110 },
111 .{
112 .build_root = "test/link/macho/linksection",
113 .import = @import("link/macho/linksection/build.zig"),
114 },
115 .{
116 .build_root = "test/link/macho/needed_framework",
117 .import = @import("link/macho/needed_framework/build.zig"),
118 },
119 .{
120 .build_root = "test/link/macho/needed_library",
121 .import = @import("link/macho/needed_library/build.zig"),
122 },
123 .{
124 .build_root = "test/link/macho/objc",
125 .import = @import("link/macho/objc/build.zig"),
126 },
127 .{
128 .build_root = "test/link/macho/objcpp",
129 .import = @import("link/macho/objcpp/build.zig"),
130 },
131 .{
132 .build_root = "test/link/macho/pagezero",
133 .import = @import("link/macho/pagezero/build.zig"),
134 },
135 .{
136 .build_root = "test/link/macho/search_strategy",
137 .import = @import("link/macho/search_strategy/build.zig"),
138 },
139 .{
140 .build_root = "test/link/macho/stack_size",
141 .import = @import("link/macho/stack_size/build.zig"),
142 },
143 .{
144 .build_root = "test/link/macho/strict_validation",
145 .import = @import("link/macho/strict_validation/build.zig"),
146 },
147 .{
148 .build_root = "test/link/macho/tls",
149 .import = @import("link/macho/tls/build.zig"),
150 },
151 .{
152 .build_root = "test/link/macho/unwind_info",
153 .import = @import("link/macho/unwind_info/build.zig"),
154 },
155 // TODO: re-enable this test. It currently has some incompatibilities with
156 // the new build system API. In particular, it depends on installing the build
157 // artifacts, which should be unnecessary, and it has a custom build step that
158 // prints directly to stderr instead of failing the step with an error message.
159 //.{
160 // .build_root = "test/link/macho/uuid",
161 // .import = @import("link/macho/uuid/build.zig"),
162 //},
163
164 .{
165 .build_root = "test/link/macho/weak_library",
166 .import = @import("link/macho/weak_library/build.zig"),
167 },
168 .{
169 .build_root = "test/link/macho/weak_framework",
170 .import = @import("link/macho/weak_framework/build.zig"),
171 },
172};
test/link/bss/build.zig+3-3
......@@ -1,17 +1,17 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
54 const test_step = b.step("test", "Test");
5 b.default_step = test_step;
66
77 const exe = b.addExecutable(.{
88 .name = "bss",
99 .root_source_file = .{ .path = "main.zig" },
10 .optimize = optimize,
10 .optimize = .Debug,
1111 });
12 b.default_step.dependOn(&exe.step);
1312
1413 const run = exe.run();
1514 run.expectStdOutEqual("0, 1, 0\n");
15
1616 test_step.dependOn(&run.step);
1717}
test/link/bss/main.zig+1-1
......@@ -1,7 +1,7 @@
11const std = @import("std");
22
33// Stress test zerofill layout
4var buffer: [0x1000000]u64 = undefined;
4var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000;
55
66pub fn main() anyerror!void {
77 buffer[0x10] = 1;
test/link/common_symbols/build.zig+10-3
......@@ -1,8 +1,16 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 add(b, test_step, .Debug);
8 add(b, test_step, .ReleaseFast);
9 add(b, test_step, .ReleaseSmall);
10 add(b, test_step, .ReleaseSafe);
11}
512
13fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
614 const lib_a = b.addStaticLibrary(.{
715 .name = "a",
816 .optimize = optimize,
......@@ -16,6 +24,5 @@ pub fn build(b: *std.Build) void {
1624 });
1725 test_exe.linkLibrary(lib_a);
1826
19 const test_step = b.step("test", "Test it");
20 test_step.dependOn(&test_exe.step);
27 test_step.dependOn(&test_exe.run().step);
2128}
test/link/common_symbols_alignment/build.zig+11-6
......@@ -1,23 +1,28 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target = b.standardTargetOptions(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 add(b, test_step, .Debug);
8 add(b, test_step, .ReleaseFast);
9 add(b, test_step, .ReleaseSmall);
10 add(b, test_step, .ReleaseSafe);
11}
612
13fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
714 const lib_a = b.addStaticLibrary(.{
815 .name = "a",
916 .optimize = optimize,
10 .target = target,
17 .target = .{},
1118 });
1219 lib_a.addCSourceFiles(&.{"a.c"}, &.{"-fcommon"});
1320
1421 const test_exe = b.addTest(.{
1522 .root_source_file = .{ .path = "main.zig" },
1623 .optimize = optimize,
17 .target = target,
1824 });
1925 test_exe.linkLibrary(lib_a);
2026
21 const test_step = b.step("test", "Test it");
22 test_step.dependOn(&test_exe.step);
27 test_step.dependOn(&test_exe.run().step);
2328}
test/link/interdependent_static_c_libs/build.zig+12-7
......@@ -1,13 +1,20 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target = b.standardTargetOptions(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 add(b, test_step, .Debug);
8 add(b, test_step, .ReleaseFast);
9 add(b, test_step, .ReleaseSmall);
10 add(b, test_step, .ReleaseSafe);
11}
612
13fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
714 const lib_a = b.addStaticLibrary(.{
815 .name = "a",
916 .optimize = optimize,
10 .target = target,
17 .target = .{},
1118 });
1219 lib_a.addCSourceFile("a.c", &[_][]const u8{});
1320 lib_a.addIncludePath(".");
......@@ -15,7 +22,7 @@ pub fn build(b: *std.Build) void {
1522 const lib_b = b.addStaticLibrary(.{
1623 .name = "b",
1724 .optimize = optimize,
18 .target = target,
25 .target = .{},
1926 });
2027 lib_b.addCSourceFile("b.c", &[_][]const u8{});
2128 lib_b.addIncludePath(".");
......@@ -23,12 +30,10 @@ pub fn build(b: *std.Build) void {
2330 const test_exe = b.addTest(.{
2431 .root_source_file = .{ .path = "main.zig" },
2532 .optimize = optimize,
26 .target = target,
2733 });
2834 test_exe.linkLibrary(lib_a);
2935 test_exe.linkLibrary(lib_b);
3036 test_exe.addIncludePath(".");
3137
32 const test_step = b.step("test", "Test it");
33 test_step.dependOn(&test_exe.step);
38 test_step.dependOn(&test_exe.run().step);
3439}
test/link/macho/bugs/13056/build.zig+12-4
......@@ -1,20 +1,28 @@
11const std = @import("std");
22
3pub const requires_macos_sdk = true;
4pub const requires_symlinks = true;
5
36pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
515
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
617 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
718 const target_info = std.zig.system.NativeTargetInfo.detect(target) catch unreachable;
819 const sdk = std.zig.system.darwin.getDarwinSDK(b.allocator, target_info.target) orelse
920 @panic("macOS SDK is required to run the test");
1021
11 const test_step = b.step("test", "Test the program");
12
1322 const exe = b.addExecutable(.{
1423 .name = "test",
1524 .optimize = optimize,
1625 });
17 b.default_step.dependOn(&exe.step);
1826 exe.addIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include" }) catch unreachable);
1927 exe.addIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include/c++/v1" }) catch unreachable);
2028 exe.addCSourceFile("test.cpp", &.{
test/link/macho/bugs/13457/build.zig+16-4
......@@ -1,10 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
614
7 const test_step = b.step("test", "Test the program");
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
817
918 const exe = b.addExecutable(.{
1019 .name = "test",
......@@ -13,6 +22,9 @@ pub fn build(b: *std.Build) void {
1322 .target = target,
1423 });
1524
16 const run = exe.runEmulatable();
25 const run = b.addRunArtifact(exe);
26 run.skip_foreign_checks = true;
27 run.expectStdOutEqual("");
28
1729 test_step.dependOn(&run.step);
1830}
test/link/macho/dead_strip/build.zig+10-7
......@@ -1,17 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
6 const optimize: std.builtin.OptimizeMode = .Debug;
57 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
68
79 const test_step = b.step("test", "Test the program");
8 test_step.dependOn(b.getInstallStep());
10 b.default_step = test_step;
911
1012 {
1113 // Without -dead_strip, we expect `iAmUnused` symbol present
12 const exe = createScenario(b, optimize, target);
14 const exe = createScenario(b, optimize, target, "no-gc");
1315
14 const check = exe.checkObject(.macho);
16 const check = exe.checkObject();
1517 check.checkInSymtab();
1618 check.checkNext("{*} (__TEXT,__text) external _iAmUnused");
1719
......@@ -22,10 +24,10 @@ pub fn build(b: *std.Build) void {
2224
2325 {
2426 // With -dead_strip, no `iAmUnused` symbol should be present
25 const exe = createScenario(b, optimize, target);
27 const exe = createScenario(b, optimize, target, "yes-gc");
2628 exe.link_gc_sections = true;
2729
28 const check = exe.checkObject(.macho);
30 const check = exe.checkObject();
2931 check.checkInSymtab();
3032 check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused");
3133
......@@ -39,9 +41,10 @@ fn createScenario(
3941 b: *std.Build,
4042 optimize: std.builtin.OptimizeMode,
4143 target: std.zig.CrossTarget,
44 name: []const u8,
4245) *std.Build.CompileStep {
4346 const exe = b.addExecutable(.{
44 .name = "test",
47 .name = name,
4548 .optimize = optimize,
4649 .target = target,
4750 });
test/link/macho/dead_strip_dylibs/build.zig+22-10
......@@ -1,16 +1,24 @@
11const std = @import("std");
22
3pub const requires_macos_sdk = true;
4pub const requires_symlinks = true;
5
36pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
59
6 const test_step = b.step("test", "Test the program");
7 test_step.dependOn(b.getInstallStep());
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
815
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
917 {
1018 // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable
11 const exe = createScenario(b, optimize);
19 const exe = createScenario(b, optimize, "no-dead-strip");
1220
13 const check = exe.checkObject(.macho);
21 const check = exe.checkObject();
1422 check.checkStart("cmd LOAD_DYLIB");
1523 check.checkNext("name {*}Cocoa");
1624
......@@ -25,18 +33,22 @@ pub fn build(b: *std.Build) void {
2533
2634 {
2735 // With -dead_strip_dylibs, we should include liba.dylib as it's unreachable
28 const exe = createScenario(b, optimize);
36 const exe = createScenario(b, optimize, "yes-dead-strip");
2937 exe.dead_strip_dylibs = true;
3038
31 const run_cmd = exe.run();
32 run_cmd.expected_term = .{ .Exited = @bitCast(u8, @as(i8, -2)) }; // should fail
39 const run_cmd = b.addRunArtifact(exe);
40 run_cmd.expectExitCode(@bitCast(u8, @as(i8, -2))); // should fail
3341 test_step.dependOn(&run_cmd.step);
3442 }
3543}
3644
37fn createScenario(b: *std.Build, optimize: std.builtin.OptimizeMode) *std.Build.CompileStep {
45fn createScenario(
46 b: *std.Build,
47 optimize: std.builtin.OptimizeMode,
48 name: []const u8,
49) *std.Build.CompileStep {
3850 const exe = b.addExecutable(.{
39 .name = "test",
51 .name = name,
4052 .optimize = optimize,
4153 });
4254 exe.addCSourceFile("main.c", &[0][]const u8{});
test/link/macho/dylib/build.zig+20-11
......@@ -1,11 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
917
1018 const dylib = b.addSharedLibrary(.{
1119 .name = "a",
......@@ -15,9 +23,8 @@ pub fn build(b: *std.Build) void {
1523 });
1624 dylib.addCSourceFile("a.c", &.{});
1725 dylib.linkLibC();
18 dylib.install();
1926
20 const check_dylib = dylib.checkObject(.macho);
27 const check_dylib = dylib.checkObject();
2128 check_dylib.checkStart("cmd ID_DYLIB");
2229 check_dylib.checkNext("name @rpath/liba.dylib");
2330 check_dylib.checkNext("timestamp 2");
......@@ -33,11 +40,11 @@ pub fn build(b: *std.Build) void {
3340 });
3441 exe.addCSourceFile("main.c", &.{});
3542 exe.linkSystemLibrary("a");
43 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
44 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
3645 exe.linkLibC();
37 exe.addLibraryPath(b.pathFromRoot("zig-out/lib/"));
38 exe.addRPath(b.pathFromRoot("zig-out/lib"));
3946
40 const check_exe = exe.checkObject(.macho);
47 const check_exe = exe.checkObject();
4148 check_exe.checkStart("cmd LOAD_DYLIB");
4249 check_exe.checkNext("name @rpath/liba.dylib");
4350 check_exe.checkNext("timestamp 2");
......@@ -45,10 +52,12 @@ pub fn build(b: *std.Build) void {
4552 check_exe.checkNext("compatibility version 10000");
4653
4754 check_exe.checkStart("cmd RPATH");
48 check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable);
55 // TODO check this (perhaps with `checkNextFileSource(dylib.getOutputDirectorySource())`)
56 //check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{
57 // b.pathFromRoot("zig-out/lib"),
58 //}) catch unreachable);
4959
5060 const run = check_exe.runAndCompare();
51 run.cwd = b.pathFromRoot(".");
5261 run.expectStdOutEqual("Hello world");
5362 test_step.dependOn(&run.step);
5463}
test/link/macho/empty/build.zig+14-5
......@@ -1,11 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test the program");
8 test_step.dependOn(b.getInstallStep());
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
917
1018 const exe = b.addExecutable(.{
1119 .name = "test",
......@@ -16,7 +24,8 @@ pub fn build(b: *std.Build) void {
1624 exe.addCSourceFile("empty.c", &[0][]const u8{});
1725 exe.linkLibC();
1826
19 const run_cmd = std.Build.EmulatableRunStep.create(b, "run", exe);
27 const run_cmd = b.addRunArtifact(exe);
28 run_cmd.skip_foreign_checks = true;
2029 run_cmd.expectStdOutEqual("Hello!\n");
2130 test_step.dependOn(&run_cmd.step);
2231}
test/link/macho/entry/build.zig+11-4
......@@ -1,11 +1,18 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
58
6 const test_step = b.step("test", "Test");
7 test_step.dependOn(b.getInstallStep());
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
814
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
916 const exe = b.addExecutable(.{
1017 .name = "main",
1118 .optimize = optimize,
......@@ -15,7 +22,7 @@ pub fn build(b: *std.Build) void {
1522 exe.linkLibC();
1623 exe.entry_symbol_name = "_non_main";
1724
18 const check_exe = exe.checkObject(.macho);
25 const check_exe = exe.checkObject();
1926
2027 check_exe.checkStart("segname __TEXT");
2128 check_exe.checkNext("vmaddr {vmaddr}");
test/link/macho/headerpad/build.zig+25-13
......@@ -1,18 +1,26 @@
11const std = @import("std");
22const builtin = @import("builtin");
33
4pub const requires_symlinks = true;
5pub const requires_macos_sdk = true;
6
47pub fn build(b: *std.Build) void {
5 const optimize = b.standardOptimizeOption(.{});
8 const test_step = b.step("test", "Test it");
9 b.default_step = test_step;
610
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
11 add(b, test_step, .Debug);
12 add(b, test_step, .ReleaseFast);
13 add(b, test_step, .ReleaseSmall);
14 add(b, test_step, .ReleaseSafe);
15}
916
17fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
1018 {
1119 // Test -headerpad_max_install_names
12 const exe = simpleExe(b, optimize);
20 const exe = simpleExe(b, optimize, "headerpad_max_install_names");
1321 exe.headerpad_max_install_names = true;
1422
15 const check = exe.checkObject(.macho);
23 const check = exe.checkObject();
1624 check.checkStart("sectname __text");
1725 check.checkNext("offset {offset}");
1826
......@@ -34,10 +42,10 @@ pub fn build(b: *std.Build) void {
3442
3543 {
3644 // Test -headerpad
37 const exe = simpleExe(b, optimize);
45 const exe = simpleExe(b, optimize, "headerpad");
3846 exe.headerpad_size = 0x10000;
3947
40 const check = exe.checkObject(.macho);
48 const check = exe.checkObject();
4149 check.checkStart("sectname __text");
4250 check.checkNext("offset {offset}");
4351 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
......@@ -50,11 +58,11 @@ pub fn build(b: *std.Build) void {
5058
5159 {
5260 // Test both flags with -headerpad overriding -headerpad_max_install_names
53 const exe = simpleExe(b, optimize);
61 const exe = simpleExe(b, optimize, "headerpad_overriding");
5462 exe.headerpad_max_install_names = true;
5563 exe.headerpad_size = 0x10000;
5664
57 const check = exe.checkObject(.macho);
65 const check = exe.checkObject();
5866 check.checkStart("sectname __text");
5967 check.checkNext("offset {offset}");
6068 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
......@@ -67,11 +75,11 @@ pub fn build(b: *std.Build) void {
6775
6876 {
6977 // Test both flags with -headerpad_max_install_names overriding -headerpad
70 const exe = simpleExe(b, optimize);
78 const exe = simpleExe(b, optimize, "headerpad_max_install_names_overriding");
7179 exe.headerpad_size = 0x1000;
7280 exe.headerpad_max_install_names = true;
7381
74 const check = exe.checkObject(.macho);
82 const check = exe.checkObject();
7583 check.checkStart("sectname __text");
7684 check.checkNext("offset {offset}");
7785
......@@ -92,9 +100,13 @@ pub fn build(b: *std.Build) void {
92100 }
93101}
94102
95fn simpleExe(b: *std.Build, optimize: std.builtin.OptimizeMode) *std.Build.CompileStep {
103fn simpleExe(
104 b: *std.Build,
105 optimize: std.builtin.OptimizeMode,
106 name: []const u8,
107) *std.Build.CompileStep {
96108 const exe = b.addExecutable(.{
97 .name = "main",
109 .name = name,
98110 .optimize = optimize,
99111 });
100112 exe.addCSourceFile("main.c", &.{});
test/link/macho/linksection/build.zig+13-5
......@@ -1,11 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target = std.zig.CrossTarget{ .os_tag = .macos };
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target = std.zig.CrossTarget{ .os_tag = .macos };
917
1018 const obj = b.addObject(.{
1119 .name = "test",
......@@ -14,7 +22,7 @@ pub fn build(b: *std.Build) void {
1422 .target = target,
1523 });
1624
17 const check = obj.checkObject(.macho);
25 const check = obj.checkObject();
1826
1927 check.checkInSymtab();
2028 check.checkNext("{*} (__DATA,__TestGlobal) external _test_global");
test/link/macho/needed_framework/build.zig+12-4
......@@ -1,11 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
36pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
59
6 const test_step = b.step("test", "Test the program");
7 test_step.dependOn(b.getInstallStep());
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
815
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
917 // -dead_strip_dylibs
1018 // -needed_framework Cocoa
1119 const exe = b.addExecutable(.{
......@@ -17,7 +25,7 @@ pub fn build(b: *std.Build) void {
1725 exe.linkFrameworkNeeded("Cocoa");
1826 exe.dead_strip_dylibs = true;
1927
20 const check = exe.checkObject(.macho);
28 const check = exe.checkObject();
2129 check.checkStart("cmd LOAD_DYLIB");
2230 check.checkNext("name {*}Cocoa");
2331 test_step.dependOn(&check.step);
test/link/macho/needed_library/build.zig+16-8
......@@ -1,11 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test the program");
8 test_step.dependOn(b.getInstallStep());
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
917
1018 const dylib = b.addSharedLibrary(.{
1119 .name = "a",
......@@ -15,7 +23,6 @@ pub fn build(b: *std.Build) void {
1523 });
1624 dylib.addCSourceFile("a.c", &.{});
1725 dylib.linkLibC();
18 dylib.install();
1926
2027 // -dead_strip_dylibs
2128 // -needed-la
......@@ -27,14 +34,15 @@ pub fn build(b: *std.Build) void {
2734 exe.addCSourceFile("main.c", &[0][]const u8{});
2835 exe.linkLibC();
2936 exe.linkSystemLibraryNeeded("a");
30 exe.addLibraryPath(b.pathFromRoot("zig-out/lib"));
31 exe.addRPath(b.pathFromRoot("zig-out/lib"));
37 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
38 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
3239 exe.dead_strip_dylibs = true;
3340
34 const check = exe.checkObject(.macho);
41 const check = exe.checkObject();
3542 check.checkStart("cmd LOAD_DYLIB");
3643 check.checkNext("name @rpath/liba.dylib");
3744
3845 const run_cmd = check.runAndCompare();
46 run_cmd.expectStdOutEqual("");
3947 test_step.dependOn(&run_cmd.step);
4048}
test/link/macho/objc/build.zig+14-3
......@@ -1,10 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
36pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
59
6 const test_step = b.step("test", "Test the program");
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
715
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
817 const exe = b.addExecutable(.{
918 .name = "test",
1019 .optimize = optimize,
......@@ -17,6 +26,8 @@ pub fn build(b: *std.Build) void {
1726 // populate paths to the sysroot here.
1827 exe.linkFramework("Foundation");
1928
20 const run_cmd = std.Build.EmulatableRunStep.create(b, "run", exe);
29 const run_cmd = b.addRunArtifact(exe);
30 run_cmd.skip_foreign_checks = true;
31 run_cmd.expectStdOutEqual("");
2132 test_step.dependOn(&run_cmd.step);
2233}
test/link/macho/objcpp/build.zig+11-2
......@@ -1,10 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
36pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
59
6 const test_step = b.step("test", "Test the program");
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
715
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
817 const exe = b.addExecutable(.{
918 .name = "test",
1019 .optimize = optimize,
test/link/macho/pagezero/build.zig+8-6
......@@ -1,11 +1,13 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9 const optimize: std.builtin.OptimizeMode = .Debug;
10 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
911
1012 {
1113 const exe = b.addExecutable(.{
......@@ -17,7 +19,7 @@ pub fn build(b: *std.Build) void {
1719 exe.linkLibC();
1820 exe.pagezero_size = 0x4000;
1921
20 const check = exe.checkObject(.macho);
22 const check = exe.checkObject();
2123 check.checkStart("LC 0");
2224 check.checkNext("segname __PAGEZERO");
2325 check.checkNext("vmaddr 0");
......@@ -39,7 +41,7 @@ pub fn build(b: *std.Build) void {
3941 exe.linkLibC();
4042 exe.pagezero_size = 0;
4143
42 const check = exe.checkObject(.macho);
44 const check = exe.checkObject();
4345 check.checkStart("LC 0");
4446 check.checkNext("segname __TEXT");
4547 check.checkNext("vmaddr 0");
test/link/macho/search_strategy/build.zig+26-20
......@@ -1,34 +1,41 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
917
1018 {
1119 // -search_dylibs_first
12 const exe = createScenario(b, optimize, target);
20 const exe = createScenario(b, optimize, target, "search_dylibs_first");
1321 exe.search_strategy = .dylibs_first;
1422
15 const check = exe.checkObject(.macho);
23 const check = exe.checkObject();
1624 check.checkStart("cmd LOAD_DYLIB");
17 check.checkNext("name @rpath/liba.dylib");
25 check.checkNext("name @rpath/libsearch_dylibs_first.dylib");
1826
1927 const run = check.runAndCompare();
20 run.cwd = b.pathFromRoot(".");
2128 run.expectStdOutEqual("Hello world");
2229 test_step.dependOn(&run.step);
2330 }
2431
2532 {
2633 // -search_paths_first
27 const exe = createScenario(b, optimize, target);
34 const exe = createScenario(b, optimize, target, "search_paths_first");
2835 exe.search_strategy = .paths_first;
2936
30 const run = std.Build.EmulatableRunStep.create(b, "run", exe);
31 run.cwd = b.pathFromRoot(".");
37 const run = b.addRunArtifact(exe);
38 run.skip_foreign_checks = true;
3239 run.expectStdOutEqual("Hello world");
3340 test_step.dependOn(&run.step);
3441 }
......@@ -38,9 +45,10 @@ fn createScenario(
3845 b: *std.Build,
3946 optimize: std.builtin.OptimizeMode,
4047 target: std.zig.CrossTarget,
48 name: []const u8,
4149) *std.Build.CompileStep {
4250 const static = b.addStaticLibrary(.{
43 .name = "a",
51 .name = name,
4452 .optimize = optimize,
4553 .target = target,
4654 });
......@@ -49,10 +57,9 @@ fn createScenario(
4957 static.override_dest_dir = std.Build.InstallDir{
5058 .custom = "static",
5159 };
52 static.install();
5360
5461 const dylib = b.addSharedLibrary(.{
55 .name = "a",
62 .name = name,
5663 .version = .{ .major = 1, .minor = 0 },
5764 .optimize = optimize,
5865 .target = target,
......@@ -62,18 +69,17 @@ fn createScenario(
6269 dylib.override_dest_dir = std.Build.InstallDir{
6370 .custom = "dynamic",
6471 };
65 dylib.install();
6672
6773 const exe = b.addExecutable(.{
68 .name = "main",
74 .name = name,
6975 .optimize = optimize,
7076 .target = target,
7177 });
7278 exe.addCSourceFile("main.c", &.{});
73 exe.linkSystemLibraryName("a");
79 exe.linkSystemLibraryName(name);
7480 exe.linkLibC();
75 exe.addLibraryPath(b.pathFromRoot("zig-out/static"));
76 exe.addLibraryPath(b.pathFromRoot("zig-out/dynamic"));
77 exe.addRPath(b.pathFromRoot("zig-out/dynamic"));
81 exe.addLibraryPathDirectorySource(static.getOutputDirectorySource());
82 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
83 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
7884 return exe;
7985}
test/link/macho/stack_size/build.zig+14-5
......@@ -1,11 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
917
1018 const exe = b.addExecutable(.{
1119 .name = "main",
......@@ -16,10 +24,11 @@ pub fn build(b: *std.Build) void {
1624 exe.linkLibC();
1725 exe.stack_size = 0x100000000;
1826
19 const check_exe = exe.checkObject(.macho);
27 const check_exe = exe.checkObject();
2028 check_exe.checkStart("cmd MAIN");
2129 check_exe.checkNext("stacksize 100000000");
2230
2331 const run = check_exe.runAndCompare();
32 run.expectStdOutEqual("");
2433 test_step.dependOn(&run.step);
2534}
test/link/macho/strict_validation/build.zig+13-5
......@@ -1,12 +1,20 @@
11const std = @import("std");
22const builtin = @import("builtin");
33
4pub const requires_symlinks = true;
5
46pub fn build(b: *std.Build) void {
5 const optimize = b.standardOptimizeOption(.{});
6 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
79
8 const test_step = b.step("test", "Test");
9 test_step.dependOn(b.getInstallStep());
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
1018
1119 const exe = b.addExecutable(.{
1220 .name = "main",
......@@ -16,7 +24,7 @@ pub fn build(b: *std.Build) void {
1624 });
1725 exe.linkLibC();
1826
19 const check_exe = exe.checkObject(.macho);
27 const check_exe = exe.checkObject();
2028
2129 check_exe.checkStart("cmd SEGMENT_64");
2230 check_exe.checkNext("segname __LINKEDIT");
test/link/macho/tls/build.zig+16-3
......@@ -1,7 +1,18 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
516 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
617
718 const lib = b.addSharedLibrary(.{
......@@ -21,6 +32,8 @@ pub fn build(b: *std.Build) void {
2132 test_exe.linkLibrary(lib);
2233 test_exe.linkLibC();
2334
24 const test_step = b.step("test", "Test it");
25 test_step.dependOn(&test_exe.step);
35 const run = test_exe.run();
36 run.skip_foreign_checks = true;
37
38 test_step.dependOn(&run.step);
2639}
test/link/macho/unwind_info/build.zig+19-8
......@@ -1,14 +1,23 @@
11const std = @import("std");
22const builtin = @import("builtin");
33
4pub const requires_symlinks = true;
5
46pub fn build(b: *std.Build) void {
5 const optimize = b.standardOptimizeOption(.{});
6 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
79
8 const test_step = b.step("test", "Test the program");
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
15
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
17 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
918
10 testUnwindInfo(b, test_step, optimize, target, false);
11 testUnwindInfo(b, test_step, optimize, target, true);
19 testUnwindInfo(b, test_step, optimize, target, false, "no-dead-strip");
20 testUnwindInfo(b, test_step, optimize, target, true, "yes-dead-strip");
1221}
1322
1423fn testUnwindInfo(
......@@ -17,11 +26,12 @@ fn testUnwindInfo(
1726 optimize: std.builtin.OptimizeMode,
1827 target: std.zig.CrossTarget,
1928 dead_strip: bool,
29 name: []const u8,
2030) void {
21 const exe = createScenario(b, optimize, target);
31 const exe = createScenario(b, optimize, target, name);
2232 exe.link_gc_sections = dead_strip;
2333
24 const check = exe.checkObject(.macho);
34 const check = exe.checkObject();
2535 check.checkStart("segname __TEXT");
2636 check.checkNext("sectname __gcc_except_tab");
2737 check.checkNext("sectname __unwind_info");
......@@ -54,9 +64,10 @@ fn createScenario(
5464 b: *std.Build,
5565 optimize: std.builtin.OptimizeMode,
5666 target: std.zig.CrossTarget,
67 name: []const u8,
5768) *std.Build.CompileStep {
5869 const exe = b.addExecutable(.{
59 .name = "test",
70 .name = name,
6071 .optimize = optimize,
6172 .target = target,
6273 });
test/link/macho/uuid/build.zig+23-62
......@@ -1,14 +1,16 @@
11const std = @import("std");
2const Builder = std.Build.Builder;
32const CompileStep = std.Build.CompileStep;
43const FileSource = std.Build.FileSource;
54const Step = std.Build.Step;
65
6pub const requires_symlinks = true;
7
78pub fn build(b: *std.Build) void {
89 const test_step = b.step("test", "Test");
9 test_step.dependOn(b.getInstallStep());
10 b.default_step = test_step;
1011
11 // We force cross-compilation to ensure we always pick a generic CPU with constant set of CPU features.
12 // We force cross-compilation to ensure we always pick a generic CPU with
13 // constant set of CPU features.
1214 const aarch64_macos = std.zig.CrossTarget{
1315 .cpu_arch = .aarch64,
1416 .os_tag = .macos,
......@@ -38,13 +40,15 @@ fn testUuid(
3840 // stay the same across builds.
3941 {
4042 const dylib = simpleDylib(b, optimize, target);
41 const install_step = installWithRename(dylib, "test1.dylib");
43 const install_step = b.addInstallArtifact(dylib);
44 install_step.dest_sub_path = "test1.dylib";
4245 install_step.step.dependOn(&dylib.step);
4346 }
4447 {
4548 const dylib = simpleDylib(b, optimize, target);
4649 dylib.strip = true;
47 const install_step = installWithRename(dylib, "test2.dylib");
50 const install_step = b.addInstallArtifact(dylib);
51 install_step.dest_sub_path = "test2.dylib";
4852 install_step.step.dependOn(&dylib.step);
4953 }
5054
......@@ -68,86 +72,43 @@ fn simpleDylib(
6872 return dylib;
6973}
7074
71fn installWithRename(cs: *CompileStep, name: []const u8) *InstallWithRename {
72 const step = InstallWithRename.create(cs.builder, cs.getOutputSource(), name);
73 cs.builder.getInstallStep().dependOn(&step.step);
74 return step;
75}
76
77const InstallWithRename = struct {
78 pub const base_id = .custom;
79
80 step: Step,
81 builder: *Builder,
82 source: FileSource,
83 name: []const u8,
84
85 pub fn create(
86 builder: *Builder,
87 source: FileSource,
88 name: []const u8,
89 ) *InstallWithRename {
90 const self = builder.allocator.create(InstallWithRename) catch @panic("OOM");
91 self.* = InstallWithRename{
92 .builder = builder,
93 .step = Step.init(.custom, builder.fmt("install and rename: {s} -> {s}", .{
94 source.getDisplayName(),
95 name,
96 }), builder.allocator, make),
97 .source = source,
98 .name = builder.dupe(name),
99 };
100 return self;
101 }
102
103 fn make(step: *Step) anyerror!void {
104 const self = @fieldParentPtr(InstallWithRename, "step", step);
105 const source_path = self.source.getPath(self.builder);
106 const target_path = self.builder.getInstallPath(.lib, self.name);
107 self.builder.updateFile(source_path, target_path) catch |err| {
108 std.log.err("Unable to rename: {s} -> {s}", .{ source_path, target_path });
109 return err;
110 };
111 }
112};
113
11475const CompareUuid = struct {
11576 pub const base_id = .custom;
11677
11778 step: Step,
118 builder: *Builder,
11979 lhs: []const u8,
12080 rhs: []const u8,
12181
122 pub fn create(builder: *Builder, lhs: []const u8, rhs: []const u8) *CompareUuid {
123 const self = builder.allocator.create(CompareUuid) catch @panic("OOM");
82 pub fn create(owner: *std.Build, lhs: []const u8, rhs: []const u8) *CompareUuid {
83 const self = owner.allocator.create(CompareUuid) catch @panic("OOM");
12484 self.* = CompareUuid{
125 .builder = builder,
126 .step = Step.init(
127 .custom,
128 builder.fmt("compare uuid: {s} and {s}", .{
85 .step = Step.init(.{
86 .id = base_id,
87 .name = owner.fmt("compare uuid: {s} and {s}", .{
12988 lhs,
13089 rhs,
13190 }),
132 builder.allocator,
133 make,
134 ),
91 .owner = owner,
92 .makeFn = make,
93 }),
13594 .lhs = lhs,
13695 .rhs = rhs,
13796 };
13897 return self;
13998 }
14099
141 fn make(step: *Step) anyerror!void {
100 fn make(step: *Step, prog_node: *std.Progress.Node) anyerror!void {
101 _ = prog_node;
102 const b = step.owner;
142103 const self = @fieldParentPtr(CompareUuid, "step", step);
143 const gpa = self.builder.allocator;
104 const gpa = b.allocator;
144105
145106 var lhs_uuid: [16]u8 = undefined;
146 const lhs_path = self.builder.getInstallPath(.lib, self.lhs);
107 const lhs_path = b.getInstallPath(.lib, self.lhs);
147108 try parseUuid(gpa, lhs_path, &lhs_uuid);
148109
149110 var rhs_uuid: [16]u8 = undefined;
150 const rhs_path = self.builder.getInstallPath(.lib, self.rhs);
111 const rhs_path = b.getInstallPath(.lib, self.rhs);
151112 try parseUuid(gpa, rhs_path, &rhs_uuid);
152113
153114 try std.testing.expectEqualStrings(&lhs_uuid, &rhs_uuid);
test/link/macho/weak_framework/build.zig+12-4
......@@ -1,11 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
36pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
59
6 const test_step = b.step("test", "Test the program");
7 test_step.dependOn(b.getInstallStep());
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
815
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
917 const exe = b.addExecutable(.{
1018 .name = "test",
1119 .optimize = optimize,
......@@ -14,7 +22,7 @@ pub fn build(b: *std.Build) void {
1422 exe.linkLibC();
1523 exe.linkFrameworkWeak("Cocoa");
1624
17 const check = exe.checkObject(.macho);
25 const check = exe.checkObject();
1826 check.checkStart("cmd LOAD_WEAK_DYLIB");
1927 check.checkNext("name {*}Cocoa");
2028 test_step.dependOn(&check.step);
test/link/macho/weak_library/build.zig+15-7
......@@ -1,11 +1,19 @@
11const std = @import("std");
22
3pub const requires_symlinks = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test the program");
8 test_step.dependOn(b.getInstallStep());
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
16 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
917
1018 const dylib = b.addSharedLibrary(.{
1119 .name = "a",
......@@ -25,10 +33,10 @@ pub fn build(b: *std.Build) void {
2533 exe.addCSourceFile("main.c", &[0][]const u8{});
2634 exe.linkLibC();
2735 exe.linkSystemLibraryWeak("a");
28 exe.addLibraryPath(b.pathFromRoot("zig-out/lib"));
29 exe.addRPath(b.pathFromRoot("zig-out/lib"));
36 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
37 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
3038
31 const check = exe.checkObject(.macho);
39 const check = exe.checkObject();
3240 check.checkStart("cmd LOAD_WEAK_DYLIB");
3341 check.checkNext("name @rpath/liba.dylib");
3442
test/link/static_lib_as_system_lib/a.c deleted-4
......@@ -1,4 +0,0 @@
1#include "a.h"
2int32_t add(int32_t a, int32_t b) {
3 return a + b;
4}
test/link/static_lib_as_system_lib/a.h deleted-2
......@@ -1,2 +0,0 @@
1#include <stdint.h>
2int32_t add(int32_t a, int32_t b);
test/link/static_lib_as_system_lib/build.zig deleted-29
......@@ -1,29 +0,0 @@
1const std = @import("std");
2
3pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target = b.standardTargetOptions(.{});
6
7 const lib_a = b.addStaticLibrary(.{
8 .name = "a",
9 .optimize = optimize,
10 .target = target,
11 });
12 lib_a.addCSourceFile("a.c", &[_][]const u8{});
13 lib_a.addIncludePath(".");
14 lib_a.install();
15
16 const test_exe = b.addTest(.{
17 .root_source_file = .{ .path = "main.zig" },
18 .optimize = optimize,
19 .target = target,
20 });
21 test_exe.linkSystemLibrary("a"); // force linking liba.a as -la
22 test_exe.addSystemIncludePath(".");
23 const search_path = std.fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "lib" }) catch unreachable;
24 test_exe.addLibraryPath(search_path);
25
26 const test_step = b.step("test", "Test it");
27 test_step.dependOn(b.getInstallStep());
28 test_step.dependOn(&test_exe.step);
29}
test/link/static_lib_as_system_lib/main.zig deleted-8
......@@ -1,8 +0,0 @@
1const std = @import("std");
2const expect = std.testing.expect;
3const c = @cImport(@cInclude("a.h"));
4
5test "import C add" {
6 const result = c.add(2, 1);
7 try expect(result == 3);
8}
test/link/wasm/archive/build.zig+13-4
......@@ -1,22 +1,31 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");
5 test_step.dependOn(b.getInstallStep());
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
614
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
716 // The code in question will pull-in compiler-rt,
817 // and therefore link with its archive file.
918 const lib = b.addSharedLibrary(.{
1019 .name = "main",
1120 .root_source_file = .{ .path = "main.zig" },
12 .optimize = b.standardOptimizeOption(.{}),
21 .optimize = optimize,
1322 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
1423 });
1524 lib.use_llvm = false;
1625 lib.use_lld = false;
1726 lib.strip = false;
1827
19 const check = lib.checkObject(.wasm);
28 const check = lib.checkObject();
2029 check.checkStart("Section custom");
2130 check.checkNext("name __truncsfhf2"); // Ensure it was imported and resolved
2231
test/link/wasm/basic-features/build.zig+5-2
......@@ -1,11 +1,13 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
46 // Library with explicitly set cpu features
57 const lib = b.addSharedLibrary(.{
68 .name = "lib",
79 .root_source_file = .{ .path = "main.zig" },
8 .optimize = b.standardOptimizeOption(.{}),
10 .optimize = .Debug,
911 .target = .{
1012 .cpu_arch = .wasm32,
1113 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
......@@ -17,11 +19,12 @@ pub fn build(b: *std.Build) void {
1719 lib.use_lld = false;
1820
1921 // Verify the result contains the features explicitly set on the target for the library.
20 const check = lib.checkObject(.wasm);
22 const check = lib.checkObject();
2123 check.checkStart("name target_features");
2224 check.checkNext("features 1");
2325 check.checkNext("+ atomics");
2426
2527 const test_step = b.step("test", "Run linker test");
2628 test_step.dependOn(&check.step);
29 b.default_step = test_step;
2730}
test/link/wasm/bss/build.zig+6-3
......@@ -1,14 +1,16 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
46 const test_step = b.step("test", "Test");
5 test_step.dependOn(b.getInstallStep());
7 b.default_step = test_step;
68
79 const lib = b.addSharedLibrary(.{
810 .name = "lib",
911 .root_source_file = .{ .path = "lib.zig" },
1012 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),
13 .optimize = .Debug,
1214 });
1315 lib.use_llvm = false;
1416 lib.use_lld = false;
......@@ -17,7 +19,7 @@ pub fn build(b: *std.Build) void {
1719 lib.import_memory = true;
1820 lib.install();
1921
20 const check_lib = lib.checkObject(.wasm);
22 const check_lib = lib.checkObject();
2123
2224 // since we import memory, make sure it exists with the correct naming
2325 check_lib.checkStart("Section import");
......@@ -36,5 +38,6 @@ pub fn build(b: *std.Build) void {
3638 check_lib.checkNext("name .rodata");
3739 check_lib.checkNext("index 1"); // bss section always last
3840 check_lib.checkNext("name .bss");
41
3942 test_step.dependOn(&check_lib.step);
4043}
test/link/wasm/export-data/build.zig+7-2
......@@ -2,7 +2,12 @@ const std = @import("std");
22
33pub fn build(b: *std.Build) void {
44 const test_step = b.step("test", "Test");
5 test_step.dependOn(b.getInstallStep());
5 b.default_step = test_step;
6
7 if (@import("builtin").os.tag == .windows) {
8 // TODO: Fix open handle in wasm-linker refraining rename from working on Windows.
9 return;
10 }
611
712 const lib = b.addSharedLibrary(.{
813 .name = "lib",
......@@ -14,7 +19,7 @@ pub fn build(b: *std.Build) void {
1419 lib.export_symbol_names = &.{ "foo", "bar" };
1520 lib.global_base = 0; // put data section at address 0 to make data symbols easier to parse
1621
17 const check_lib = lib.checkObject(.wasm);
22 const check_lib = lib.checkObject();
1823
1924 check_lib.checkStart("Section global");
2025 check_lib.checkNext("entries 3");
test/link/wasm/export/build.zig+14-5
......@@ -1,8 +1,18 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
514
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
616 const no_export = b.addSharedLibrary(.{
717 .name = "no-export",
818 .root_source_file = .{ .path = "main.zig" },
......@@ -32,25 +42,24 @@ pub fn build(b: *std.Build) void {
3242 force_export.use_llvm = false;
3343 force_export.use_lld = false;
3444
35 const check_no_export = no_export.checkObject(.wasm);
45 const check_no_export = no_export.checkObject();
3646 check_no_export.checkStart("Section export");
3747 check_no_export.checkNext("entries 1");
3848 check_no_export.checkNext("name memory");
3949 check_no_export.checkNext("kind memory");
4050
41 const check_dynamic_export = dynamic_export.checkObject(.wasm);
51 const check_dynamic_export = dynamic_export.checkObject();
4252 check_dynamic_export.checkStart("Section export");
4353 check_dynamic_export.checkNext("entries 2");
4454 check_dynamic_export.checkNext("name foo");
4555 check_dynamic_export.checkNext("kind function");
4656
47 const check_force_export = force_export.checkObject(.wasm);
57 const check_force_export = force_export.checkObject();
4858 check_force_export.checkStart("Section export");
4959 check_force_export.checkNext("entries 2");
5060 check_force_export.checkNext("name foo");
5161 check_force_export.checkNext("kind function");
5262
53 const test_step = b.step("test", "Run linker test");
5463 test_step.dependOn(&check_no_export.step);
5564 test_step.dependOn(&check_dynamic_export.step);
5665 test_step.dependOn(&check_force_export.step);
test/link/wasm/extern-mangle/build.zig+11-5
......@@ -1,20 +1,26 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");
5 test_step.dependOn(b.getInstallStep());
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
66
7 add(b, test_step, .Debug);
8 add(b, test_step, .ReleaseFast);
9 add(b, test_step, .ReleaseSmall);
10 add(b, test_step, .ReleaseSafe);
11}
12
13fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
714 const lib = b.addSharedLibrary(.{
815 .name = "lib",
916 .root_source_file = .{ .path = "lib.zig" },
1017 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),
18 .optimize = optimize,
1219 });
1320 lib.import_symbols = true; // import `a` and `b`
1421 lib.rdynamic = true; // export `foo`
15 lib.install();
1622
17 const check_lib = lib.checkObject(.wasm);
23 const check_lib = lib.checkObject();
1824 check_lib.checkStart("Section import");
1925 check_lib.checkNext("entries 2"); // a.hello & b.hello
2026 check_lib.checkNext("module a");
test/link/wasm/extern/build.zig+15-3
......@@ -1,19 +1,31 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
416 const exe = b.addExecutable(.{
517 .name = "extern",
618 .root_source_file = .{ .path = "main.zig" },
7 .optimize = b.standardOptimizeOption(.{}),
19 .optimize = optimize,
820 .target = .{ .cpu_arch = .wasm32, .os_tag = .wasi },
921 });
1022 exe.addCSourceFile("foo.c", &.{});
1123 exe.use_llvm = false;
1224 exe.use_lld = false;
1325
14 const run = exe.runEmulatable();
26 const run = b.addRunArtifact(exe);
27 run.skip_foreign_checks = true;
1528 run.expectStdOutEqual("Result: 30");
1629
17 const test_step = b.step("test", "Run linker test");
1830 test_step.dependOn(&run.step);
1931}
test/link/wasm/function-table/build.zig+16-9
......@@ -1,13 +1,20 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
58
6 const test_step = b.step("test", "Test");
7 test_step.dependOn(b.getInstallStep());
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
814
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
916 const import_table = b.addSharedLibrary(.{
10 .name = "lib",
17 .name = "import_table",
1118 .root_source_file = .{ .path = "lib.zig" },
1219 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
1320 .optimize = optimize,
......@@ -17,7 +24,7 @@ pub fn build(b: *std.Build) void {
1724 import_table.import_table = true;
1825
1926 const export_table = b.addSharedLibrary(.{
20 .name = "lib",
27 .name = "export_table",
2128 .root_source_file = .{ .path = "lib.zig" },
2229 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
2330 .optimize = optimize,
......@@ -27,7 +34,7 @@ pub fn build(b: *std.Build) void {
2734 export_table.export_table = true;
2835
2936 const regular_table = b.addSharedLibrary(.{
30 .name = "lib",
37 .name = "regular_table",
3138 .root_source_file = .{ .path = "lib.zig" },
3239 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
3340 .optimize = optimize,
......@@ -35,9 +42,9 @@ pub fn build(b: *std.Build) void {
3542 regular_table.use_llvm = false;
3643 regular_table.use_lld = false;
3744
38 const check_import = import_table.checkObject(.wasm);
39 const check_export = export_table.checkObject(.wasm);
40 const check_regular = regular_table.checkObject(.wasm);
45 const check_import = import_table.checkObject();
46 const check_export = export_table.checkObject();
47 const check_regular = regular_table.checkObject();
4148
4249 check_import.checkStart("Section import");
4350 check_import.checkNext("entries 1");
test/link/wasm/infer-features/build.zig+6-5
......@@ -1,12 +1,12 @@
11const std = @import("std");
22
3pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
3pub const requires_stage2 = true;
54
5pub fn build(b: *std.Build) void {
66 // Wasm Object file which we will use to infer the features from
77 const c_obj = b.addObject(.{
88 .name = "c_obj",
9 .optimize = optimize,
9 .optimize = .Debug,
1010 .target = .{
1111 .cpu_arch = .wasm32,
1212 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.bleeding_edge },
......@@ -20,7 +20,7 @@ pub fn build(b: *std.Build) void {
2020 const lib = b.addSharedLibrary(.{
2121 .name = "lib",
2222 .root_source_file = .{ .path = "main.zig" },
23 .optimize = optimize,
23 .optimize = .Debug,
2424 .target = .{
2525 .cpu_arch = .wasm32,
2626 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
......@@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void {
3232 lib.addObject(c_obj);
3333
3434 // Verify the result contains the features from the C Object file.
35 const check = lib.checkObject(.wasm);
35 const check = lib.checkObject();
3636 check.checkStart("name target_features");
3737 check.checkNext("features 7");
3838 check.checkNext("+ atomics");
......@@ -45,4 +45,5 @@ pub fn build(b: *std.Build) void {
4545
4646 const test_step = b.step("test", "Run linker test");
4747 test_step.dependOn(&check.step);
48 b.default_step = test_step;
4849}
test/link/wasm/producers/build.zig+14-7
......@@ -1,26 +1,33 @@
11const std = @import("std");
22const builtin = @import("builtin");
33
4pub const requires_stage2 = true;
5
46pub fn build(b: *std.Build) void {
5 const test_step = b.step("test", "Test");
6 test_step.dependOn(b.getInstallStep());
7 const test_step = b.step("test", "Test it");
8 b.default_step = test_step;
9
10 add(b, test_step, .Debug);
11 add(b, test_step, .ReleaseFast);
12 add(b, test_step, .ReleaseSmall);
13 add(b, test_step, .ReleaseSafe);
14}
715
16fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
817 const lib = b.addSharedLibrary(.{
918 .name = "lib",
1019 .root_source_file = .{ .path = "lib.zig" },
1120 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
12 .optimize = b.standardOptimizeOption(.{}),
21 .optimize = optimize,
1322 });
1423 lib.use_llvm = false;
1524 lib.use_lld = false;
1625 lib.strip = false;
1726 lib.install();
1827
19 const zig_version = builtin.zig_version;
20 var version_buf: [100]u8 = undefined;
21 const version_fmt = std.fmt.bufPrint(&version_buf, "version {}", .{zig_version}) catch unreachable;
28 const version_fmt = "version " ++ builtin.zig_version_string;
2229
23 const check_lib = lib.checkObject(.wasm);
30 const check_lib = lib.checkObject();
2431 check_lib.checkStart("name producers");
2532 check_lib.checkNext("fields 2");
2633 check_lib.checkNext("field_name language");
test/link/wasm/segments/build.zig+13-4
......@@ -1,21 +1,30 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");
5 test_step.dependOn(b.getInstallStep());
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
614
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
716 const lib = b.addSharedLibrary(.{
817 .name = "lib",
918 .root_source_file = .{ .path = "lib.zig" },
1019 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),
20 .optimize = optimize,
1221 });
1322 lib.use_llvm = false;
1423 lib.use_lld = false;
1524 lib.strip = false;
1625 lib.install();
1726
18 const check_lib = lib.checkObject(.wasm);
27 const check_lib = lib.checkObject();
1928 check_lib.checkStart("Section data");
2029 check_lib.checkNext("entries 2"); // rodata & data, no bss because we're exporting memory
2130
test/link/wasm/stack_pointer/build.zig+13-4
......@@ -1,14 +1,23 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");
5 test_step.dependOn(b.getInstallStep());
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
614
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
716 const lib = b.addSharedLibrary(.{
817 .name = "lib",
918 .root_source_file = .{ .path = "lib.zig" },
1019 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),
20 .optimize = optimize,
1221 });
1322 lib.use_llvm = false;
1423 lib.use_lld = false;
......@@ -16,7 +25,7 @@ pub fn build(b: *std.Build) void {
1625 lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size
1726 lib.install();
1827
19 const check_lib = lib.checkObject(.wasm);
28 const check_lib = lib.checkObject();
2029
2130 // ensure global exists and its initial value is equal to explitic stack size
2231 check_lib.checkStart("Section global");
test/link/wasm/type/build.zig+13-4
......@@ -1,21 +1,30 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");
5 test_step.dependOn(b.getInstallStep());
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
614
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
716 const lib = b.addSharedLibrary(.{
817 .name = "lib",
918 .root_source_file = .{ .path = "lib.zig" },
1019 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),
20 .optimize = optimize,
1221 });
1322 lib.use_llvm = false;
1423 lib.use_lld = false;
1524 lib.strip = false;
1625 lib.install();
1726
18 const check_lib = lib.checkObject(.wasm);
27 const check_lib = lib.checkObject();
1928 check_lib.checkStart("Section type");
2029 // only 2 entries, although we have 3 functions.
2130 // This is to test functions with the same function signature
test/nvptx.zig created+106
......@@ -0,0 +1,106 @@
1const std = @import("std");
2const Cases = @import("src/Cases.zig");
3
4pub fn addCases(ctx: *Cases) !void {
5 {
6 var case = addPtx(ctx, "simple addition and subtraction");
7
8 case.addCompile(
9 \\fn add(a: i32, b: i32) i32 {
10 \\ return a + b;
11 \\}
12 \\
13 \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void {
14 \\ const x = add(a, 7);
15 \\ var y = add(2, 0);
16 \\ y -= x;
17 \\ out.* = y;
18 \\}
19 );
20 }
21
22 {
23 var case = addPtx(ctx, "read special registers");
24
25 case.addCompile(
26 \\fn threadIdX() u32 {
27 \\ return asm ("mov.u32 \t%[r], %tid.x;"
28 \\ : [r] "=r" (-> u32),
29 \\ );
30 \\}
31 \\
32 \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void {
33 \\ const i = threadIdX();
34 \\ out[i] = a[i] + 7;
35 \\}
36 );
37 }
38
39 {
40 var case = addPtx(ctx, "address spaces");
41
42 case.addCompile(
43 \\var x: i32 addrspace(.global) = 0;
44 \\
45 \\pub export fn increment(out: *i32) callconv(.PtxKernel) void {
46 \\ x += 1;
47 \\ out.* = x;
48 \\}
49 );
50 }
51
52 {
53 var case = addPtx(ctx, "reduce in shared mem");
54 case.addCompile(
55 \\fn threadIdX() u32 {
56 \\ return asm ("mov.u32 \t%[r], %tid.x;"
57 \\ : [r] "=r" (-> u32),
58 \\ );
59 \\}
60 \\
61 \\ var _sdata: [1024]f32 addrspace(.shared) = undefined;
62 \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void {
63 \\ var sdata = @addrSpaceCast(.generic, &_sdata);
64 \\ const tid: u32 = threadIdX();
65 \\ var sum = d_x[tid];
66 \\ sdata[tid] = sum;
67 \\ asm volatile ("bar.sync \t0;");
68 \\ var s: u32 = 512;
69 \\ while (s > 0) : (s = s >> 1) {
70 \\ if (tid < s) {
71 \\ sum += sdata[tid + s];
72 \\ sdata[tid] = sum;
73 \\ }
74 \\ asm volatile ("bar.sync \t0;");
75 \\ }
76 \\
77 \\ if (tid == 0) {
78 \\ out.* = sum;
79 \\ }
80 \\ }
81 );
82 }
83}
84
85const nvptx_target = std.zig.CrossTarget{
86 .cpu_arch = .nvptx64,
87 .os_tag = .cuda,
88};
89
90pub fn addPtx(
91 ctx: *Cases,
92 name: []const u8,
93) *Cases.Case {
94 ctx.cases.append(.{
95 .name = name,
96 .target = nvptx_target,
97 .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator),
98 .output_mode = .Obj,
99 .deps = std.ArrayList(Cases.DepModule).init(ctx.cases.allocator),
100 .link_libc = false,
101 .backend = .llvm,
102 // Bug in Debug mode
103 .optimize_mode = .ReleaseSafe,
104 }) catch @panic("out of memory");
105 return &ctx.cases.items[ctx.cases.items.len - 1];
106}
test/src/Cases.zig created+1587
......@@ -0,0 +1,1587 @@
1gpa: Allocator,
2arena: Allocator,
3cases: std.ArrayList(Case),
4incremental_cases: std.ArrayList(IncrementalCase),
5
6pub const IncrementalCase = struct {
7 base_path: []const u8,
8};
9
10pub const Update = struct {
11 /// The input to the current update. We simulate an incremental update
12 /// with the file's contents changed to this value each update.
13 ///
14 /// This value can change entirely between updates, which would be akin
15 /// to deleting the source file and creating a new one from scratch; or
16 /// you can keep it mostly consistent, with small changes, testing the
17 /// effects of the incremental compilation.
18 files: std.ArrayList(File),
19 /// This is a description of what happens with the update, for debugging
20 /// purposes.
21 name: []const u8,
22 case: union(enum) {
23 /// Check that it compiles with no errors.
24 Compile: void,
25 /// Check the main binary output file against an expected set of bytes.
26 /// This is most useful with, for example, `-ofmt=c`.
27 CompareObjectFile: []const u8,
28 /// An error update attempts to compile bad code, and ensures that it
29 /// fails to compile, and for the expected reasons.
30 /// A slice containing the expected stderr template, which
31 /// gets some values substituted.
32 Error: []const []const u8,
33 /// An execution update compiles and runs the input, testing the
34 /// stdout against the expected results
35 /// This is a slice containing the expected message.
36 Execution: []const u8,
37 /// A header update compiles the input with the equivalent of
38 /// `-femit-h` and tests the produced header against the
39 /// expected result
40 Header: []const u8,
41 },
42
43 pub fn addSourceFile(update: *Update, name: []const u8, src: [:0]const u8) void {
44 update.files.append(.{ .path = name, .src = src }) catch @panic("out of memory");
45 }
46};
47
48pub const File = struct {
49 src: [:0]const u8,
50 path: []const u8,
51};
52
53pub const DepModule = struct {
54 name: []const u8,
55 path: []const u8,
56};
57
58pub const Backend = enum {
59 stage1,
60 stage2,
61 llvm,
62};
63
64/// A `Case` consists of a list of `Update`. The same `Compilation` is used for each
65/// update, so each update's source is treated as a single file being
66/// updated by the test harness and incrementally compiled.
67pub const Case = struct {
68 /// The name of the test case. This is shown if a test fails, and
69 /// otherwise ignored.
70 name: []const u8,
71 /// The platform the test targets. For non-native platforms, an emulator
72 /// such as QEMU is required for tests to complete.
73 target: CrossTarget,
74 /// In order to be able to run e.g. Execution updates, this must be set
75 /// to Executable.
76 output_mode: std.builtin.OutputMode,
77 optimize_mode: std.builtin.Mode = .Debug,
78 updates: std.ArrayList(Update),
79 emit_h: bool = false,
80 is_test: bool = false,
81 expect_exact: bool = false,
82 backend: Backend = .stage2,
83 link_libc: bool = false,
84
85 deps: std.ArrayList(DepModule),
86
87 pub fn addSourceFile(case: *Case, name: []const u8, src: [:0]const u8) void {
88 const update = &case.updates.items[case.updates.items.len - 1];
89 update.files.append(.{ .path = name, .src = src }) catch @panic("OOM");
90 }
91
92 pub fn addDepModule(case: *Case, name: []const u8, path: []const u8) void {
93 case.deps.append(.{
94 .name = name,
95 .path = path,
96 }) catch @panic("out of memory");
97 }
98
99 /// Adds a subcase in which the module is updated with `src`, compiled,
100 /// run, and the output is tested against `result`.
101 pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {
102 self.updates.append(.{
103 .files = std.ArrayList(File).init(self.updates.allocator),
104 .name = "update",
105 .case = .{ .Execution = result },
106 }) catch @panic("out of memory");
107 addSourceFile(self, "tmp.zig", src);
108 }
109
110 pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void {
111 return self.addErrorNamed("update", src, errors);
112 }
113
114 /// Adds a subcase in which the module is updated with `src`, which
115 /// should contain invalid input, and ensures that compilation fails
116 /// for the expected reasons, given in sequential order in `errors` in
117 /// the form `:line:column: error: message`.
118 pub fn addErrorNamed(
119 self: *Case,
120 name: []const u8,
121 src: [:0]const u8,
122 errors: []const []const u8,
123 ) void {
124 assert(errors.len != 0);
125 self.updates.append(.{
126 .files = std.ArrayList(File).init(self.updates.allocator),
127 .name = name,
128 .case = .{ .Error = errors },
129 }) catch @panic("out of memory");
130 addSourceFile(self, "tmp.zig", src);
131 }
132
133 /// Adds a subcase in which the module is updated with `src`, and
134 /// asserts that it compiles without issue
135 pub fn addCompile(self: *Case, src: [:0]const u8) void {
136 self.updates.append(.{
137 .files = std.ArrayList(File).init(self.updates.allocator),
138 .name = "compile",
139 .case = .{ .Compile = {} },
140 }) catch @panic("out of memory");
141 addSourceFile(self, "tmp.zig", src);
142 }
143};
144
145pub fn addExe(
146 ctx: *Cases,
147 name: []const u8,
148 target: CrossTarget,
149) *Case {
150 ctx.cases.append(Case{
151 .name = name,
152 .target = target,
153 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
154 .output_mode = .Exe,
155 .deps = std.ArrayList(DepModule).init(ctx.arena),
156 }) catch @panic("out of memory");
157 return &ctx.cases.items[ctx.cases.items.len - 1];
158}
159
160/// Adds a test case for Zig input, producing an executable
161pub fn exe(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
162 return ctx.addExe(name, target);
163}
164
165pub fn exeFromCompiledC(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
166 var target_adjusted = target;
167 target_adjusted.ofmt = .c;
168 ctx.cases.append(Case{
169 .name = name,
170 .target = target_adjusted,
171 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
172 .output_mode = .Exe,
173 .deps = std.ArrayList(DepModule).init(ctx.arena),
174 .link_libc = true,
175 }) catch @panic("out of memory");
176 return &ctx.cases.items[ctx.cases.items.len - 1];
177}
178
179/// Adds a test case that uses the LLVM backend to emit an executable.
180/// Currently this implies linking libc, because only then we can generate a testable executable.
181pub fn exeUsingLlvmBackend(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
182 ctx.cases.append(Case{
183 .name = name,
184 .target = target,
185 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
186 .output_mode = .Exe,
187 .deps = std.ArrayList(DepModule).init(ctx.arena),
188 .backend = .llvm,
189 .link_libc = true,
190 }) catch @panic("out of memory");
191 return &ctx.cases.items[ctx.cases.items.len - 1];
192}
193
194pub fn addObj(
195 ctx: *Cases,
196 name: []const u8,
197 target: CrossTarget,
198) *Case {
199 ctx.cases.append(Case{
200 .name = name,
201 .target = target,
202 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
203 .output_mode = .Obj,
204 .deps = std.ArrayList(DepModule).init(ctx.arena),
205 }) catch @panic("out of memory");
206 return &ctx.cases.items[ctx.cases.items.len - 1];
207}
208
209pub fn addTest(
210 ctx: *Cases,
211 name: []const u8,
212 target: CrossTarget,
213) *Case {
214 ctx.cases.append(Case{
215 .name = name,
216 .target = target,
217 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
218 .output_mode = .Exe,
219 .is_test = true,
220 .deps = std.ArrayList(DepModule).init(ctx.arena),
221 }) catch @panic("out of memory");
222 return &ctx.cases.items[ctx.cases.items.len - 1];
223}
224
225/// Adds a test case for Zig input, producing an object file.
226pub fn obj(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
227 return ctx.addObj(name, target);
228}
229
230/// Adds a test case for ZIR input, producing an object file.
231pub fn objZIR(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
232 return ctx.addObj(name, target, .ZIR);
233}
234
235/// Adds a test case for Zig or ZIR input, producing C code.
236pub fn addC(ctx: *Cases, name: []const u8, target: CrossTarget) *Case {
237 var target_adjusted = target;
238 target_adjusted.ofmt = std.Target.ObjectFormat.c;
239 ctx.cases.append(Case{
240 .name = name,
241 .target = target_adjusted,
242 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
243 .output_mode = .Obj,
244 .deps = std.ArrayList(DepModule).init(ctx.arena),
245 }) catch @panic("out of memory");
246 return &ctx.cases.items[ctx.cases.items.len - 1];
247}
248
249pub fn addCompareOutput(
250 ctx: *Cases,
251 name: []const u8,
252 src: [:0]const u8,
253 expected_stdout: []const u8,
254) void {
255 ctx.addExe(name, .{}).addCompareOutput(src, expected_stdout);
256}
257
258/// Adds a test case that compiles the Zig source given in `src`, executes
259/// it, runs it, and tests the output against `expected_stdout`
260pub fn compareOutput(
261 ctx: *Cases,
262 name: []const u8,
263 src: [:0]const u8,
264 expected_stdout: []const u8,
265) void {
266 return ctx.addCompareOutput(name, src, expected_stdout);
267}
268
269pub fn addTransform(
270 ctx: *Cases,
271 name: []const u8,
272 target: CrossTarget,
273 src: [:0]const u8,
274 result: [:0]const u8,
275) void {
276 ctx.addObj(name, target).addTransform(src, result);
277}
278
279/// Adds a test case that compiles the Zig given in `src` to ZIR and tests
280/// the ZIR against `result`
281pub fn transform(
282 ctx: *Cases,
283 name: []const u8,
284 target: CrossTarget,
285 src: [:0]const u8,
286 result: [:0]const u8,
287) void {
288 ctx.addTransform(name, target, src, result);
289}
290
291pub fn addError(
292 ctx: *Cases,
293 name: []const u8,
294 target: CrossTarget,
295 src: [:0]const u8,
296 expected_errors: []const []const u8,
297) void {
298 ctx.addObj(name, target).addError(src, expected_errors);
299}
300
301/// Adds a test case that ensures that the Zig given in `src` fails to
302/// compile for the expected reasons, given in sequential order in
303/// `expected_errors` in the form `:line:column: error: message`.
304pub fn compileError(
305 ctx: *Cases,
306 name: []const u8,
307 target: CrossTarget,
308 src: [:0]const u8,
309 expected_errors: []const []const u8,
310) void {
311 ctx.addError(name, target, src, expected_errors);
312}
313
314/// Adds a test case that asserts that the Zig given in `src` compiles
315/// without any errors.
316pub fn addCompile(
317 ctx: *Cases,
318 name: []const u8,
319 target: CrossTarget,
320 src: [:0]const u8,
321) void {
322 ctx.addObj(name, target).addCompile(src);
323}
324
325/// Adds a test for each file in the provided directory.
326/// Testing strategy (TestStrategy) is inferred automatically from filenames.
327/// Recurses nested directories.
328///
329/// Each file should include a test manifest as a contiguous block of comments at
330/// the end of the file. The first line should be the test type, followed by a set of
331/// key-value config values, followed by a blank line, then the expected output.
332pub fn addFromDir(ctx: *Cases, dir: std.fs.IterableDir) void {
333 var current_file: []const u8 = "none";
334 ctx.addFromDirInner(dir, &current_file) catch |err| {
335 std.debug.panic("test harness failed to process file '{s}': {s}\n", .{
336 current_file, @errorName(err),
337 });
338 };
339}
340
341fn addFromDirInner(
342 ctx: *Cases,
343 iterable_dir: std.fs.IterableDir,
344 /// This is kept up to date with the currently being processed file so
345 /// that if any errors occur the caller knows it happened during this file.
346 current_file: *[]const u8,
347) !void {
348 var it = try iterable_dir.walk(ctx.arena);
349 var filenames = std.ArrayList([]const u8).init(ctx.arena);
350
351 while (try it.next()) |entry| {
352 if (entry.kind != .File) continue;
353
354 // Ignore stuff such as .swp files
355 switch (Compilation.classifyFileExt(entry.basename)) {
356 .unknown => continue,
357 else => {},
358 }
359 try filenames.append(try ctx.arena.dupe(u8, entry.path));
360 }
361
362 // Sort filenames, so that incremental tests are contiguous and in-order
363 sortTestFilenames(filenames.items);
364
365 var test_it = TestIterator{ .filenames = filenames.items };
366 while (test_it.next()) |maybe_batch| {
367 const batch = maybe_batch orelse break;
368 const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent;
369 const filename = batch[0];
370 current_file.* = filename;
371 if (strategy == .incremental) {
372 try ctx.incremental_cases.append(.{ .base_path = filename });
373 continue;
374 }
375
376 const max_file_size = 10 * 1024 * 1024;
377 const src = try iterable_dir.dir.readFileAllocOptions(ctx.arena, filename, max_file_size, null, 1, 0);
378
379 // Parse the manifest
380 var manifest = try TestManifest.parse(ctx.arena, src);
381
382 const backends = try manifest.getConfigForKeyAlloc(ctx.arena, "backend", Backend);
383 const targets = try manifest.getConfigForKeyAlloc(ctx.arena, "target", CrossTarget);
384 const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool);
385 const link_libc = try manifest.getConfigForKeyAssertSingle("link_libc", bool);
386 const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode);
387
388 var cases = std.ArrayList(usize).init(ctx.arena);
389
390 // Cross-product to get all possible test combinations
391 for (backends) |backend| {
392 for (targets) |target| {
393 const next = ctx.cases.items.len;
394 try ctx.cases.append(.{
395 .name = std.fs.path.stem(filename),
396 .target = target,
397 .backend = backend,
398 .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator),
399 .is_test = is_test,
400 .output_mode = output_mode,
401 .link_libc = link_libc,
402 .deps = std.ArrayList(DepModule).init(ctx.cases.allocator),
403 });
404 try cases.append(next);
405 }
406 }
407
408 for (cases.items) |case_index| {
409 const case = &ctx.cases.items[case_index];
410 switch (manifest.type) {
411 .compile => {
412 case.addCompile(src);
413 },
414 .@"error" => {
415 const errors = try manifest.trailingAlloc(ctx.arena);
416 case.addError(src, errors);
417 },
418 .run => {
419 var output = std.ArrayList(u8).init(ctx.arena);
420 var trailing_it = manifest.trailing();
421 while (trailing_it.next()) |line| {
422 try output.appendSlice(line);
423 try output.append('\n');
424 }
425 if (output.items.len > 0) {
426 try output.resize(output.items.len - 1);
427 }
428 case.addCompareOutput(src, try output.toOwnedSlice());
429 },
430 .cli => @panic("TODO cli tests"),
431 }
432 }
433 } else |err| {
434 // make sure the current file is set to the file that produced an error
435 current_file.* = test_it.currentFilename();
436 return err;
437 }
438}
439
440pub fn init(gpa: Allocator, arena: Allocator) Cases {
441 return .{
442 .gpa = gpa,
443 .cases = std.ArrayList(Case).init(gpa),
444 .incremental_cases = std.ArrayList(IncrementalCase).init(gpa),
445 .arena = arena,
446 };
447}
448
449pub fn lowerToBuildSteps(
450 self: *Cases,
451 b: *std.Build,
452 parent_step: *std.Build.Step,
453 opt_test_filter: ?[]const u8,
454 cases_dir_path: []const u8,
455 incremental_exe: *std.Build.CompileStep,
456) void {
457 for (self.incremental_cases.items) |incr_case| {
458 if (opt_test_filter) |test_filter| {
459 if (std.mem.indexOf(u8, incr_case.base_path, test_filter) == null) continue;
460 }
461 const case_base_path_with_dir = std.fs.path.join(b.allocator, &.{
462 cases_dir_path, incr_case.base_path,
463 }) catch @panic("OOM");
464 const run = b.addRunArtifact(incremental_exe);
465 run.setName(incr_case.base_path);
466 run.addArgs(&.{
467 case_base_path_with_dir,
468 b.zig_exe,
469 });
470 run.expectStdOutEqual("");
471 parent_step.dependOn(&run.step);
472 }
473
474 for (self.cases.items) |case| {
475 if (case.updates.items.len != 1) continue; // handled with incremental_cases above
476 assert(case.updates.items.len == 1);
477 const update = case.updates.items[0];
478
479 if (opt_test_filter) |test_filter| {
480 if (std.mem.indexOf(u8, case.name, test_filter) == null) continue;
481 }
482
483 const writefiles = b.addWriteFiles();
484 for (update.files.items) |file| {
485 writefiles.add(file.path, file.src);
486 }
487 const root_source_file = writefiles.getFileSource(update.files.items[0].path).?;
488
489 const artifact = if (case.is_test) b.addTest(.{
490 .root_source_file = root_source_file,
491 .name = case.name,
492 .target = case.target,
493 .optimize = case.optimize_mode,
494 }) else switch (case.output_mode) {
495 .Obj => b.addObject(.{
496 .root_source_file = root_source_file,
497 .name = case.name,
498 .target = case.target,
499 .optimize = case.optimize_mode,
500 }),
501 .Lib => b.addStaticLibrary(.{
502 .root_source_file = root_source_file,
503 .name = case.name,
504 .target = case.target,
505 .optimize = case.optimize_mode,
506 }),
507 .Exe => b.addExecutable(.{
508 .root_source_file = root_source_file,
509 .name = case.name,
510 .target = case.target,
511 .optimize = case.optimize_mode,
512 }),
513 };
514
515 if (case.link_libc) artifact.linkLibC();
516
517 switch (case.backend) {
518 .stage1 => continue,
519 .stage2 => {
520 artifact.use_llvm = false;
521 artifact.use_lld = false;
522 },
523 .llvm => {
524 artifact.use_llvm = true;
525 },
526 }
527
528 for (case.deps.items) |dep| {
529 artifact.addAnonymousModule(dep.name, .{
530 .source_file = writefiles.getFileSource(dep.path).?,
531 });
532 }
533
534 switch (update.case) {
535 .Compile => {
536 parent_step.dependOn(&artifact.step);
537 },
538 .CompareObjectFile => |expected_output| {
539 const check = b.addCheckFile(artifact.getOutputSource(), .{
540 .expected_exact = expected_output,
541 });
542
543 parent_step.dependOn(&check.step);
544 },
545 .Error => |expected_msgs| {
546 assert(expected_msgs.len != 0);
547 artifact.expect_errors = expected_msgs;
548 parent_step.dependOn(&artifact.step);
549 },
550 .Execution => |expected_stdout| {
551 const run = b.addRunArtifact(artifact);
552 run.skip_foreign_checks = true;
553 if (!case.is_test) {
554 run.expectStdOutEqual(expected_stdout);
555 }
556 parent_step.dependOn(&run.step);
557 },
558 .Header => @panic("TODO"),
559 }
560 }
561}
562
563/// Sort test filenames in-place, so that incremental test cases ("foo.0.zig",
564/// "foo.1.zig", etc.) are contiguous and appear in numerical order.
565fn sortTestFilenames(filenames: [][]const u8) void {
566 const Context = struct {
567 pub fn lessThan(_: @This(), a: []const u8, b: []const u8) bool {
568 const a_parts = getTestFileNameParts(a);
569 const b_parts = getTestFileNameParts(b);
570
571 // Sort "<base_name>.X.<file_ext>" based on "<base_name>" and "<file_ext>" first
572 return switch (std.mem.order(u8, a_parts.base_name, b_parts.base_name)) {
573 .lt => true,
574 .gt => false,
575 .eq => switch (std.mem.order(u8, a_parts.file_ext, b_parts.file_ext)) {
576 .lt => true,
577 .gt => false,
578 .eq => {
579 // a and b differ only in their ".X" part
580
581 // Sort "<base_name>.<file_ext>" before any "<base_name>.X.<file_ext>"
582 if (a_parts.test_index) |a_index| {
583 if (b_parts.test_index) |b_index| {
584 // Make sure that incremental tests appear in linear order
585 return a_index < b_index;
586 } else {
587 return false;
588 }
589 } else {
590 return b_parts.test_index != null;
591 }
592 },
593 },
594 };
595 }
596 };
597 std.sort.sort([]const u8, filenames, Context{}, Context.lessThan);
598}
599
600/// Iterates a set of filenames extracting batches that are either incremental
601/// ("foo.0.zig", "foo.1.zig", etc.) or independent ("foo.zig", "bar.zig", etc.).
602/// Assumes filenames are sorted.
603const TestIterator = struct {
604 start: usize = 0,
605 end: usize = 0,
606 filenames: []const []const u8,
607 /// reset on each call to `next`
608 index: usize = 0,
609
610 const Error = error{InvalidIncrementalTestIndex};
611
612 fn next(it: *TestIterator) Error!?[]const []const u8 {
613 try it.nextInner();
614 if (it.start == it.end) return null;
615 return it.filenames[it.start..it.end];
616 }
617
618 fn nextInner(it: *TestIterator) Error!void {
619 it.start = it.end;
620 if (it.end == it.filenames.len) return;
621 if (it.end + 1 == it.filenames.len) {
622 it.end += 1;
623 return;
624 }
625
626 const remaining = it.filenames[it.end..];
627 it.index = 0;
628 while (it.index < remaining.len - 1) : (it.index += 1) {
629 // First, check if this file is part of an incremental update sequence
630 // Split filename into "<base_name>.<index>.<file_ext>"
631 const prev_parts = getTestFileNameParts(remaining[it.index]);
632 const new_parts = getTestFileNameParts(remaining[it.index + 1]);
633
634 // If base_name and file_ext match, these files are in the same test sequence
635 // and the new one should be the incremented version of the previous test
636 if (std.mem.eql(u8, prev_parts.base_name, new_parts.base_name) and
637 std.mem.eql(u8, prev_parts.file_ext, new_parts.file_ext))
638 {
639 // This is "foo.X.zig" followed by "foo.Y.zig". Make sure that X = Y + 1
640 if (prev_parts.test_index == null)
641 return error.InvalidIncrementalTestIndex;
642 if (new_parts.test_index == null)
643 return error.InvalidIncrementalTestIndex;
644 if (new_parts.test_index.? != prev_parts.test_index.? + 1)
645 return error.InvalidIncrementalTestIndex;
646 } else {
647 // This is not the same test sequence, so the new file must be the first file
648 // in a new sequence ("*.0.zig") or an independent test file ("*.zig")
649 if (new_parts.test_index != null and new_parts.test_index.? != 0)
650 return error.InvalidIncrementalTestIndex;
651
652 it.end += it.index + 1;
653 break;
654 }
655 } else {
656 it.end += remaining.len;
657 }
658 }
659
660 /// In the event of an `error.InvalidIncrementalTestIndex`, this function can
661 /// be used to find the current filename that was being processed.
662 /// Asserts the iterator hasn't reached the end.
663 fn currentFilename(it: TestIterator) []const u8 {
664 assert(it.end != it.filenames.len);
665 const remaining = it.filenames[it.end..];
666 return remaining[it.index + 1];
667 }
668};
669
670/// For a filename in the format "<filename>.X.<ext>" or "<filename>.<ext>", returns
671/// "<filename>", "<ext>" and X parsed as a decimal number. If X is not present, or
672/// cannot be parsed as a decimal number, it is treated as part of <filename>
673fn getTestFileNameParts(name: []const u8) struct {
674 base_name: []const u8,
675 file_ext: []const u8,
676 test_index: ?usize,
677} {
678 const file_ext = std.fs.path.extension(name);
679 const trimmed = name[0 .. name.len - file_ext.len]; // Trim off ".<ext>"
680 const maybe_index = std.fs.path.extension(trimmed); // Extract ".X"
681
682 // Attempt to parse index
683 const index: ?usize = if (maybe_index.len > 0)
684 std.fmt.parseInt(usize, maybe_index[1..], 10) catch null
685 else
686 null;
687
688 // Adjust "<filename>" extent based on parsing success
689 const base_name_end = trimmed.len - if (index != null) maybe_index.len else 0;
690 return .{
691 .base_name = name[0..base_name_end],
692 .file_ext = if (file_ext.len > 0) file_ext[1..] else file_ext,
693 .test_index = index,
694 };
695}
696
697const TestStrategy = enum {
698 /// Execute tests as independent compilations, unless they are explicitly
699 /// incremental ("foo.0.zig", "foo.1.zig", etc.)
700 independent,
701 /// Execute all tests as incremental updates to a single compilation. Explicitly
702 /// incremental tests ("foo.0.zig", "foo.1.zig", etc.) still execute in order
703 incremental,
704};
705
706/// Default config values for known test manifest key-value pairings.
707/// Currently handled defaults are:
708/// * backend
709/// * target
710/// * output_mode
711/// * is_test
712const TestManifestConfigDefaults = struct {
713 /// Asserts if the key doesn't exist - yep, it's an oversight alright.
714 fn get(@"type": TestManifest.Type, key: []const u8) []const u8 {
715 if (std.mem.eql(u8, key, "backend")) {
716 return "stage2";
717 } else if (std.mem.eql(u8, key, "target")) {
718 if (@"type" == .@"error") {
719 return "native";
720 }
721 comptime {
722 var defaults: []const u8 = "";
723 // TODO should we only return "mainstream" targets by default here?
724 // TODO we should also specify ABIs explicitly as the backends are
725 // getting more and more complete
726 // Linux
727 inline for (&[_][]const u8{ "x86_64", "arm", "aarch64" }) |arch| {
728 defaults = defaults ++ arch ++ "-linux" ++ ",";
729 }
730 // macOS
731 inline for (&[_][]const u8{ "x86_64", "aarch64" }) |arch| {
732 defaults = defaults ++ arch ++ "-macos" ++ ",";
733 }
734 // Windows
735 defaults = defaults ++ "x86_64-windows" ++ ",";
736 // Wasm
737 defaults = defaults ++ "wasm32-wasi";
738 return defaults;
739 }
740 } else if (std.mem.eql(u8, key, "output_mode")) {
741 return switch (@"type") {
742 .@"error" => "Obj",
743 .run => "Exe",
744 .compile => "Obj",
745 .cli => @panic("TODO test harness for CLI tests"),
746 };
747 } else if (std.mem.eql(u8, key, "is_test")) {
748 return "0";
749 } else if (std.mem.eql(u8, key, "link_libc")) {
750 return "0";
751 } else unreachable;
752 }
753};
754
755/// Manifest syntax example:
756/// (see https://github.com/ziglang/zig/issues/11288)
757///
758/// error
759/// backend=stage1,stage2
760/// output_mode=exe
761///
762/// :3:19: error: foo
763///
764/// run
765/// target=x86_64-linux,aarch64-macos
766///
767/// I am expected stdout! Hello!
768///
769/// cli
770///
771/// build test
772const TestManifest = struct {
773 type: Type,
774 config_map: std.StringHashMap([]const u8),
775 trailing_bytes: []const u8 = "",
776
777 const Type = enum {
778 @"error",
779 run,
780 cli,
781 compile,
782 };
783
784 const TrailingIterator = struct {
785 inner: std.mem.TokenIterator(u8),
786
787 fn next(self: *TrailingIterator) ?[]const u8 {
788 const next_inner = self.inner.next() orelse return null;
789 return std.mem.trim(u8, next_inner[2..], " \t");
790 }
791 };
792
793 fn ConfigValueIterator(comptime T: type) type {
794 return struct {
795 inner: std.mem.SplitIterator(u8),
796
797 fn next(self: *@This()) !?T {
798 const next_raw = self.inner.next() orelse return null;
799 const parseFn = getDefaultParser(T);
800 return try parseFn(next_raw);
801 }
802 };
803 }
804
805 fn parse(arena: Allocator, bytes: []const u8) !TestManifest {
806 // The manifest is the last contiguous block of comments in the file
807 // We scan for the beginning by searching backward for the first non-empty line that does not start with "//"
808 var start: ?usize = null;
809 var end: usize = bytes.len;
810 if (bytes.len > 0) {
811 var cursor: usize = bytes.len - 1;
812 while (true) {
813 // Move to beginning of line
814 while (cursor > 0 and bytes[cursor - 1] != '\n') cursor -= 1;
815
816 if (std.mem.startsWith(u8, bytes[cursor..], "//")) {
817 start = cursor; // Contiguous comment line, include in manifest
818 } else {
819 if (start != null) break; // Encountered non-comment line, end of manifest
820
821 // We ignore all-whitespace lines following the comment block, but anything else
822 // means that there is no manifest present.
823 if (std.mem.trim(u8, bytes[cursor..end], " \r\n\t").len == 0) {
824 end = cursor;
825 } else break; // If it's not whitespace, there is no manifest
826 }
827
828 // Move to previous line
829 if (cursor != 0) cursor -= 1 else break;
830 }
831 }
832
833 const actual_start = start orelse return error.MissingTestManifest;
834 const manifest_bytes = bytes[actual_start..end];
835
836 var it = std.mem.tokenize(u8, manifest_bytes, "\r\n");
837
838 // First line is the test type
839 const tt: Type = blk: {
840 const line = it.next() orelse return error.MissingTestCaseType;
841 const raw = std.mem.trim(u8, line[2..], " \t");
842 if (std.mem.eql(u8, raw, "error")) {
843 break :blk .@"error";
844 } else if (std.mem.eql(u8, raw, "run")) {
845 break :blk .run;
846 } else if (std.mem.eql(u8, raw, "cli")) {
847 break :blk .cli;
848 } else if (std.mem.eql(u8, raw, "compile")) {
849 break :blk .compile;
850 } else {
851 std.log.warn("unknown test case type requested: {s}", .{raw});
852 return error.UnknownTestCaseType;
853 }
854 };
855
856 var manifest: TestManifest = .{
857 .type = tt,
858 .config_map = std.StringHashMap([]const u8).init(arena),
859 };
860
861 // Any subsequent line until a blank comment line is key=value(s) pair
862 while (it.next()) |line| {
863 const trimmed = std.mem.trim(u8, line[2..], " \t");
864 if (trimmed.len == 0) break;
865
866 // Parse key=value(s)
867 var kv_it = std.mem.split(u8, trimmed, "=");
868 const key = kv_it.first();
869 try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig);
870 }
871
872 // Finally, trailing is expected output
873 manifest.trailing_bytes = manifest_bytes[it.index..];
874
875 return manifest;
876 }
877
878 fn getConfigForKey(
879 self: TestManifest,
880 key: []const u8,
881 comptime T: type,
882 ) ConfigValueIterator(T) {
883 const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key);
884 return ConfigValueIterator(T){
885 .inner = std.mem.split(u8, bytes, ","),
886 };
887 }
888
889 fn getConfigForKeyAlloc(
890 self: TestManifest,
891 allocator: Allocator,
892 key: []const u8,
893 comptime T: type,
894 ) ![]const T {
895 var out = std.ArrayList(T).init(allocator);
896 defer out.deinit();
897 var it = self.getConfigForKey(key, T);
898 while (try it.next()) |item| {
899 try out.append(item);
900 }
901 return try out.toOwnedSlice();
902 }
903
904 fn getConfigForKeyAssertSingle(self: TestManifest, key: []const u8, comptime T: type) !T {
905 var it = self.getConfigForKey(key, T);
906 const res = (try it.next()) orelse unreachable;
907 assert((try it.next()) == null);
908 return res;
909 }
910
911 fn trailing(self: TestManifest) TrailingIterator {
912 return .{
913 .inner = std.mem.tokenize(u8, self.trailing_bytes, "\r\n"),
914 };
915 }
916
917 fn trailingAlloc(self: TestManifest, allocator: Allocator) error{OutOfMemory}![]const []const u8 {
918 var out = std.ArrayList([]const u8).init(allocator);
919 defer out.deinit();
920 var it = self.trailing();
921 while (it.next()) |line| {
922 try out.append(line);
923 }
924 return try out.toOwnedSlice();
925 }
926
927 fn ParseFn(comptime T: type) type {
928 return fn ([]const u8) anyerror!T;
929 }
930
931 fn getDefaultParser(comptime T: type) ParseFn(T) {
932 if (T == CrossTarget) return struct {
933 fn parse(str: []const u8) anyerror!T {
934 var opts = CrossTarget.ParseOptions{
935 .arch_os_abi = str,
936 };
937 return try CrossTarget.parse(opts);
938 }
939 }.parse;
940
941 switch (@typeInfo(T)) {
942 .Int => return struct {
943 fn parse(str: []const u8) anyerror!T {
944 return try std.fmt.parseInt(T, str, 0);
945 }
946 }.parse,
947 .Bool => return struct {
948 fn parse(str: []const u8) anyerror!T {
949 const as_int = try std.fmt.parseInt(u1, str, 0);
950 return as_int > 0;
951 }
952 }.parse,
953 .Enum => return struct {
954 fn parse(str: []const u8) anyerror!T {
955 return std.meta.stringToEnum(T, str) orelse {
956 std.log.err("unknown enum variant for {s}: {s}", .{ @typeName(T), str });
957 return error.UnknownEnumVariant;
958 };
959 }
960 }.parse,
961 .Struct => @compileError("no default parser for " ++ @typeName(T)),
962 else => @compileError("no default parser for " ++ @typeName(T)),
963 }
964 }
965};
966
967const Cases = @This();
968const builtin = @import("builtin");
969const std = @import("std");
970const assert = std.debug.assert;
971const Allocator = std.mem.Allocator;
972const CrossTarget = std.zig.CrossTarget;
973const Compilation = @import("../../src/Compilation.zig");
974const zig_h = @import("../../src/link.zig").File.C.zig_h;
975const introspect = @import("../../src/introspect.zig");
976const ThreadPool = std.Thread.Pool;
977const WaitGroup = std.Thread.WaitGroup;
978const build_options = @import("build_options");
979const Package = @import("../../src/Package.zig");
980
981pub const std_options = struct {
982 pub const log_level: std.log.Level = .err;
983};
984
985var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{
986 .stack_trace_frames = build_options.mem_leak_frames,
987}){};
988
989// TODO: instead of embedding the compiler in this process, spawn the compiler
990// as a sub-process and communicate the updates using the compiler protocol.
991pub fn main() !void {
992 const use_gpa = build_options.force_gpa or !builtin.link_libc;
993 const gpa = gpa: {
994 if (use_gpa) {
995 break :gpa general_purpose_allocator.allocator();
996 }
997 // We would prefer to use raw libc allocator here, but cannot
998 // use it if it won't support the alignment we need.
999 if (@alignOf(std.c.max_align_t) < @alignOf(i128)) {
1000 break :gpa std.heap.c_allocator;
1001 }
1002 break :gpa std.heap.raw_c_allocator;
1003 };
1004
1005 var single_threaded_arena = std.heap.ArenaAllocator.init(gpa);
1006 defer single_threaded_arena.deinit();
1007
1008 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
1009 .child_allocator = single_threaded_arena.allocator(),
1010 };
1011 const arena = thread_safe_arena.allocator();
1012
1013 const args = try std.process.argsAlloc(arena);
1014 const case_file_path = args[1];
1015 const zig_exe_path = args[2];
1016
1017 var filenames = std.ArrayList([]const u8).init(arena);
1018
1019 const case_dirname = std.fs.path.dirname(case_file_path).?;
1020 var iterable_dir = try std.fs.cwd().openIterableDir(case_dirname, .{});
1021 defer iterable_dir.close();
1022
1023 if (std.mem.endsWith(u8, case_file_path, ".0.zig")) {
1024 const stem = case_file_path[case_dirname.len + 1 .. case_file_path.len - "0.zig".len];
1025 var it = iterable_dir.iterate();
1026 while (try it.next()) |entry| {
1027 if (entry.kind != .File) continue;
1028 if (!std.mem.startsWith(u8, entry.name, stem)) continue;
1029 try filenames.append(try std.fs.path.join(arena, &.{ case_dirname, entry.name }));
1030 }
1031 } else {
1032 try filenames.append(case_file_path);
1033 }
1034
1035 if (filenames.items.len == 0) {
1036 std.debug.print("failed to find the input source file(s) from '{s}'\n", .{
1037 case_file_path,
1038 });
1039 std.process.exit(1);
1040 }
1041
1042 // Sort filenames, so that incremental tests are contiguous and in-order
1043 sortTestFilenames(filenames.items);
1044
1045 var ctx = Cases.init(gpa, arena);
1046
1047 var test_it = TestIterator{ .filenames = filenames.items };
1048 while (test_it.next()) |maybe_batch| {
1049 const batch = maybe_batch orelse break;
1050 const strategy: TestStrategy = if (batch.len > 1) .incremental else .independent;
1051 var cases = std.ArrayList(usize).init(arena);
1052
1053 for (batch) |filename| {
1054 const max_file_size = 10 * 1024 * 1024;
1055 const src = try iterable_dir.dir.readFileAllocOptions(arena, filename, max_file_size, null, 1, 0);
1056
1057 // Parse the manifest
1058 var manifest = try TestManifest.parse(arena, src);
1059
1060 if (cases.items.len == 0) {
1061 const backends = try manifest.getConfigForKeyAlloc(arena, "backend", Backend);
1062 const targets = try manifest.getConfigForKeyAlloc(arena, "target", CrossTarget);
1063 const is_test = try manifest.getConfigForKeyAssertSingle("is_test", bool);
1064 const output_mode = try manifest.getConfigForKeyAssertSingle("output_mode", std.builtin.OutputMode);
1065
1066 // Cross-product to get all possible test combinations
1067 for (backends) |backend| {
1068 for (targets) |target| {
1069 const next = ctx.cases.items.len;
1070 try ctx.cases.append(.{
1071 .name = std.fs.path.stem(filename),
1072 .target = target,
1073 .backend = backend,
1074 .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator),
1075 .is_test = is_test,
1076 .output_mode = output_mode,
1077 .link_libc = backend == .llvm,
1078 .deps = std.ArrayList(DepModule).init(ctx.cases.allocator),
1079 });
1080 try cases.append(next);
1081 }
1082 }
1083 }
1084
1085 for (cases.items) |case_index| {
1086 const case = &ctx.cases.items[case_index];
1087 switch (manifest.type) {
1088 .compile => {
1089 case.addCompile(src);
1090 },
1091 .@"error" => {
1092 const errors = try manifest.trailingAlloc(arena);
1093 switch (strategy) {
1094 .independent => {
1095 case.addError(src, errors);
1096 },
1097 .incremental => {
1098 case.addErrorNamed("update", src, errors);
1099 },
1100 }
1101 },
1102 .run => {
1103 var output = std.ArrayList(u8).init(arena);
1104 var trailing_it = manifest.trailing();
1105 while (trailing_it.next()) |line| {
1106 try output.appendSlice(line);
1107 try output.append('\n');
1108 }
1109 if (output.items.len > 0) {
1110 try output.resize(output.items.len - 1);
1111 }
1112 case.addCompareOutput(src, try output.toOwnedSlice());
1113 },
1114 .cli => @panic("TODO cli tests"),
1115 }
1116 }
1117 }
1118 } else |err| {
1119 return err;
1120 }
1121
1122 return runCases(&ctx, zig_exe_path);
1123}
1124
1125fn runCases(self: *Cases, zig_exe_path: []const u8) !void {
1126 const host = try std.zig.system.NativeTargetInfo.detect(.{});
1127
1128 var progress = std.Progress{};
1129 const root_node = progress.start("compiler", self.cases.items.len);
1130 progress.terminal = null;
1131 defer root_node.end();
1132
1133 var zig_lib_directory = try introspect.findZigLibDir(self.gpa);
1134 defer zig_lib_directory.handle.close();
1135 defer self.gpa.free(zig_lib_directory.path.?);
1136
1137 var aux_thread_pool: ThreadPool = undefined;
1138 try aux_thread_pool.init(.{ .allocator = self.gpa });
1139 defer aux_thread_pool.deinit();
1140
1141 // Use the same global cache dir for all the tests, such that we for example don't have to
1142 // rebuild musl libc for every case (when LLVM backend is enabled).
1143 var global_tmp = std.testing.tmpDir(.{});
1144 defer global_tmp.cleanup();
1145
1146 var cache_dir = try global_tmp.dir.makeOpenPath("zig-cache", .{});
1147 defer cache_dir.close();
1148 const tmp_dir_path = try std.fs.path.join(self.gpa, &[_][]const u8{ ".", "zig-cache", "tmp", &global_tmp.sub_path });
1149 defer self.gpa.free(tmp_dir_path);
1150
1151 const global_cache_directory: Compilation.Directory = .{
1152 .handle = cache_dir,
1153 .path = try std.fs.path.join(self.gpa, &[_][]const u8{ tmp_dir_path, "zig-cache" }),
1154 };
1155 defer self.gpa.free(global_cache_directory.path.?);
1156
1157 {
1158 for (self.cases.items) |*case| {
1159 if (build_options.skip_non_native) {
1160 if (case.target.getCpuArch() != builtin.cpu.arch)
1161 continue;
1162 if (case.target.getObjectFormat() != builtin.object_format)
1163 continue;
1164 }
1165
1166 // Skip tests that require LLVM backend when it is not available
1167 if (!build_options.have_llvm and case.backend == .llvm)
1168 continue;
1169
1170 assert(case.backend != .stage1);
1171
1172 if (build_options.test_filter) |test_filter| {
1173 if (std.mem.indexOf(u8, case.name, test_filter) == null) continue;
1174 }
1175
1176 var prg_node = root_node.start(case.name, case.updates.items.len);
1177 prg_node.activate();
1178 defer prg_node.end();
1179
1180 try runOneCase(
1181 self.gpa,
1182 &prg_node,
1183 case.*,
1184 zig_lib_directory,
1185 zig_exe_path,
1186 &aux_thread_pool,
1187 global_cache_directory,
1188 host,
1189 );
1190 }
1191 }
1192}
1193
1194fn runOneCase(
1195 allocator: Allocator,
1196 root_node: *std.Progress.Node,
1197 case: Case,
1198 zig_lib_directory: Compilation.Directory,
1199 zig_exe_path: []const u8,
1200 thread_pool: *ThreadPool,
1201 global_cache_directory: Compilation.Directory,
1202 host: std.zig.system.NativeTargetInfo,
1203) !void {
1204 const tmp_src_path = "tmp.zig";
1205 const enable_rosetta = build_options.enable_rosetta;
1206 const enable_qemu = build_options.enable_qemu;
1207 const enable_wine = build_options.enable_wine;
1208 const enable_wasmtime = build_options.enable_wasmtime;
1209 const enable_darling = build_options.enable_darling;
1210 const glibc_runtimes_dir: ?[]const u8 = build_options.glibc_runtimes_dir;
1211
1212 const target_info = try std.zig.system.NativeTargetInfo.detect(case.target);
1213 const target = target_info.target;
1214
1215 var arena_allocator = std.heap.ArenaAllocator.init(allocator);
1216 defer arena_allocator.deinit();
1217 const arena = arena_allocator.allocator();
1218
1219 var tmp = std.testing.tmpDir(.{});
1220 defer tmp.cleanup();
1221
1222 var cache_dir = try tmp.dir.makeOpenPath("zig-cache", .{});
1223 defer cache_dir.close();
1224
1225 const tmp_dir_path = try std.fs.path.join(
1226 arena,
1227 &[_][]const u8{ ".", "zig-cache", "tmp", &tmp.sub_path },
1228 );
1229 const local_cache_path = try std.fs.path.join(
1230 arena,
1231 &[_][]const u8{ tmp_dir_path, "zig-cache" },
1232 );
1233
1234 const zig_cache_directory: Compilation.Directory = .{
1235 .handle = cache_dir,
1236 .path = local_cache_path,
1237 };
1238
1239 var main_pkg: Package = .{
1240 .root_src_directory = .{ .path = tmp_dir_path, .handle = tmp.dir },
1241 .root_src_path = tmp_src_path,
1242 };
1243 defer {
1244 var it = main_pkg.table.iterator();
1245 while (it.next()) |kv| {
1246 allocator.free(kv.key_ptr.*);
1247 kv.value_ptr.*.destroy(allocator);
1248 }
1249 main_pkg.table.deinit(allocator);
1250 }
1251
1252 for (case.deps.items) |dep| {
1253 var pkg = try Package.create(
1254 allocator,
1255 tmp_dir_path,
1256 dep.path,
1257 );
1258 errdefer pkg.destroy(allocator);
1259 try main_pkg.add(allocator, dep.name, pkg);
1260 }
1261
1262 const bin_name = try std.zig.binNameAlloc(arena, .{
1263 .root_name = "test_case",
1264 .target = target,
1265 .output_mode = case.output_mode,
1266 });
1267
1268 const emit_directory: Compilation.Directory = .{
1269 .path = tmp_dir_path,
1270 .handle = tmp.dir,
1271 };
1272 const emit_bin: Compilation.EmitLoc = .{
1273 .directory = emit_directory,
1274 .basename = bin_name,
1275 };
1276 const emit_h: ?Compilation.EmitLoc = if (case.emit_h) .{
1277 .directory = emit_directory,
1278 .basename = "test_case.h",
1279 } else null;
1280 const use_llvm: bool = switch (case.backend) {
1281 .llvm => true,
1282 else => false,
1283 };
1284 const comp = try Compilation.create(allocator, .{
1285 .local_cache_directory = zig_cache_directory,
1286 .global_cache_directory = global_cache_directory,
1287 .zig_lib_directory = zig_lib_directory,
1288 .thread_pool = thread_pool,
1289 .root_name = "test_case",
1290 .target = target,
1291 // TODO: support tests for object file building, and library builds
1292 // and linking. This will require a rework to support multi-file
1293 // tests.
1294 .output_mode = case.output_mode,
1295 .is_test = case.is_test,
1296 .optimize_mode = case.optimize_mode,
1297 .emit_bin = emit_bin,
1298 .emit_h = emit_h,
1299 .main_pkg = &main_pkg,
1300 .keep_source_files_loaded = true,
1301 .is_native_os = case.target.isNativeOs(),
1302 .is_native_abi = case.target.isNativeAbi(),
1303 .dynamic_linker = target_info.dynamic_linker.get(),
1304 .link_libc = case.link_libc,
1305 .use_llvm = use_llvm,
1306 .self_exe_path = zig_exe_path,
1307 // TODO instead of turning off color, pass in a std.Progress.Node
1308 .color = .off,
1309 .reference_trace = 0,
1310 // TODO: force self-hosted linkers with stage2 backend to avoid LLD creeping in
1311 // until the auto-select mechanism deems them worthy
1312 .use_lld = switch (case.backend) {
1313 .stage2 => false,
1314 else => null,
1315 },
1316 });
1317 defer comp.destroy();
1318
1319 update: for (case.updates.items, 0..) |update, update_index| {
1320 var update_node = root_node.start(update.name, 3);
1321 update_node.activate();
1322 defer update_node.end();
1323
1324 var sync_node = update_node.start("write", 0);
1325 sync_node.activate();
1326 for (update.files.items) |file| {
1327 try tmp.dir.writeFile(file.path, file.src);
1328 }
1329 sync_node.end();
1330
1331 var module_node = update_node.start("parse/analysis/codegen", 0);
1332 module_node.activate();
1333 try comp.makeBinFileWritable();
1334 try comp.update(&module_node);
1335 module_node.end();
1336
1337 if (update.case != .Error) {
1338 var all_errors = try comp.getAllErrorsAlloc();
1339 defer all_errors.deinit(allocator);
1340 if (all_errors.errorMessageCount() > 0) {
1341 all_errors.renderToStdErr(.{
1342 .ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()),
1343 });
1344 // TODO print generated C code
1345 return error.UnexpectedCompileErrors;
1346 }
1347 }
1348
1349 switch (update.case) {
1350 .Header => |expected_output| {
1351 var file = try tmp.dir.openFile("test_case.h", .{ .mode = .read_only });
1352 defer file.close();
1353 const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024);
1354
1355 try std.testing.expectEqualStrings(expected_output, out);
1356 },
1357 .CompareObjectFile => |expected_output| {
1358 var file = try tmp.dir.openFile(bin_name, .{ .mode = .read_only });
1359 defer file.close();
1360 const out = try file.reader().readAllAlloc(arena, 5 * 1024 * 1024);
1361
1362 try std.testing.expectEqualStrings(expected_output, out);
1363 },
1364 .Compile => {},
1365 .Error => |expected_errors| {
1366 var test_node = update_node.start("assert", 0);
1367 test_node.activate();
1368 defer test_node.end();
1369
1370 var error_bundle = try comp.getAllErrorsAlloc();
1371 defer error_bundle.deinit(allocator);
1372
1373 if (error_bundle.errorMessageCount() == 0) {
1374 return error.ExpectedCompilationErrors;
1375 }
1376
1377 var actual_stderr = std.ArrayList(u8).init(arena);
1378 try error_bundle.renderToWriter(.{
1379 .ttyconf = .no_color,
1380 .include_reference_trace = false,
1381 .include_source_line = false,
1382 }, actual_stderr.writer());
1383
1384 // Render the expected lines into a string that we can compare verbatim.
1385 var expected_generated = std.ArrayList(u8).init(arena);
1386
1387 var actual_line_it = std.mem.split(u8, actual_stderr.items, "\n");
1388 for (expected_errors) |expect_line| {
1389 const actual_line = actual_line_it.next() orelse {
1390 try expected_generated.appendSlice(expect_line);
1391 try expected_generated.append('\n');
1392 continue;
1393 };
1394 if (std.mem.endsWith(u8, actual_line, expect_line)) {
1395 try expected_generated.appendSlice(actual_line);
1396 try expected_generated.append('\n');
1397 continue;
1398 }
1399 if (std.mem.startsWith(u8, expect_line, ":?:?: ")) {
1400 if (std.mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) {
1401 try expected_generated.appendSlice(actual_line);
1402 try expected_generated.append('\n');
1403 continue;
1404 }
1405 }
1406 try expected_generated.appendSlice(expect_line);
1407 try expected_generated.append('\n');
1408 }
1409
1410 try std.testing.expectEqualStrings(expected_generated.items, actual_stderr.items);
1411 },
1412 .Execution => |expected_stdout| {
1413 if (!std.process.can_spawn) {
1414 std.debug.print("Unable to spawn child processes on {s}, skipping test.\n", .{@tagName(builtin.os.tag)});
1415 continue :update; // Pass test.
1416 }
1417
1418 update_node.setEstimatedTotalItems(4);
1419
1420 var argv = std.ArrayList([]const u8).init(allocator);
1421 defer argv.deinit();
1422
1423 var exec_result = x: {
1424 var exec_node = update_node.start("execute", 0);
1425 exec_node.activate();
1426 defer exec_node.end();
1427
1428 // We go out of our way here to use the unique temporary directory name in
1429 // the exe_path so that it makes its way into the cache hash, avoiding
1430 // cache collisions from multiple threads doing `zig run` at the same time
1431 // on the same test_case.c input filename.
1432 const ss = std.fs.path.sep_str;
1433 const exe_path = try std.fmt.allocPrint(
1434 arena,
1435 ".." ++ ss ++ "{s}" ++ ss ++ "{s}",
1436 .{ &tmp.sub_path, bin_name },
1437 );
1438 if (case.target.ofmt != null and case.target.ofmt.? == .c) {
1439 if (host.getExternalExecutor(target_info, .{ .link_libc = true }) != .native) {
1440 // We wouldn't be able to run the compiled C code.
1441 continue :update; // Pass test.
1442 }
1443 try argv.appendSlice(&[_][]const u8{
1444 zig_exe_path,
1445 "run",
1446 "-cflags",
1447 "-std=c99",
1448 "-pedantic",
1449 "-Werror",
1450 "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875
1451 "--",
1452 "-lc",
1453 exe_path,
1454 });
1455 if (zig_lib_directory.path) |p| {
1456 try argv.appendSlice(&.{ "-I", p });
1457 }
1458 } else switch (host.getExternalExecutor(target_info, .{ .link_libc = case.link_libc })) {
1459 .native => {
1460 if (case.backend == .stage2 and case.target.getCpuArch() == .arm) {
1461 // https://github.com/ziglang/zig/issues/13623
1462 continue :update; // Pass test.
1463 }
1464 try argv.append(exe_path);
1465 },
1466 .bad_dl, .bad_os_or_cpu => continue :update, // Pass test.
1467
1468 .rosetta => if (enable_rosetta) {
1469 try argv.append(exe_path);
1470 } else {
1471 continue :update; // Rosetta not available, pass test.
1472 },
1473
1474 .qemu => |qemu_bin_name| if (enable_qemu) {
1475 const need_cross_glibc = target.isGnuLibC() and case.link_libc;
1476 const glibc_dir_arg: ?[]const u8 = if (need_cross_glibc)
1477 glibc_runtimes_dir orelse continue :update // glibc dir not available; pass test
1478 else
1479 null;
1480 try argv.append(qemu_bin_name);
1481 if (glibc_dir_arg) |dir| {
1482 const linux_triple = try target.linuxTriple(arena);
1483 const full_dir = try std.fs.path.join(arena, &[_][]const u8{
1484 dir,
1485 linux_triple,
1486 });
1487
1488 try argv.append("-L");
1489 try argv.append(full_dir);
1490 }
1491 try argv.append(exe_path);
1492 } else {
1493 continue :update; // QEMU not available; pass test.
1494 },
1495
1496 .wine => |wine_bin_name| if (enable_wine) {
1497 try argv.append(wine_bin_name);
1498 try argv.append(exe_path);
1499 } else {
1500 continue :update; // Wine not available; pass test.
1501 },
1502
1503 .wasmtime => |wasmtime_bin_name| if (enable_wasmtime) {
1504 try argv.append(wasmtime_bin_name);
1505 try argv.append("--dir=.");
1506 try argv.append(exe_path);
1507 } else {
1508 continue :update; // wasmtime not available; pass test.
1509 },
1510
1511 .darling => |darling_bin_name| if (enable_darling) {
1512 try argv.append(darling_bin_name);
1513 // Since we use relative to cwd here, we invoke darling with
1514 // "shell" subcommand.
1515 try argv.append("shell");
1516 try argv.append(exe_path);
1517 } else {
1518 continue :update; // Darling not available; pass test.
1519 },
1520 }
1521
1522 try comp.makeBinFileExecutable();
1523
1524 while (true) {
1525 break :x std.ChildProcess.exec(.{
1526 .allocator = allocator,
1527 .argv = argv.items,
1528 .cwd_dir = tmp.dir,
1529 .cwd = tmp_dir_path,
1530 }) catch |err| switch (err) {
1531 error.FileBusy => {
1532 // There is a fundamental design flaw in Unix systems with how
1533 // ETXTBSY interacts with fork+exec.
1534 // https://github.com/golang/go/issues/22315
1535 // https://bugs.openjdk.org/browse/JDK-8068370
1536 // Unfortunately, this could be a real error, but we can't
1537 // tell the difference here.
1538 continue;
1539 },
1540 else => {
1541 std.debug.print("\n{s}.{d} The following command failed with {s}:\n", .{
1542 case.name, update_index, @errorName(err),
1543 });
1544 dumpArgs(argv.items);
1545 return error.ChildProcessExecution;
1546 },
1547 };
1548 }
1549 };
1550 var test_node = update_node.start("test", 0);
1551 test_node.activate();
1552 defer test_node.end();
1553 defer allocator.free(exec_result.stdout);
1554 defer allocator.free(exec_result.stderr);
1555 switch (exec_result.term) {
1556 .Exited => |code| {
1557 if (code != 0) {
1558 std.debug.print("\n{s}\n{s}: execution exited with code {d}:\n", .{
1559 exec_result.stderr, case.name, code,
1560 });
1561 dumpArgs(argv.items);
1562 return error.ChildProcessExecution;
1563 }
1564 },
1565 else => {
1566 std.debug.print("\n{s}\n{s}: execution crashed:\n", .{
1567 exec_result.stderr, case.name,
1568 });
1569 dumpArgs(argv.items);
1570 return error.ChildProcessExecution;
1571 },
1572 }
1573 try std.testing.expectEqualStrings(expected_stdout, exec_result.stdout);
1574 // We allow stderr to have garbage in it because wasmtime prints a
1575 // warning about --invoke even though we don't pass it.
1576 //std.testing.expectEqualStrings("", exec_result.stderr);
1577 },
1578 }
1579 }
1580}
1581
1582fn dumpArgs(argv: []const []const u8) void {
1583 for (argv) |arg| {
1584 std.debug.print("{s} ", .{arg});
1585 }
1586 std.debug.print("\n", .{});
1587}
test/src/CompareOutput.zig created+174
......@@ -0,0 +1,174 @@
1//! This is the implementation of the test harness.
2//! For the actual test cases, see test/compare_output.zig.
3
4b: *std.Build,
5step: *std.Build.Step,
6test_index: usize,
7test_filter: ?[]const u8,
8optimize_modes: []const OptimizeMode,
9
10const Special = enum {
11 None,
12 Asm,
13 RuntimeSafety,
14};
15
16const TestCase = struct {
17 name: []const u8,
18 sources: ArrayList(SourceFile),
19 expected_output: []const u8,
20 link_libc: bool,
21 special: Special,
22 cli_args: []const []const u8,
23
24 const SourceFile = struct {
25 filename: []const u8,
26 source: []const u8,
27 };
28
29 pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void {
30 self.sources.append(SourceFile{
31 .filename = filename,
32 .source = source,
33 }) catch @panic("OOM");
34 }
35
36 pub fn setCommandLineArgs(self: *TestCase, args: []const []const u8) void {
37 self.cli_args = args;
38 }
39};
40
41pub fn createExtra(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8, special: Special) TestCase {
42 var tc = TestCase{
43 .name = name,
44 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
45 .expected_output = expected_output,
46 .link_libc = false,
47 .special = special,
48 .cli_args = &[_][]const u8{},
49 };
50 const root_src_name = if (special == Special.Asm) "source.s" else "source.zig";
51 tc.addSourceFile(root_src_name, source);
52 return tc;
53}
54
55pub fn create(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) TestCase {
56 return createExtra(self, name, source, expected_output, Special.None);
57}
58
59pub fn addC(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) void {
60 var tc = self.create(name, source, expected_output);
61 tc.link_libc = true;
62 self.addCase(tc);
63}
64
65pub fn add(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) void {
66 const tc = self.create(name, source, expected_output);
67 self.addCase(tc);
68}
69
70pub fn addAsm(self: *CompareOutput, name: []const u8, source: []const u8, expected_output: []const u8) void {
71 const tc = self.createExtra(name, source, expected_output, Special.Asm);
72 self.addCase(tc);
73}
74
75pub fn addRuntimeSafety(self: *CompareOutput, name: []const u8, source: []const u8) void {
76 const tc = self.createExtra(name, source, undefined, Special.RuntimeSafety);
77 self.addCase(tc);
78}
79
80pub fn addCase(self: *CompareOutput, case: TestCase) void {
81 const b = self.b;
82
83 const write_src = b.addWriteFiles();
84 for (case.sources.items) |src_file| {
85 write_src.add(src_file.filename, src_file.source);
86 }
87
88 switch (case.special) {
89 Special.Asm => {
90 const annotated_case_name = fmt.allocPrint(self.b.allocator, "run assemble-and-link {s}", .{
91 case.name,
92 }) catch @panic("OOM");
93 if (self.test_filter) |filter| {
94 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
95 }
96
97 const exe = b.addExecutable(.{
98 .name = "test",
99 .target = .{},
100 .optimize = .Debug,
101 });
102 exe.addAssemblyFileSource(write_src.getFileSource(case.sources.items[0].filename).?);
103
104 const run = exe.run();
105 run.setName(annotated_case_name);
106 run.addArgs(case.cli_args);
107 run.expectStdOutEqual(case.expected_output);
108
109 self.step.dependOn(&run.step);
110 },
111 Special.None => {
112 for (self.optimize_modes) |optimize| {
113 const annotated_case_name = fmt.allocPrint(self.b.allocator, "run compare-output {s} ({s})", .{
114 case.name, @tagName(optimize),
115 }) catch @panic("OOM");
116 if (self.test_filter) |filter| {
117 if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
118 }
119
120 const basename = case.sources.items[0].filename;
121 const exe = b.addExecutable(.{
122 .name = "test",
123 .root_source_file = write_src.getFileSource(basename).?,
124 .optimize = optimize,
125 .target = .{},
126 });
127 if (case.link_libc) {
128 exe.linkSystemLibrary("c");
129 }
130
131 const run = exe.run();
132 run.setName(annotated_case_name);
133 run.addArgs(case.cli_args);
134 run.expectStdOutEqual(case.expected_output);
135
136 self.step.dependOn(&run.step);
137 }
138 },
139 Special.RuntimeSafety => {
140 // TODO iterate over self.optimize_modes and test this in both
141 // debug and release safe mode
142 const annotated_case_name = fmt.allocPrint(self.b.allocator, "run safety {s}", .{case.name}) catch @panic("OOM");
143 if (self.test_filter) |filter| {
144 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
145 }
146
147 const basename = case.sources.items[0].filename;
148 const exe = b.addExecutable(.{
149 .name = "test",
150 .root_source_file = write_src.getFileSource(basename).?,
151 .target = .{},
152 .optimize = .Debug,
153 });
154 if (case.link_libc) {
155 exe.linkSystemLibrary("c");
156 }
157
158 const run = exe.run();
159 run.setName(annotated_case_name);
160 run.addArgs(case.cli_args);
161 run.expectExitCode(126);
162
163 self.step.dependOn(&run.step);
164 },
165 }
166}
167
168const CompareOutput = @This();
169const std = @import("std");
170const ArrayList = std.ArrayList;
171const fmt = std.fmt;
172const mem = std.mem;
173const fs = std.fs;
174const OptimizeMode = std.builtin.OptimizeMode;
test/src/StackTrace.zig created+107
......@@ -0,0 +1,107 @@
1b: *std.Build,
2step: *Step,
3test_index: usize,
4test_filter: ?[]const u8,
5optimize_modes: []const OptimizeMode,
6check_exe: *std.Build.CompileStep,
7
8const Expect = [@typeInfo(OptimizeMode).Enum.fields.len][]const u8;
9
10pub fn addCase(self: *StackTrace, config: anytype) void {
11 if (@hasField(@TypeOf(config), "exclude")) {
12 if (config.exclude.exclude()) return;
13 }
14 if (@hasField(@TypeOf(config), "exclude_arch")) {
15 const exclude_arch: []const std.Target.Cpu.Arch = &config.exclude_arch;
16 for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return;
17 }
18 if (@hasField(@TypeOf(config), "exclude_os")) {
19 const exclude_os: []const std.Target.Os.Tag = &config.exclude_os;
20 for (exclude_os) |os| if (os == builtin.os.tag) return;
21 }
22 for (self.optimize_modes) |optimize_mode| {
23 switch (optimize_mode) {
24 .Debug => {
25 if (@hasField(@TypeOf(config), "Debug")) {
26 self.addExpect(config.name, config.source, optimize_mode, config.Debug);
27 }
28 },
29 .ReleaseSafe => {
30 if (@hasField(@TypeOf(config), "ReleaseSafe")) {
31 self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSafe);
32 }
33 },
34 .ReleaseFast => {
35 if (@hasField(@TypeOf(config), "ReleaseFast")) {
36 self.addExpect(config.name, config.source, optimize_mode, config.ReleaseFast);
37 }
38 },
39 .ReleaseSmall => {
40 if (@hasField(@TypeOf(config), "ReleaseSmall")) {
41 self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSmall);
42 }
43 },
44 }
45 }
46}
47
48fn addExpect(
49 self: *StackTrace,
50 name: []const u8,
51 source: []const u8,
52 optimize_mode: OptimizeMode,
53 mode_config: anytype,
54) void {
55 if (@hasField(@TypeOf(mode_config), "exclude")) {
56 if (mode_config.exclude.exclude()) return;
57 }
58 if (@hasField(@TypeOf(mode_config), "exclude_arch")) {
59 const exclude_arch: []const std.Target.Cpu.Arch = &mode_config.exclude_arch;
60 for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return;
61 }
62 if (@hasField(@TypeOf(mode_config), "exclude_os")) {
63 const exclude_os: []const std.Target.Os.Tag = &mode_config.exclude_os;
64 for (exclude_os) |os| if (os == builtin.os.tag) return;
65 }
66
67 const b = self.b;
68 const annotated_case_name = fmt.allocPrint(b.allocator, "check {s} ({s})", .{
69 name, @tagName(optimize_mode),
70 }) catch @panic("OOM");
71 if (self.test_filter) |filter| {
72 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
73 }
74
75 const src_basename = "source.zig";
76 const write_src = b.addWriteFile(src_basename, source);
77 const exe = b.addExecutable(.{
78 .name = "test",
79 .root_source_file = write_src.getFileSource(src_basename).?,
80 .optimize = optimize_mode,
81 .target = .{},
82 });
83
84 const run = b.addRunArtifact(exe);
85 run.removeEnvironmentVariable("ZIG_DEBUG_COLOR");
86 run.setEnvironmentVariable("NO_COLOR", "1");
87 run.expectExitCode(1);
88 run.expectStdOutEqual("");
89
90 const check_run = b.addRunArtifact(self.check_exe);
91 check_run.setName(annotated_case_name);
92 check_run.addFileSourceArg(run.captureStdErr());
93 check_run.addArgs(&.{
94 @tagName(optimize_mode),
95 });
96 check_run.expectStdOutEqual(mode_config.expect);
97
98 self.step.dependOn(&check_run.step);
99}
100
101const StackTrace = @This();
102const std = @import("std");
103const builtin = @import("builtin");
104const Step = std.Build.Step;
105const OptimizeMode = std.builtin.OptimizeMode;
106const fmt = std.fmt;
107const mem = std.mem;
test/src/check-stack-trace.zig created+79
......@@ -0,0 +1,79 @@
1const builtin = @import("builtin");
2const std = @import("std");
3const mem = std.mem;
4const fs = std.fs;
5
6pub fn main() !void {
7 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
8 defer arena_instance.deinit();
9 const arena = arena_instance.allocator();
10
11 const args = try std.process.argsAlloc(arena);
12
13 const input_path = args[1];
14 const optimize_mode_text = args[2];
15
16 const input_bytes = try std.fs.cwd().readFileAlloc(arena, input_path, 5 * 1024 * 1024);
17 const optimize_mode = std.meta.stringToEnum(std.builtin.OptimizeMode, optimize_mode_text).?;
18
19 var stderr = input_bytes;
20
21 // process result
22 // - keep only basename of source file path
23 // - replace address with symbolic string
24 // - replace function name with symbolic string when optimize_mode != .Debug
25 // - skip empty lines
26 const got: []const u8 = got_result: {
27 var buf = std.ArrayList(u8).init(arena);
28 defer buf.deinit();
29 if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1];
30 var it = mem.split(u8, stderr, "\n");
31 process_lines: while (it.next()) |line| {
32 if (line.len == 0) continue;
33
34 // offset search past `[drive]:` on windows
35 var pos: usize = if (builtin.os.tag == .windows) 2 else 0;
36 // locate delims/anchor
37 const delims = [_][]const u8{ ":", ":", ":", " in ", "(", ")" };
38 var marks = [_]usize{0} ** delims.len;
39 for (delims, 0..) |delim, i| {
40 marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse {
41 // unexpected pattern: emit raw line and cont
42 try buf.appendSlice(line);
43 try buf.appendSlice("\n");
44 continue :process_lines;
45 };
46 pos = marks[i] + delim.len;
47 }
48 // locate source basename
49 pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse {
50 // unexpected pattern: emit raw line and cont
51 try buf.appendSlice(line);
52 try buf.appendSlice("\n");
53 continue :process_lines;
54 };
55 // end processing if source basename changes
56 if (!mem.eql(u8, "source.zig", line[pos + 1 .. marks[0]])) break;
57 // emit substituted line
58 try buf.appendSlice(line[pos + 1 .. marks[2] + delims[2].len]);
59 try buf.appendSlice(" [address]");
60 if (optimize_mode == .Debug) {
61 // On certain platforms (windows) or possibly depending on how we choose to link main
62 // the object file extension may be present so we simply strip any extension.
63 if (mem.indexOfScalar(u8, line[marks[4]..marks[5]], '.')) |idot| {
64 try buf.appendSlice(line[marks[3] .. marks[4] + idot]);
65 try buf.appendSlice(line[marks[5]..]);
66 } else {
67 try buf.appendSlice(line[marks[3]..]);
68 }
69 } else {
70 try buf.appendSlice(line[marks[3] .. marks[3] + delims[3].len]);
71 try buf.appendSlice("[function]");
72 }
73 try buf.appendSlice("\n");
74 }
75 break :got_result try buf.toOwnedSlice();
76 };
77
78 try std.io.getStdOut().writeAll(got);
79}
test/src/compare_output.zig deleted-177
......@@ -1,177 +0,0 @@
1// This is the implementation of the test harness.
2// For the actual test cases, see test/compare_output.zig.
3const std = @import("std");
4const ArrayList = std.ArrayList;
5const fmt = std.fmt;
6const mem = std.mem;
7const fs = std.fs;
8const OptimizeMode = std.builtin.OptimizeMode;
9
10pub const CompareOutputContext = struct {
11 b: *std.Build,
12 step: *std.Build.Step,
13 test_index: usize,
14 test_filter: ?[]const u8,
15 optimize_modes: []const OptimizeMode,
16
17 const Special = enum {
18 None,
19 Asm,
20 RuntimeSafety,
21 };
22
23 const TestCase = struct {
24 name: []const u8,
25 sources: ArrayList(SourceFile),
26 expected_output: []const u8,
27 link_libc: bool,
28 special: Special,
29 cli_args: []const []const u8,
30
31 const SourceFile = struct {
32 filename: []const u8,
33 source: []const u8,
34 };
35
36 pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void {
37 self.sources.append(SourceFile{
38 .filename = filename,
39 .source = source,
40 }) catch unreachable;
41 }
42
43 pub fn setCommandLineArgs(self: *TestCase, args: []const []const u8) void {
44 self.cli_args = args;
45 }
46 };
47
48 pub fn createExtra(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8, special: Special) TestCase {
49 var tc = TestCase{
50 .name = name,
51 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
52 .expected_output = expected_output,
53 .link_libc = false,
54 .special = special,
55 .cli_args = &[_][]const u8{},
56 };
57 const root_src_name = if (special == Special.Asm) "source.s" else "source.zig";
58 tc.addSourceFile(root_src_name, source);
59 return tc;
60 }
61
62 pub fn create(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) TestCase {
63 return createExtra(self, name, source, expected_output, Special.None);
64 }
65
66 pub fn addC(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void {
67 var tc = self.create(name, source, expected_output);
68 tc.link_libc = true;
69 self.addCase(tc);
70 }
71
72 pub fn add(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void {
73 const tc = self.create(name, source, expected_output);
74 self.addCase(tc);
75 }
76
77 pub fn addAsm(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void {
78 const tc = self.createExtra(name, source, expected_output, Special.Asm);
79 self.addCase(tc);
80 }
81
82 pub fn addRuntimeSafety(self: *CompareOutputContext, name: []const u8, source: []const u8) void {
83 const tc = self.createExtra(name, source, undefined, Special.RuntimeSafety);
84 self.addCase(tc);
85 }
86
87 pub fn addCase(self: *CompareOutputContext, case: TestCase) void {
88 const b = self.b;
89
90 const write_src = b.addWriteFiles();
91 for (case.sources.items) |src_file| {
92 write_src.add(src_file.filename, src_file.source);
93 }
94
95 switch (case.special) {
96 Special.Asm => {
97 const annotated_case_name = fmt.allocPrint(self.b.allocator, "assemble-and-link {s}", .{
98 case.name,
99 }) catch unreachable;
100 if (self.test_filter) |filter| {
101 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
102 }
103
104 const exe = b.addExecutable(.{
105 .name = "test",
106 .target = .{},
107 .optimize = .Debug,
108 });
109 exe.addAssemblyFileSource(write_src.getFileSource(case.sources.items[0].filename).?);
110
111 const run = exe.run();
112 run.addArgs(case.cli_args);
113 run.expectStdErrEqual("");
114 run.expectStdOutEqual(case.expected_output);
115
116 self.step.dependOn(&run.step);
117 },
118 Special.None => {
119 for (self.optimize_modes) |optimize| {
120 const annotated_case_name = fmt.allocPrint(self.b.allocator, "{s} {s} ({s})", .{
121 "compare-output",
122 case.name,
123 @tagName(optimize),
124 }) catch unreachable;
125 if (self.test_filter) |filter| {
126 if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
127 }
128
129 const basename = case.sources.items[0].filename;
130 const exe = b.addExecutable(.{
131 .name = "test",
132 .root_source_file = write_src.getFileSource(basename).?,
133 .optimize = optimize,
134 .target = .{},
135 });
136 if (case.link_libc) {
137 exe.linkSystemLibrary("c");
138 }
139
140 const run = exe.run();
141 run.addArgs(case.cli_args);
142 run.expectStdErrEqual("");
143 run.expectStdOutEqual(case.expected_output);
144
145 self.step.dependOn(&run.step);
146 }
147 },
148 Special.RuntimeSafety => {
149 // TODO iterate over self.optimize_modes and test this in both
150 // debug and release safe mode
151 const annotated_case_name = fmt.allocPrint(self.b.allocator, "safety {s}", .{case.name}) catch unreachable;
152 if (self.test_filter) |filter| {
153 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
154 }
155
156 const basename = case.sources.items[0].filename;
157 const exe = b.addExecutable(.{
158 .name = "test",
159 .root_source_file = write_src.getFileSource(basename).?,
160 .target = .{},
161 .optimize = .Debug,
162 });
163 if (case.link_libc) {
164 exe.linkSystemLibrary("c");
165 }
166
167 const run = exe.run();
168 run.addArgs(case.cli_args);
169 run.stderr_action = .ignore;
170 run.stdout_action = .ignore;
171 run.expected_term = .{ .Exited = 126 };
172
173 self.step.dependOn(&run.step);
174 },
175 }
176 }
177};
test/stage2/cbe.zig deleted-1015
......@@ -1,1015 +0,0 @@
1const std = @import("std");
2const TestContext = @import("../../src/test.zig").TestContext;
3
4// These tests should work with all platforms, but we're using linux_x64 for
5// now for consistency. Will be expanded eventually.
6const linux_x64 = std.zig.CrossTarget{
7 .cpu_arch = .x86_64,
8 .os_tag = .linux,
9};
10
11pub fn addCases(ctx: *TestContext) !void {
12 {
13 var case = ctx.exeFromCompiledC("hello world with updates", .{});
14
15 // Regular old hello world
16 case.addCompareOutput(
17 \\extern fn puts(s: [*:0]const u8) c_int;
18 \\pub export fn main() c_int {
19 \\ _ = puts("hello world!");
20 \\ return 0;
21 \\}
22 , "hello world!" ++ std.cstr.line_sep);
23
24 // Now change the message only
25 case.addCompareOutput(
26 \\extern fn puts(s: [*:0]const u8) c_int;
27 \\pub export fn main() c_int {
28 \\ _ = puts("yo");
29 \\ return 0;
30 \\}
31 , "yo" ++ std.cstr.line_sep);
32
33 // Add an unused Decl
34 case.addCompareOutput(
35 \\extern fn puts(s: [*:0]const u8) c_int;
36 \\pub export fn main() c_int {
37 \\ _ = puts("yo!");
38 \\ return 0;
39 \\}
40 \\fn unused() void {}
41 , "yo!" ++ std.cstr.line_sep);
42
43 // Comptime return type and calling convention expected.
44 case.addError(
45 \\var x: i32 = 1234;
46 \\pub export fn main() x {
47 \\ return 0;
48 \\}
49 \\export fn foo() callconv(y) c_int {
50 \\ return 0;
51 \\}
52 \\var y: @import("std").builtin.CallingConvention = .C;
53 , &.{
54 ":2:22: error: expected type 'type', found 'i32'",
55 ":5:26: error: unable to resolve comptime value",
56 ":5:26: note: calling convention must be comptime-known",
57 });
58 }
59
60 {
61 var case = ctx.exeFromCompiledC("var args", .{});
62
63 case.addCompareOutput(
64 \\extern fn printf(format: [*:0]const u8, ...) c_int;
65 \\
66 \\pub export fn main() c_int {
67 \\ _ = printf("Hello, %s!\n", "world");
68 \\ return 0;
69 \\}
70 , "Hello, world!" ++ std.cstr.line_sep);
71 }
72
73 {
74 var case = ctx.exeFromCompiledC("@intToError", .{});
75
76 case.addCompareOutput(
77 \\pub export fn main() c_int {
78 \\ // comptime checks
79 \\ const a = error.A;
80 \\ const b = error.B;
81 \\ const c = @intToError(2);
82 \\ const d = @intToError(1);
83 \\ if (!(c == b)) unreachable;
84 \\ if (!(a == d)) unreachable;
85 \\ // runtime checks
86 \\ var x = error.A;
87 \\ var y = error.B;
88 \\ var z = @intToError(2);
89 \\ var f = @intToError(1);
90 \\ if (!(y == z)) unreachable;
91 \\ if (!(x == f)) unreachable;
92 \\ return 0;
93 \\}
94 , "");
95 case.addError(
96 \\pub export fn main() c_int {
97 \\ _ = @intToError(0);
98 \\ return 0;
99 \\}
100 , &.{":2:21: error: integer value '0' represents no error"});
101 case.addError(
102 \\pub export fn main() c_int {
103 \\ _ = @intToError(3);
104 \\ return 0;
105 \\}
106 , &.{":2:21: error: integer value '3' represents no error"});
107 }
108
109 {
110 var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64);
111
112 // Exit with 0
113 case.addCompareOutput(
114 \\fn exitGood() noreturn {
115 \\ asm volatile ("syscall"
116 \\ :
117 \\ : [number] "{rax}" (231),
118 \\ [arg1] "{rdi}" (0)
119 \\ );
120 \\ unreachable;
121 \\}
122 \\
123 \\pub export fn main() c_int {
124 \\ exitGood();
125 \\}
126 , "");
127
128 // Pass a usize parameter to exit
129 case.addCompareOutput(
130 \\pub export fn main() c_int {
131 \\ exit(0);
132 \\}
133 \\
134 \\fn exit(code: usize) noreturn {
135 \\ asm volatile ("syscall"
136 \\ :
137 \\ : [number] "{rax}" (231),
138 \\ [arg1] "{rdi}" (code)
139 \\ );
140 \\ unreachable;
141 \\}
142 , "");
143
144 // Change the parameter to u8
145 case.addCompareOutput(
146 \\pub export fn main() c_int {
147 \\ exit(0);
148 \\}
149 \\
150 \\fn exit(code: u8) noreturn {
151 \\ asm volatile ("syscall"
152 \\ :
153 \\ : [number] "{rax}" (231),
154 \\ [arg1] "{rdi}" (code)
155 \\ );
156 \\ unreachable;
157 \\}
158 , "");
159
160 // Do some arithmetic at the exit callsite
161 case.addCompareOutput(
162 \\pub export fn main() c_int {
163 \\ exitMath(1);
164 \\}
165 \\
166 \\fn exitMath(a: u8) noreturn {
167 \\ exit(0 + a - a);
168 \\}
169 \\
170 \\fn exit(code: u8) noreturn {
171 \\ asm volatile ("syscall"
172 \\ :
173 \\ : [number] "{rax}" (231),
174 \\ [arg1] "{rdi}" (code)
175 \\ );
176 \\ unreachable;
177 \\}
178 \\
179 , "");
180
181 // Invert the arithmetic
182 case.addCompareOutput(
183 \\pub export fn main() c_int {
184 \\ exitMath(1);
185 \\}
186 \\
187 \\fn exitMath(a: u8) noreturn {
188 \\ exit(a + 0 - a);
189 \\}
190 \\
191 \\fn exit(code: u8) noreturn {
192 \\ asm volatile ("syscall"
193 \\ :
194 \\ : [number] "{rax}" (231),
195 \\ [arg1] "{rdi}" (code)
196 \\ );
197 \\ unreachable;
198 \\}
199 \\
200 , "");
201 }
202
203 {
204 var case = ctx.exeFromCompiledC("alloc and retptr", .{});
205
206 case.addCompareOutput(
207 \\fn add(a: i32, b: i32) i32 {
208 \\ return a + b;
209 \\}
210 \\
211 \\fn addIndirect(a: i32, b: i32) i32 {
212 \\ return add(a, b);
213 \\}
214 \\
215 \\pub export fn main() c_int {
216 \\ return addIndirect(1, 2) - 3;
217 \\}
218 , "");
219 }
220
221 {
222 var case = ctx.exeFromCompiledC("inferred local const and var", .{});
223
224 case.addCompareOutput(
225 \\fn add(a: i32, b: i32) i32 {
226 \\ return a + b;
227 \\}
228 \\
229 \\pub export fn main() c_int {
230 \\ const x = add(1, 2);
231 \\ var y = add(3, 0);
232 \\ y -= x;
233 \\ return y;
234 \\}
235 , "");
236 }
237 {
238 var case = ctx.exeFromCompiledC("control flow", .{});
239
240 // Simple while loop
241 case.addCompareOutput(
242 \\pub export fn main() c_int {
243 \\ var a: c_int = 0;
244 \\ while (a < 5) : (a+=1) {}
245 \\ return a - 5;
246 \\}
247 , "");
248 case.addCompareOutput(
249 \\pub export fn main() c_int {
250 \\ var a = true;
251 \\ while (!a) {}
252 \\ return 0;
253 \\}
254 , "");
255
256 // If expression
257 case.addCompareOutput(
258 \\pub export fn main() c_int {
259 \\ var cond: c_int = 0;
260 \\ var a: c_int = @as(c_int, if (cond == 0)
261 \\ 2
262 \\ else
263 \\ 3) + 9;
264 \\ return a - 11;
265 \\}
266 , "");
267
268 // If expression with breakpoint that does not get hit
269 case.addCompareOutput(
270 \\pub export fn main() c_int {
271 \\ var x: i32 = 1;
272 \\ if (x != 1) @breakpoint();
273 \\ return 0;
274 \\}
275 , "");
276
277 // Switch expression
278 case.addCompareOutput(
279 \\pub export fn main() c_int {
280 \\ var cond: c_int = 0;
281 \\ var a: c_int = switch (cond) {
282 \\ 1 => 1,
283 \\ 2 => 2,
284 \\ 99...300, 12 => 3,
285 \\ 0 => 4,
286 \\ else => 5,
287 \\ };
288 \\ return a - 4;
289 \\}
290 , "");
291
292 // Switch expression missing else case.
293 case.addError(
294 \\pub export fn main() c_int {
295 \\ var cond: c_int = 0;
296 \\ const a: c_int = switch (cond) {
297 \\ 1 => 1,
298 \\ 2 => 2,
299 \\ 3 => 3,
300 \\ 4 => 4,
301 \\ };
302 \\ return a - 4;
303 \\}
304 , &.{":3:22: error: switch must handle all possibilities"});
305
306 // Switch expression, has an unreachable prong.
307 case.addCompareOutput(
308 \\pub export fn main() c_int {
309 \\ var cond: c_int = 0;
310 \\ const a: c_int = switch (cond) {
311 \\ 1 => 1,
312 \\ 2 => 2,
313 \\ 99...300, 12 => 3,
314 \\ 0 => 4,
315 \\ 13 => unreachable,
316 \\ else => 5,
317 \\ };
318 \\ return a - 4;
319 \\}
320 , "");
321
322 // Switch expression, has an unreachable prong and prongs write
323 // to result locations.
324 case.addCompareOutput(
325 \\pub export fn main() c_int {
326 \\ var cond: c_int = 0;
327 \\ var a: c_int = switch (cond) {
328 \\ 1 => 1,
329 \\ 2 => 2,
330 \\ 99...300, 12 => 3,
331 \\ 0 => 4,
332 \\ 13 => unreachable,
333 \\ else => 5,
334 \\ };
335 \\ return a - 4;
336 \\}
337 , "");
338
339 // Integer switch expression has duplicate case value.
340 case.addError(
341 \\pub export fn main() c_int {
342 \\ var cond: c_int = 0;
343 \\ const a: c_int = switch (cond) {
344 \\ 1 => 1,
345 \\ 2 => 2,
346 \\ 96, 11...13, 97 => 3,
347 \\ 0 => 4,
348 \\ 90, 12 => 100,
349 \\ else => 5,
350 \\ };
351 \\ return a - 4;
352 \\}
353 , &.{
354 ":8:13: error: duplicate switch value",
355 ":6:15: note: previous value here",
356 });
357
358 // Boolean switch expression has duplicate case value.
359 case.addError(
360 \\pub export fn main() c_int {
361 \\ var a: bool = false;
362 \\ const b: c_int = switch (a) {
363 \\ false => 1,
364 \\ true => 2,
365 \\ false => 3,
366 \\ };
367 \\ _ = b;
368 \\}
369 , &.{
370 ":6:9: error: duplicate switch value",
371 });
372
373 // Sparse (no range capable) switch expression has duplicate case value.
374 case.addError(
375 \\pub export fn main() c_int {
376 \\ const A: type = i32;
377 \\ const b: c_int = switch (A) {
378 \\ i32 => 1,
379 \\ bool => 2,
380 \\ f64, i32 => 3,
381 \\ else => 4,
382 \\ };
383 \\ _ = b;
384 \\}
385 , &.{
386 ":6:14: error: duplicate switch value",
387 ":4:9: note: previous value here",
388 });
389
390 // Ranges not allowed for some kinds of switches.
391 case.addError(
392 \\pub export fn main() c_int {
393 \\ const A: type = i32;
394 \\ const b: c_int = switch (A) {
395 \\ i32 => 1,
396 \\ bool => 2,
397 \\ f16...f64 => 3,
398 \\ else => 4,
399 \\ };
400 \\ _ = b;
401 \\}
402 , &.{
403 ":3:30: error: ranges not allowed when switching on type 'type'",
404 ":6:12: note: range here",
405 });
406
407 // Switch expression has unreachable else prong.
408 case.addError(
409 \\pub export fn main() c_int {
410 \\ var a: u2 = 0;
411 \\ const b: i32 = switch (a) {
412 \\ 0 => 10,
413 \\ 1 => 20,
414 \\ 2 => 30,
415 \\ 3 => 40,
416 \\ else => 50,
417 \\ };
418 \\ _ = b;
419 \\}
420 , &.{
421 ":8:14: error: unreachable else prong; all cases already handled",
422 });
423 }
424 //{
425 // var case = ctx.exeFromCompiledC("optionals", .{});
426
427 // // Simple while loop
428 // case.addCompareOutput(
429 // \\pub export fn main() c_int {
430 // \\ var count: c_int = 0;
431 // \\ var opt_ptr: ?*c_int = &count;
432 // \\ while (opt_ptr) |_| : (count += 1) {
433 // \\ if (count == 4) opt_ptr = null;
434 // \\ }
435 // \\ return count - 5;
436 // \\}
437 // , "");
438
439 // // Same with non pointer optionals
440 // case.addCompareOutput(
441 // \\pub export fn main() c_int {
442 // \\ var count: c_int = 0;
443 // \\ var opt_ptr: ?c_int = count;
444 // \\ while (opt_ptr) |_| : (count += 1) {
445 // \\ if (count == 4) opt_ptr = null;
446 // \\ }
447 // \\ return count - 5;
448 // \\}
449 // , "");
450 //}
451
452 {
453 var case = ctx.exeFromCompiledC("errors", .{});
454 case.addCompareOutput(
455 \\pub export fn main() c_int {
456 \\ var e1 = error.Foo;
457 \\ var e2 = error.Bar;
458 \\ assert(e1 != e2);
459 \\ assert(e1 == error.Foo);
460 \\ assert(e2 == error.Bar);
461 \\ return 0;
462 \\}
463 \\fn assert(b: bool) void {
464 \\ if (!b) unreachable;
465 \\}
466 , "");
467 case.addCompareOutput(
468 \\pub export fn main() c_int {
469 \\ var e: anyerror!c_int = 0;
470 \\ const i = e catch 69;
471 \\ return i;
472 \\}
473 , "");
474 case.addCompareOutput(
475 \\pub export fn main() c_int {
476 \\ var e: anyerror!c_int = error.Foo;
477 \\ const i = e catch 69;
478 \\ return 69 - i;
479 \\}
480 , "");
481 case.addCompareOutput(
482 \\const E = error{e};
483 \\const S = struct { x: u32 };
484 \\fn f() E!u32 {
485 \\ const x = (try @as(E!S, S{ .x = 1 })).x;
486 \\ return x;
487 \\}
488 \\pub export fn main() c_int {
489 \\ const x = f() catch @as(u32, 0);
490 \\ if (x != 1) unreachable;
491 \\ return 0;
492 \\}
493 , "");
494 }
495
496 {
497 var case = ctx.exeFromCompiledC("structs", .{});
498 case.addError(
499 \\const Point = struct { x: i32, y: i32 };
500 \\pub export fn main() c_int {
501 \\ var p: Point = .{
502 \\ .y = 24,
503 \\ .x = 12,
504 \\ .y = 24,
505 \\ };
506 \\ return p.y - p.x - p.x;
507 \\}
508 , &.{
509 ":6:10: error: duplicate field",
510 ":4:10: note: other field here",
511 });
512 case.addError(
513 \\const Point = struct { x: i32, y: i32 };
514 \\pub export fn main() c_int {
515 \\ var p: Point = .{
516 \\ .y = 24,
517 \\ };
518 \\ return p.y - p.x - p.x;
519 \\}
520 , &.{
521 ":3:21: error: missing struct field: x",
522 ":1:15: note: struct 'tmp.Point' declared here",
523 });
524 case.addError(
525 \\const Point = struct { x: i32, y: i32 };
526 \\pub export fn main() c_int {
527 \\ var p: Point = .{
528 \\ .x = 12,
529 \\ .y = 24,
530 \\ .z = 48,
531 \\ };
532 \\ return p.y - p.x - p.x;
533 \\}
534 , &.{
535 ":6:10: error: no field named 'z' in struct 'tmp.Point'",
536 ":1:15: note: struct declared here",
537 });
538 case.addCompareOutput(
539 \\const Point = struct { x: i32, y: i32 };
540 \\pub export fn main() c_int {
541 \\ var p: Point = .{
542 \\ .x = 12,
543 \\ .y = 24,
544 \\ };
545 \\ return p.y - p.x - p.x;
546 \\}
547 , "");
548 case.addCompareOutput(
549 \\const Point = struct { x: i32, y: i32, z: i32, a: i32, b: i32 };
550 \\pub export fn main() c_int {
551 \\ var p: Point = .{
552 \\ .x = 18,
553 \\ .y = 24,
554 \\ .z = 1,
555 \\ .a = 2,
556 \\ .b = 3,
557 \\ };
558 \\ return p.y - p.x - p.z - p.a - p.b;
559 \\}
560 , "");
561 }
562
563 {
564 var case = ctx.exeFromCompiledC("unions", .{});
565
566 case.addError(
567 \\const U = union {
568 \\ a: u32,
569 \\ b
570 \\};
571 , &.{
572 ":3:5: error: union field missing type",
573 });
574
575 case.addError(
576 \\const E = enum { a, b };
577 \\const U = union(E) {
578 \\ a: u32 = 1,
579 \\ b: f32 = 2,
580 \\};
581 , &.{
582 ":2:11: error: explicitly valued tagged union requires inferred enum tag type",
583 ":3:14: note: tag value specified here",
584 });
585
586 case.addError(
587 \\const U = union(enum) {
588 \\ a: u32 = 1,
589 \\ b: f32 = 2,
590 \\};
591 , &.{
592 ":1:11: error: explicitly valued tagged union missing integer tag type",
593 ":2:14: note: tag value specified here",
594 });
595 }
596
597 {
598 var case = ctx.exeFromCompiledC("enums", .{});
599
600 case.addError(
601 \\const E1 = packed enum { a, b, c };
602 \\const E2 = extern enum { a, b, c };
603 \\export fn foo() void {
604 \\ _ = E1.a;
605 \\}
606 \\export fn bar() void {
607 \\ _ = E2.a;
608 \\}
609 , &.{
610 ":1:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
611 ":2:12: error: enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type",
612 });
613
614 // comptime and types are caught in AstGen.
615 case.addError(
616 \\const E1 = enum {
617 \\ a,
618 \\ comptime b,
619 \\ c,
620 \\};
621 \\const E2 = enum {
622 \\ a,
623 \\ b: i32,
624 \\ c,
625 \\};
626 \\export fn foo() void {
627 \\ _ = E1.a;
628 \\}
629 \\export fn bar() void {
630 \\ _ = E2.a;
631 \\}
632 , &.{
633 ":3:5: error: enum fields cannot be marked comptime",
634 ":8:8: error: enum fields do not have types",
635 ":6:12: note: consider 'union(enum)' here to make it a tagged union",
636 });
637
638 // @enumToInt, @intToEnum, enum literal coercion, field access syntax, comparison, switch
639 case.addCompareOutput(
640 \\const Number = enum { One, Two, Three };
641 \\
642 \\pub export fn main() c_int {
643 \\ var number1 = Number.One;
644 \\ var number2: Number = .Two;
645 \\ const number3 = @intToEnum(Number, 2);
646 \\ if (number1 == number2) return 1;
647 \\ if (number2 == number3) return 1;
648 \\ if (@enumToInt(number1) != 0) return 1;
649 \\ if (@enumToInt(number2) != 1) return 1;
650 \\ if (@enumToInt(number3) != 2) return 1;
651 \\ var x: Number = .Two;
652 \\ if (number2 != x) return 1;
653 \\ switch (x) {
654 \\ .One => return 1,
655 \\ .Two => return 0,
656 \\ number3 => return 2,
657 \\ }
658 \\}
659 , "");
660
661 // Specifying alignment is a parse error.
662 // This also tests going from a successful build to a parse error.
663 case.addError(
664 \\const E1 = enum {
665 \\ a,
666 \\ b align(4),
667 \\ c,
668 \\};
669 \\export fn foo() void {
670 \\ _ = E1.a;
671 \\}
672 , &.{
673 ":3:13: error: enum fields cannot be aligned",
674 });
675
676 // Redundant non-exhaustive enum mark.
677 // This also tests going from a parse error to an AstGen error.
678 case.addError(
679 \\const E1 = enum {
680 \\ a,
681 \\ _,
682 \\ b,
683 \\ c,
684 \\ _,
685 \\};
686 \\export fn foo() void {
687 \\ _ = E1.a;
688 \\}
689 , &.{
690 ":6:5: error: redundant non-exhaustive enum mark",
691 ":3:5: note: other mark here",
692 });
693
694 case.addError(
695 \\const E1 = enum {
696 \\ a,
697 \\ b,
698 \\ c,
699 \\ _ = 10,
700 \\};
701 \\export fn foo() void {
702 \\ _ = E1.a;
703 \\}
704 , &.{
705 ":5:9: error: '_' is used to mark an enum as non-exhaustive and cannot be assigned a value",
706 });
707
708 case.addError(
709 \\const E1 = enum { a, b, _ };
710 \\export fn foo() void {
711 \\ _ = E1.a;
712 \\}
713 , &.{
714 ":1:12: error: non-exhaustive enum missing integer tag type",
715 ":1:25: note: marked non-exhaustive here",
716 });
717
718 case.addError(
719 \\const E1 = enum { a, b, c, b, d };
720 \\pub export fn main() c_int {
721 \\ _ = E1.a;
722 \\}
723 , &.{
724 ":1:28: error: duplicate enum field 'b'",
725 ":1:22: note: other field here",
726 });
727
728 case.addError(
729 \\pub export fn main() c_int {
730 \\ const a = true;
731 \\ _ = @enumToInt(a);
732 \\}
733 , &.{
734 ":3:20: error: expected enum or tagged union, found 'bool'",
735 });
736
737 case.addError(
738 \\pub export fn main() c_int {
739 \\ const a = 1;
740 \\ _ = @intToEnum(bool, a);
741 \\}
742 , &.{
743 ":3:20: error: expected enum, found 'bool'",
744 });
745
746 case.addError(
747 \\const E = enum { a, b, c };
748 \\pub export fn main() c_int {
749 \\ _ = @intToEnum(E, 3);
750 \\}
751 , &.{
752 ":3:9: error: enum 'tmp.E' has no tag with value '3'",
753 ":1:11: note: enum declared here",
754 });
755
756 case.addError(
757 \\const E = enum { a, b, c };
758 \\pub export fn main() c_int {
759 \\ var x: E = .a;
760 \\ switch (x) {
761 \\ .a => {},
762 \\ .c => {},
763 \\ }
764 \\}
765 , &.{
766 ":4:5: error: switch must handle all possibilities",
767 ":1:21: note: unhandled enumeration value: 'b'",
768 ":1:11: note: enum 'tmp.E' declared here",
769 });
770
771 case.addError(
772 \\const E = enum { a, b, c };
773 \\pub export fn main() c_int {
774 \\ var x: E = .a;
775 \\ switch (x) {
776 \\ .a => {},
777 \\ .b => {},
778 \\ .b => {},
779 \\ .c => {},
780 \\ }
781 \\}
782 , &.{
783 ":7:10: error: duplicate switch value",
784 ":6:10: note: previous value here",
785 });
786
787 case.addError(
788 \\const E = enum { a, b, c };
789 \\pub export fn main() c_int {
790 \\ var x: E = .a;
791 \\ switch (x) {
792 \\ .a => {},
793 \\ .b => {},
794 \\ .c => {},
795 \\ else => {},
796 \\ }
797 \\}
798 , &.{
799 ":8:14: error: unreachable else prong; all cases already handled",
800 });
801
802 case.addError(
803 \\const E = enum { a, b, c };
804 \\pub export fn main() c_int {
805 \\ var x: E = .a;
806 \\ switch (x) {
807 \\ .a => {},
808 \\ .b => {},
809 \\ _ => {},
810 \\ }
811 \\}
812 , &.{
813 ":4:5: error: '_' prong only allowed when switching on non-exhaustive enums",
814 ":7:11: note: '_' prong here",
815 });
816
817 case.addError(
818 \\const E = enum { a, b, c };
819 \\pub export fn main() c_int {
820 \\ _ = E.d;
821 \\}
822 , &.{
823 ":3:11: error: enum 'tmp.E' has no member named 'd'",
824 ":1:11: note: enum declared here",
825 });
826
827 case.addError(
828 \\const E = enum { a, b, c };
829 \\pub export fn main() c_int {
830 \\ var x: E = .d;
831 \\ _ = x;
832 \\}
833 , &.{
834 ":3:17: error: no field named 'd' in enum 'tmp.E'",
835 ":1:11: note: enum declared here",
836 });
837 }
838
839 {
840 var case = ctx.exeFromCompiledC("shift right + left", .{});
841 case.addCompareOutput(
842 \\pub export fn main() c_int {
843 \\ var i: u32 = 16;
844 \\ assert(i >> 1, 8);
845 \\ return 0;
846 \\}
847 \\fn assert(a: u32, b: u32) void {
848 \\ if (a != b) unreachable;
849 \\}
850 , "");
851
852 case.addCompareOutput(
853 \\pub export fn main() c_int {
854 \\ var i: u32 = 16;
855 \\ assert(i << 1, 32);
856 \\ return 0;
857 \\}
858 \\fn assert(a: u32, b: u32) void {
859 \\ if (a != b) unreachable;
860 \\}
861 , "");
862 }
863
864 {
865 var case = ctx.exeFromCompiledC("inferred error sets", .{});
866
867 case.addCompareOutput(
868 \\pub export fn main() c_int {
869 \\ if (foo()) |_| {
870 \\ @panic("test fail");
871 \\ } else |err| {
872 \\ if (err != error.ItBroke) {
873 \\ @panic("test fail");
874 \\ }
875 \\ }
876 \\ return 0;
877 \\}
878 \\fn foo() !void {
879 \\ return error.ItBroke;
880 \\}
881 , "");
882 }
883
884 {
885 // TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64)
886 var case = ctx.exeFromCompiledC("add/sub wrapping operations", .{});
887 case.addCompareOutput(
888 \\pub export fn main() c_int {
889 \\ // Addition
890 \\ if (!add_u3(1, 1, 2)) return 1;
891 \\ if (!add_u3(7, 1, 0)) return 1;
892 \\ if (!add_i3(1, 1, 2)) return 1;
893 \\ if (!add_i3(3, 2, -3)) return 1;
894 \\ if (!add_i3(-3, -2, 3)) return 1;
895 \\ if (!add_c_int(1, 1, 2)) return 1;
896 \\ // TODO enable these when stage2 supports std.math.maxInt
897 \\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1;
898 \\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1;
899 \\
900 \\ // Subtraction
901 \\ if (!sub_u3(2, 1, 1)) return 1;
902 \\ if (!sub_u3(0, 1, 7)) return 1;
903 \\ if (!sub_i3(2, 1, 1)) return 1;
904 \\ if (!sub_i3(3, -2, -3)) return 1;
905 \\ if (!sub_i3(-3, 2, 3)) return 1;
906 \\ if (!sub_c_int(2, 1, 1)) return 1;
907 \\ // TODO enable these when stage2 supports std.math.maxInt
908 \\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1;
909 \\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1;
910 \\
911 \\ return 0;
912 \\}
913 \\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool {
914 \\ return expected == lhs +% rhs;
915 \\}
916 \\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool {
917 \\ return expected == lhs +% rhs;
918 \\}
919 \\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
920 \\ return expected == lhs +% rhs;
921 \\}
922 \\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool {
923 \\ return expected == lhs -% rhs;
924 \\}
925 \\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool {
926 \\ return expected == lhs -% rhs;
927 \\}
928 \\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
929 \\ return expected == lhs -% rhs;
930 \\}
931 , "");
932 }
933
934 {
935 var case = ctx.exeFromCompiledC("@rem", linux_x64);
936 case.addCompareOutput(
937 \\fn assert(ok: bool) void {
938 \\ if (!ok) unreachable;
939 \\}
940 \\fn rem(lhs: i32, rhs: i32, expected: i32) bool {
941 \\ return @rem(lhs, rhs) == expected;
942 \\}
943 \\pub export fn main() c_int {
944 \\ assert(rem(-5, 3, -2));
945 \\ assert(rem(5, 3, 2));
946 \\ return 0;
947 \\}
948 , "");
949 }
950
951 ctx.h("simple header", linux_x64,
952 \\export fn start() void{}
953 ,
954 \\zig_extern void start(void);
955 \\
956 );
957 ctx.h("header with single param function", linux_x64,
958 \\export fn start(a: u8) void{
959 \\ _ = a;
960 \\}
961 ,
962 \\zig_extern void start(uint8_t const a0);
963 \\
964 );
965 ctx.h("header with multiple param function", linux_x64,
966 \\export fn start(a: u8, b: u8, c: u8) void{
967 \\ _ = a; _ = b; _ = c;
968 \\}
969 ,
970 \\zig_extern void start(uint8_t const a0, uint8_t const a1, uint8_t const a2);
971 \\
972 );
973 ctx.h("header with u32 param function", linux_x64,
974 \\export fn start(a: u32) void{ _ = a; }
975 ,
976 \\zig_extern void start(uint32_t const a0);
977 \\
978 );
979 ctx.h("header with usize param function", linux_x64,
980 \\export fn start(a: usize) void{ _ = a; }
981 ,
982 \\zig_extern void start(uintptr_t const a0);
983 \\
984 );
985 ctx.h("header with bool param function", linux_x64,
986 \\export fn start(a: bool) void{_ = a;}
987 ,
988 \\zig_extern void start(bool const a0);
989 \\
990 );
991 ctx.h("header with noreturn function", linux_x64,
992 \\export fn start() noreturn {
993 \\ unreachable;
994 \\}
995 ,
996 \\zig_extern zig_noreturn void start(void);
997 \\
998 );
999 ctx.h("header with multiple functions", linux_x64,
1000 \\export fn a() void{}
1001 \\export fn b() void{}
1002 \\export fn c() void{}
1003 ,
1004 \\zig_extern void a(void);
1005 \\zig_extern void b(void);
1006 \\zig_extern void c(void);
1007 \\
1008 );
1009 ctx.h("header with multiple includes", linux_x64,
1010 \\export fn start(a: u32, b: usize) void{ _ = a; _ = b; }
1011 ,
1012 \\zig_extern void start(uint32_t const a0, uintptr_t const a1);
1013 \\
1014 );
1015}
test/stage2/nvptx.zig deleted-107
......@@ -1,107 +0,0 @@
1const std = @import("std");
2const TestContext = @import("../../src/test.zig").TestContext;
3
4pub fn addCases(ctx: *TestContext) !void {
5 {
6 var case = addPtx(ctx, "nvptx: simple addition and subtraction");
7
8 case.compiles(
9 \\fn add(a: i32, b: i32) i32 {
10 \\ return a + b;
11 \\}
12 \\
13 \\pub export fn add_and_substract(a: i32, out: *i32) callconv(.PtxKernel) void {
14 \\ const x = add(a, 7);
15 \\ var y = add(2, 0);
16 \\ y -= x;
17 \\ out.* = y;
18 \\}
19 );
20 }
21
22 {
23 var case = addPtx(ctx, "nvptx: read special registers");
24
25 case.compiles(
26 \\fn threadIdX() u32 {
27 \\ return asm ("mov.u32 \t%[r], %tid.x;"
28 \\ : [r] "=r" (-> u32),
29 \\ );
30 \\}
31 \\
32 \\pub export fn special_reg(a: []const i32, out: []i32) callconv(.PtxKernel) void {
33 \\ const i = threadIdX();
34 \\ out[i] = a[i] + 7;
35 \\}
36 );
37 }
38
39 {
40 var case = addPtx(ctx, "nvptx: address spaces");
41
42 case.compiles(
43 \\var x: i32 addrspace(.global) = 0;
44 \\
45 \\pub export fn increment(out: *i32) callconv(.PtxKernel) void {
46 \\ x += 1;
47 \\ out.* = x;
48 \\}
49 );
50 }
51
52 {
53 var case = addPtx(ctx, "nvptx: reduce in shared mem");
54 case.compiles(
55 \\fn threadIdX() u32 {
56 \\ return asm ("mov.u32 \t%[r], %tid.x;"
57 \\ : [r] "=r" (-> u32),
58 \\ );
59 \\}
60 \\
61 \\ var _sdata: [1024]f32 addrspace(.shared) = undefined;
62 \\ pub export fn reduceSum(d_x: []const f32, out: *f32) callconv(.PtxKernel) void {
63 \\ var sdata = @addrSpaceCast(.generic, &_sdata);
64 \\ const tid: u32 = threadIdX();
65 \\ var sum = d_x[tid];
66 \\ sdata[tid] = sum;
67 \\ asm volatile ("bar.sync \t0;");
68 \\ var s: u32 = 512;
69 \\ while (s > 0) : (s = s >> 1) {
70 \\ if (tid < s) {
71 \\ sum += sdata[tid + s];
72 \\ sdata[tid] = sum;
73 \\ }
74 \\ asm volatile ("bar.sync \t0;");
75 \\ }
76 \\
77 \\ if (tid == 0) {
78 \\ out.* = sum;
79 \\ }
80 \\ }
81 );
82 }
83}
84
85const nvptx_target = std.zig.CrossTarget{
86 .cpu_arch = .nvptx64,
87 .os_tag = .cuda,
88};
89
90pub fn addPtx(
91 ctx: *TestContext,
92 name: []const u8,
93) *TestContext.Case {
94 ctx.cases.append(TestContext.Case{
95 .name = name,
96 .target = nvptx_target,
97 .updates = std.ArrayList(TestContext.Update).init(ctx.cases.allocator),
98 .output_mode = .Obj,
99 .files = std.ArrayList(TestContext.File).init(ctx.cases.allocator),
100 .deps = std.ArrayList(TestContext.DepModule).init(ctx.cases.allocator),
101 .link_libc = false,
102 .backend = .llvm,
103 // Bug in Debug mode
104 .optimize_mode = .ReleaseSafe,
105 }) catch @panic("out of memory");
106 return &ctx.cases.items[ctx.cases.items.len - 1];
107}
test/standalone.zig+208-107
......@@ -1,117 +1,218 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const tests = @import("tests.zig");
1pub const SimpleCase = struct {
2 src_path: []const u8,
3 link_libc: bool = false,
4 all_modes: bool = false,
5 target: std.zig.CrossTarget = .{},
6 is_test: bool = false,
7 is_exe: bool = true,
8 /// Run only on this OS.
9 os_filter: ?std.Target.Os.Tag = null,
10};
411
5pub fn addCases(cases: *tests.StandaloneContext) void {
6 cases.add("test/standalone/hello_world/hello.zig");
7 cases.addC("test/standalone/hello_world/hello_libc.zig");
12pub const BuildCase = struct {
13 build_root: []const u8,
14 import: type,
15};
816
9 cases.addBuildFile("test/standalone/options/build.zig", .{
10 .extra_argv = &.{
11 "-Dbool_true",
12 "-Dbool_false=false",
13 "-Dint=1234",
14 "-De=two",
15 "-Dstring=hello",
17pub const simple_cases = [_]SimpleCase{
18 .{
19 .src_path = "test/standalone/hello_world/hello.zig",
20 .all_modes = true,
21 },
22 .{
23 .src_path = "test/standalone/hello_world/hello_libc.zig",
24 .link_libc = true,
25 .all_modes = true,
26 },
27 .{
28 .src_path = "test/standalone/cat/main.zig",
29 },
30 // https://github.com/ziglang/zig/issues/6025
31 //.{
32 // .src_path = "test/standalone/issue_9693/main.zig",
33 //},
34 .{
35 .src_path = "test/standalone/brace_expansion.zig",
36 .is_test = true,
37 },
38 .{
39 .src_path = "test/standalone/issue_7030.zig",
40 .target = .{
41 .cpu_arch = .wasm32,
42 .os_tag = .freestanding,
1643 },
17 });
18
19 cases.add("test/standalone/cat/main.zig");
20 if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/6025
21 cases.add("test/standalone/issue_9693/main.zig");
22 }
23 cases.add("test/standalone/issue_12471/main.zig");
24 cases.add("test/standalone/guess_number/main.zig");
25 cases.add("test/standalone/main_return_error/error_u8.zig");
26 cases.add("test/standalone/main_return_error/error_u8_non_zero.zig");
27 cases.add("test/standalone/noreturn_call/inline.zig");
28 cases.add("test/standalone/noreturn_call/as_arg.zig");
29 cases.addBuildFile("test/standalone/test_runner_path/build.zig", .{ .requires_stage2 = true });
30 cases.addBuildFile("test/standalone/issue_13970/build.zig", .{});
31 cases.addBuildFile("test/standalone/main_pkg_path/build.zig", .{});
32 cases.addBuildFile("test/standalone/shared_library/build.zig", .{});
33 cases.addBuildFile("test/standalone/mix_o_files/build.zig", .{});
34 cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{
35 .build_modes = true,
36 .cross_targets = true,
37 });
38 cases.addBuildFile("test/standalone/global_linkage/build.zig", .{});
39 cases.addBuildFile("test/standalone/static_c_lib/build.zig", .{});
40 cases.addBuildFile("test/standalone/issue_339/build.zig", .{});
41 cases.addBuildFile("test/standalone/issue_8550/build.zig", .{});
42 cases.addBuildFile("test/standalone/issue_794/build.zig", .{});
43 cases.addBuildFile("test/standalone/issue_5825/build.zig", .{});
44 cases.addBuildFile("test/standalone/pkg_import/build.zig", .{});
45 cases.addBuildFile("test/standalone/use_alias/build.zig", .{});
46 cases.addBuildFile("test/standalone/brace_expansion/build.zig", .{});
47 if (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64) {
48 // https://github.com/ziglang/zig/issues/13685
49 cases.addBuildFile("test/standalone/empty_env/build.zig", .{});
50 }
51 cases.addBuildFile("test/standalone/issue_7030/build.zig", .{});
52 cases.addBuildFile("test/standalone/install_raw_hex/build.zig", .{});
53 if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/12194
54 cases.addBuildFile("test/standalone/issue_9812/build.zig", .{});
55 }
56 if (builtin.os.tag != .windows) {
57 // https://github.com/ziglang/zig/issues/12419
58 cases.addBuildFile("test/standalone/issue_11595/build.zig", .{});
59 }
60
61 if (builtin.os.tag != .wasi and
62 // https://github.com/ziglang/zig/issues/13550
63 (builtin.os.tag != .macos or builtin.cpu.arch != .aarch64) and
64 // https://github.com/ziglang/zig/issues/13686
65 (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64))
66 {
67 cases.addBuildFile("test/standalone/load_dynamic_library/build.zig", .{});
68 }
44 },
6945
70 if (builtin.os.tag == .windows) {
71 cases.addBuildFile("test/standalone/windows_spawn/build.zig", .{});
72 }
46 .{ .src_path = "test/standalone/issue_12471/main.zig" },
47 .{ .src_path = "test/standalone/guess_number/main.zig" },
48 .{ .src_path = "test/standalone/main_return_error/error_u8.zig" },
49 .{ .src_path = "test/standalone/main_return_error/error_u8_non_zero.zig" },
50 .{ .src_path = "test/standalone/noreturn_call/inline.zig" },
51 .{ .src_path = "test/standalone/noreturn_call/as_arg.zig" },
7352
74 cases.addBuildFile("test/standalone/c_compiler/build.zig", .{
75 .build_modes = true,
76 .cross_targets = true,
77 });
78
79 if (builtin.os.tag == .windows) {
80 cases.addC("test/standalone/issue_9402/main.zig");
81 }
82 // Try to build and run a PIE executable.
83 if (builtin.os.tag == .linux) {
84 cases.addBuildFile("test/standalone/pie/build.zig", .{});
85 }
86 cases.addBuildFile("test/standalone/issue_12706/build.zig", .{});
87 if (std.os.have_sigpipe_support) {
88 cases.addBuildFile("test/standalone/sigpipe/build.zig", .{});
89 }
53 .{
54 .src_path = "test/standalone/issue_9402/main.zig",
55 .os_filter = .windows,
56 .link_libc = true,
57 },
9058
9159 // Ensure the development tools are buildable. Alphabetically sorted.
9260 // No need to build `tools/spirv/grammar.zig`.
93 cases.add("tools/extract-grammar.zig");
94 cases.add("tools/gen_outline_atomics.zig");
95 cases.add("tools/gen_spirv_spec.zig");
96 cases.add("tools/gen_stubs.zig");
97 cases.add("tools/generate_linux_syscalls.zig");
98 cases.add("tools/process_headers.zig");
99 cases.add("tools/update-license-headers.zig");
100 cases.add("tools/update-linux-headers.zig");
101 cases.add("tools/update_clang_options.zig");
102 cases.add("tools/update_cpu_features.zig");
103 cases.add("tools/update_glibc.zig");
104 cases.add("tools/update_spirv_features.zig");
61 .{ .src_path = "tools/extract-grammar.zig" },
62 .{ .src_path = "tools/gen_outline_atomics.zig" },
63 .{ .src_path = "tools/gen_spirv_spec.zig" },
64 .{ .src_path = "tools/gen_stubs.zig" },
65 .{ .src_path = "tools/generate_linux_syscalls.zig" },
66 .{ .src_path = "tools/process_headers.zig" },
67 .{ .src_path = "tools/update-license-headers.zig" },
68 .{ .src_path = "tools/update-linux-headers.zig" },
69 .{ .src_path = "tools/update_clang_options.zig" },
70 .{ .src_path = "tools/update_cpu_features.zig" },
71 .{ .src_path = "tools/update_glibc.zig" },
72 .{ .src_path = "tools/update_spirv_features.zig" },
73};
10574
106 cases.addBuildFile("test/standalone/issue_13030/build.zig", .{ .build_modes = true });
107 cases.addBuildFile("test/standalone/emit_asm_and_bin/build.zig", .{});
108 cases.addBuildFile("test/standalone/issue_12588/build.zig", .{});
109 cases.addBuildFile("test/standalone/embed_generated_file/build.zig", .{});
110 cases.addBuildFile("test/standalone/extern/build.zig", .{});
75pub const build_cases = [_]BuildCase{
76 .{
77 .build_root = "test/standalone/test_runner_path",
78 .import = @import("standalone/test_runner_path/build.zig"),
79 },
80 .{
81 .build_root = "test/standalone/issue_13970",
82 .import = @import("standalone/issue_13970/build.zig"),
83 },
84 .{
85 .build_root = "test/standalone/main_pkg_path",
86 .import = @import("standalone/main_pkg_path/build.zig"),
87 },
88 .{
89 .build_root = "test/standalone/shared_library",
90 .import = @import("standalone/shared_library/build.zig"),
91 },
92 .{
93 .build_root = "test/standalone/mix_o_files",
94 .import = @import("standalone/mix_o_files/build.zig"),
95 },
96 .{
97 .build_root = "test/standalone/mix_c_files",
98 .import = @import("standalone/mix_c_files/build.zig"),
99 },
100 .{
101 .build_root = "test/standalone/global_linkage",
102 .import = @import("standalone/global_linkage/build.zig"),
103 },
104 .{
105 .build_root = "test/standalone/static_c_lib",
106 .import = @import("standalone/static_c_lib/build.zig"),
107 },
108 .{
109 .build_root = "test/standalone/issue_339",
110 .import = @import("standalone/issue_339/build.zig"),
111 },
112 .{
113 .build_root = "test/standalone/issue_8550",
114 .import = @import("standalone/issue_8550/build.zig"),
115 },
116 .{
117 .build_root = "test/standalone/issue_794",
118 .import = @import("standalone/issue_794/build.zig"),
119 },
120 .{
121 .build_root = "test/standalone/issue_5825",
122 .import = @import("standalone/issue_5825/build.zig"),
123 },
124 .{
125 .build_root = "test/standalone/pkg_import",
126 .import = @import("standalone/pkg_import/build.zig"),
127 },
128 .{
129 .build_root = "test/standalone/use_alias",
130 .import = @import("standalone/use_alias/build.zig"),
131 },
132 .{
133 .build_root = "test/standalone/install_raw_hex",
134 .import = @import("standalone/install_raw_hex/build.zig"),
135 },
136 // TODO take away EmitOption.emit_to option and make it give a FileSource
137 //.{
138 // .build_root = "test/standalone/emit_asm_and_bin",
139 // .import = @import("standalone/emit_asm_and_bin/build.zig"),
140 //},
141 // TODO take away EmitOption.emit_to option and make it give a FileSource
142 //.{
143 // .build_root = "test/standalone/issue_12588",
144 // .import = @import("standalone/issue_12588/build.zig"),
145 //},
146 .{
147 .build_root = "test/standalone/embed_generated_file",
148 .import = @import("standalone/embed_generated_file/build.zig"),
149 },
150 .{
151 .build_root = "test/standalone/extern",
152 .import = @import("standalone/extern/build.zig"),
153 },
154 .{
155 .build_root = "test/standalone/dep_diamond",
156 .import = @import("standalone/dep_diamond/build.zig"),
157 },
158 .{
159 .build_root = "test/standalone/dep_triangle",
160 .import = @import("standalone/dep_triangle/build.zig"),
161 },
162 .{
163 .build_root = "test/standalone/dep_recursive",
164 .import = @import("standalone/dep_recursive/build.zig"),
165 },
166 .{
167 .build_root = "test/standalone/dep_mutually_recursive",
168 .import = @import("standalone/dep_mutually_recursive/build.zig"),
169 },
170 .{
171 .build_root = "test/standalone/dep_shared_builtin",
172 .import = @import("standalone/dep_shared_builtin/build.zig"),
173 },
174 .{
175 .build_root = "test/standalone/empty_env",
176 .import = @import("standalone/empty_env/build.zig"),
177 },
178 .{
179 .build_root = "test/standalone/issue_9812",
180 .import = @import("standalone/issue_9812/build.zig"),
181 },
182 .{
183 .build_root = "test/standalone/issue_11595",
184 .import = @import("standalone/issue_11595/build.zig"),
185 },
186 .{
187 .build_root = "test/standalone/load_dynamic_library",
188 .import = @import("standalone/load_dynamic_library/build.zig"),
189 },
190 .{
191 .build_root = "test/standalone/windows_spawn",
192 .import = @import("standalone/windows_spawn/build.zig"),
193 },
194 .{
195 .build_root = "test/standalone/c_compiler",
196 .import = @import("standalone/c_compiler/build.zig"),
197 },
198 .{
199 .build_root = "test/standalone/pie",
200 .import = @import("standalone/pie/build.zig"),
201 },
202 .{
203 .build_root = "test/standalone/issue_12706",
204 .import = @import("standalone/issue_12706/build.zig"),
205 },
206 // TODO This test is disabled for doing naughty things in the build script.
207 // The logic needs to get moved to a child process instead of build.zig.
208 //.{
209 // .build_root = "test/standalone/sigpipe",
210 // .import = @import("standalone/sigpipe/build.zig"),
211 //},
212 .{
213 .build_root = "test/standalone/issue_13030",
214 .import = @import("standalone/issue_13030/build.zig"),
215 },
216};
111217
112 cases.addBuildFile("test/standalone/dep_diamond/build.zig", .{});
113 cases.addBuildFile("test/standalone/dep_triangle/build.zig", .{});
114 cases.addBuildFile("test/standalone/dep_recursive/build.zig", .{});
115 cases.addBuildFile("test/standalone/dep_mutually_recursive/build.zig", .{});
116 cases.addBuildFile("test/standalone/dep_shared_builtin/build.zig", .{});
117}
218const std = @import("std");
test/standalone/brace_expansion.zig created+292
......@@ -0,0 +1,292 @@
1const std = @import("std");
2const io = std.io;
3const mem = std.mem;
4const debug = std.debug;
5const assert = debug.assert;
6const testing = std.testing;
7const ArrayList = std.ArrayList;
8const maxInt = std.math.maxInt;
9
10const Token = union(enum) {
11 Word: []const u8,
12 OpenBrace,
13 CloseBrace,
14 Comma,
15 Eof,
16};
17
18var gpa = std.heap.GeneralPurposeAllocator(.{}){};
19var global_allocator = gpa.allocator();
20
21fn tokenize(input: []const u8) !ArrayList(Token) {
22 const State = enum {
23 Start,
24 Word,
25 };
26
27 var token_list = ArrayList(Token).init(global_allocator);
28 errdefer token_list.deinit();
29 var tok_begin: usize = undefined;
30 var state = State.Start;
31
32 for (input, 0..) |b, i| {
33 switch (state) {
34 .Start => switch (b) {
35 'a'...'z', 'A'...'Z' => {
36 state = State.Word;
37 tok_begin = i;
38 },
39 '{' => try token_list.append(Token.OpenBrace),
40 '}' => try token_list.append(Token.CloseBrace),
41 ',' => try token_list.append(Token.Comma),
42 else => return error.InvalidInput,
43 },
44 .Word => switch (b) {
45 'a'...'z', 'A'...'Z' => {},
46 '{', '}', ',' => {
47 try token_list.append(Token{ .Word = input[tok_begin..i] });
48 switch (b) {
49 '{' => try token_list.append(Token.OpenBrace),
50 '}' => try token_list.append(Token.CloseBrace),
51 ',' => try token_list.append(Token.Comma),
52 else => unreachable,
53 }
54 state = State.Start;
55 },
56 else => return error.InvalidInput,
57 },
58 }
59 }
60 switch (state) {
61 State.Start => {},
62 State.Word => try token_list.append(Token{ .Word = input[tok_begin..] }),
63 }
64 try token_list.append(Token.Eof);
65 return token_list;
66}
67
68const Node = union(enum) {
69 Scalar: []const u8,
70 List: ArrayList(Node),
71 Combine: []Node,
72
73 fn deinit(self: Node) void {
74 switch (self) {
75 .Scalar => {},
76 .Combine => |pair| {
77 pair[0].deinit();
78 pair[1].deinit();
79 global_allocator.free(pair);
80 },
81 .List => |list| {
82 for (list.items) |item| {
83 item.deinit();
84 }
85 list.deinit();
86 },
87 }
88 }
89};
90
91const ParseError = error{
92 InvalidInput,
93 OutOfMemory,
94};
95
96fn parse(tokens: *const ArrayList(Token), token_index: *usize) ParseError!Node {
97 const first_token = tokens.items[token_index.*];
98 token_index.* += 1;
99
100 const result_node = switch (first_token) {
101 .Word => |word| Node{ .Scalar = word },
102 .OpenBrace => blk: {
103 var list = ArrayList(Node).init(global_allocator);
104 errdefer {
105 for (list.items) |node| node.deinit();
106 list.deinit();
107 }
108 while (true) {
109 try list.append(try parse(tokens, token_index));
110
111 const token = tokens.items[token_index.*];
112 token_index.* += 1;
113
114 switch (token) {
115 .CloseBrace => break,
116 .Comma => continue,
117 else => return error.InvalidInput,
118 }
119 }
120 break :blk Node{ .List = list };
121 },
122 else => return error.InvalidInput,
123 };
124
125 switch (tokens.items[token_index.*]) {
126 .Word, .OpenBrace => {
127 const pair = try global_allocator.alloc(Node, 2);
128 errdefer global_allocator.free(pair);
129 pair[0] = result_node;
130 pair[1] = try parse(tokens, token_index);
131 return Node{ .Combine = pair };
132 },
133 else => return result_node,
134 }
135}
136
137fn expandString(input: []const u8, output: *ArrayList(u8)) !void {
138 const tokens = try tokenize(input);
139 defer tokens.deinit();
140 if (tokens.items.len == 1) {
141 return output.resize(0);
142 }
143
144 var token_index: usize = 0;
145 const root = try parse(&tokens, &token_index);
146 defer root.deinit();
147 const last_token = tokens.items[token_index];
148 switch (last_token) {
149 Token.Eof => {},
150 else => return error.InvalidInput,
151 }
152
153 var result_list = ArrayList(ArrayList(u8)).init(global_allocator);
154 defer {
155 for (result_list.items) |*buf| buf.deinit();
156 result_list.deinit();
157 }
158
159 try expandNode(root, &result_list);
160
161 try output.resize(0);
162 for (result_list.items, 0..) |buf, i| {
163 if (i != 0) {
164 try output.append(' ');
165 }
166 try output.appendSlice(buf.items);
167 }
168}
169
170const ExpandNodeError = error{OutOfMemory};
171
172fn expandNode(node: Node, output: *ArrayList(ArrayList(u8))) ExpandNodeError!void {
173 assert(output.items.len == 0);
174 switch (node) {
175 .Scalar => |scalar| {
176 var list = ArrayList(u8).init(global_allocator);
177 errdefer list.deinit();
178 try list.appendSlice(scalar);
179 try output.append(list);
180 },
181 .Combine => |pair| {
182 const a_node = pair[0];
183 const b_node = pair[1];
184
185 var child_list_a = ArrayList(ArrayList(u8)).init(global_allocator);
186 defer {
187 for (child_list_a.items) |*buf| buf.deinit();
188 child_list_a.deinit();
189 }
190 try expandNode(a_node, &child_list_a);
191
192 var child_list_b = ArrayList(ArrayList(u8)).init(global_allocator);
193 defer {
194 for (child_list_b.items) |*buf| buf.deinit();
195 child_list_b.deinit();
196 }
197 try expandNode(b_node, &child_list_b);
198
199 for (child_list_a.items) |buf_a| {
200 for (child_list_b.items) |buf_b| {
201 var combined_buf = ArrayList(u8).init(global_allocator);
202 errdefer combined_buf.deinit();
203
204 try combined_buf.appendSlice(buf_a.items);
205 try combined_buf.appendSlice(buf_b.items);
206 try output.append(combined_buf);
207 }
208 }
209 },
210 .List => |list| {
211 for (list.items) |child_node| {
212 var child_list = ArrayList(ArrayList(u8)).init(global_allocator);
213 errdefer for (child_list.items) |*buf| buf.deinit();
214 defer child_list.deinit();
215
216 try expandNode(child_node, &child_list);
217
218 for (child_list.items) |buf| {
219 try output.append(buf);
220 }
221 }
222 },
223 }
224}
225
226pub fn main() !void {
227 defer _ = gpa.deinit();
228 const stdin_file = io.getStdIn();
229 const stdout_file = io.getStdOut();
230
231 const stdin = try stdin_file.reader().readAllAlloc(global_allocator, std.math.maxInt(usize));
232 defer global_allocator.free(stdin);
233
234 var result_buf = ArrayList(u8).init(global_allocator);
235 defer result_buf.deinit();
236
237 try expandString(stdin, &result_buf);
238 try stdout_file.writeAll(result_buf.items);
239}
240
241test "invalid inputs" {
242 global_allocator = std.testing.allocator;
243
244 try expectError("}ABC", error.InvalidInput);
245 try expectError("{ABC", error.InvalidInput);
246 try expectError("}{", error.InvalidInput);
247 try expectError("{}", error.InvalidInput);
248 try expectError("A,B,C", error.InvalidInput);
249 try expectError("{A{B,C}", error.InvalidInput);
250 try expectError("{A,}", error.InvalidInput);
251
252 try expectError("\n", error.InvalidInput);
253}
254
255fn expectError(test_input: []const u8, expected_err: anyerror) !void {
256 var output_buf = ArrayList(u8).init(global_allocator);
257 defer output_buf.deinit();
258
259 try testing.expectError(expected_err, expandString(test_input, &output_buf));
260}
261
262test "valid inputs" {
263 global_allocator = std.testing.allocator;
264
265 try expectExpansion("{x,y,z}", "x y z");
266 try expectExpansion("{A,B}{x,y}", "Ax Ay Bx By");
267 try expectExpansion("{A,B{x,y}}", "A Bx By");
268
269 try expectExpansion("{ABC}", "ABC");
270 try expectExpansion("{A,B,C}", "A B C");
271 try expectExpansion("ABC", "ABC");
272
273 try expectExpansion("", "");
274 try expectExpansion("{A,B}{C,{x,y}}{g,h}", "ACg ACh Axg Axh Ayg Ayh BCg BCh Bxg Bxh Byg Byh");
275 try expectExpansion("{A,B}{C,C{x,y}}{g,h}", "ACg ACh ACxg ACxh ACyg ACyh BCg BCh BCxg BCxh BCyg BCyh");
276 try expectExpansion("{A,B}a", "Aa Ba");
277 try expectExpansion("{C,{x,y}}", "C x y");
278 try expectExpansion("z{C,{x,y}}", "zC zx zy");
279 try expectExpansion("a{b,c{d,e{f,g}}}", "ab acd acef aceg");
280 try expectExpansion("a{x,y}b", "axb ayb");
281 try expectExpansion("z{{a,b}}", "za zb");
282 try expectExpansion("a{b}", "ab");
283}
284
285fn expectExpansion(test_input: []const u8, expected_result: []const u8) !void {
286 var result = ArrayList(u8).init(global_allocator);
287 defer result.deinit();
288
289 expandString(test_input, &result) catch unreachable;
290
291 try testing.expectEqualSlices(u8, expected_result, result.items);
292}
test/standalone/brace_expansion/build.zig deleted-11
......@@ -1,11 +0,0 @@
1const std = @import("std");
2
3pub fn build(b: *std.Build) void {
4 const main = b.addTest(.{
5 .root_source_file = .{ .path = "main.zig" },
6 .optimize = b.standardOptimizeOption(.{}),
7 });
8
9 const test_step = b.step("test", "Test it");
10 test_step.dependOn(&main.step);
11}
test/standalone/brace_expansion/main.zig deleted-292
......@@ -1,292 +0,0 @@
1const std = @import("std");
2const io = std.io;
3const mem = std.mem;
4const debug = std.debug;
5const assert = debug.assert;
6const testing = std.testing;
7const ArrayList = std.ArrayList;
8const maxInt = std.math.maxInt;
9
10const Token = union(enum) {
11 Word: []const u8,
12 OpenBrace,
13 CloseBrace,
14 Comma,
15 Eof,
16};
17
18var gpa = std.heap.GeneralPurposeAllocator(.{}){};
19var global_allocator = gpa.allocator();
20
21fn tokenize(input: []const u8) !ArrayList(Token) {
22 const State = enum {
23 Start,
24 Word,
25 };
26
27 var token_list = ArrayList(Token).init(global_allocator);
28 errdefer token_list.deinit();
29 var tok_begin: usize = undefined;
30 var state = State.Start;
31
32 for (input, 0..) |b, i| {
33 switch (state) {
34 .Start => switch (b) {
35 'a'...'z', 'A'...'Z' => {
36 state = State.Word;
37 tok_begin = i;
38 },
39 '{' => try token_list.append(Token.OpenBrace),
40 '}' => try token_list.append(Token.CloseBrace),
41 ',' => try token_list.append(Token.Comma),
42 else => return error.InvalidInput,
43 },
44 .Word => switch (b) {
45 'a'...'z', 'A'...'Z' => {},
46 '{', '}', ',' => {
47 try token_list.append(Token{ .Word = input[tok_begin..i] });
48 switch (b) {
49 '{' => try token_list.append(Token.OpenBrace),
50 '}' => try token_list.append(Token.CloseBrace),
51 ',' => try token_list.append(Token.Comma),
52 else => unreachable,
53 }
54 state = State.Start;
55 },
56 else => return error.InvalidInput,
57 },
58 }
59 }
60 switch (state) {
61 State.Start => {},
62 State.Word => try token_list.append(Token{ .Word = input[tok_begin..] }),
63 }
64 try token_list.append(Token.Eof);
65 return token_list;
66}
67
68const Node = union(enum) {
69 Scalar: []const u8,
70 List: ArrayList(Node),
71 Combine: []Node,
72
73 fn deinit(self: Node) void {
74 switch (self) {
75 .Scalar => {},
76 .Combine => |pair| {
77 pair[0].deinit();
78 pair[1].deinit();
79 global_allocator.free(pair);
80 },
81 .List => |list| {
82 for (list.items) |item| {
83 item.deinit();
84 }
85 list.deinit();
86 },
87 }
88 }
89};
90
91const ParseError = error{
92 InvalidInput,
93 OutOfMemory,
94};
95
96fn parse(tokens: *const ArrayList(Token), token_index: *usize) ParseError!Node {
97 const first_token = tokens.items[token_index.*];
98 token_index.* += 1;
99
100 const result_node = switch (first_token) {
101 .Word => |word| Node{ .Scalar = word },
102 .OpenBrace => blk: {
103 var list = ArrayList(Node).init(global_allocator);
104 errdefer {
105 for (list.items) |node| node.deinit();
106 list.deinit();
107 }
108 while (true) {
109 try list.append(try parse(tokens, token_index));
110
111 const token = tokens.items[token_index.*];
112 token_index.* += 1;
113
114 switch (token) {
115 .CloseBrace => break,
116 .Comma => continue,
117 else => return error.InvalidInput,
118 }
119 }
120 break :blk Node{ .List = list };
121 },
122 else => return error.InvalidInput,
123 };
124
125 switch (tokens.items[token_index.*]) {
126 .Word, .OpenBrace => {
127 const pair = try global_allocator.alloc(Node, 2);
128 errdefer global_allocator.free(pair);
129 pair[0] = result_node;
130 pair[1] = try parse(tokens, token_index);
131 return Node{ .Combine = pair };
132 },
133 else => return result_node,
134 }
135}
136
137fn expandString(input: []const u8, output: *ArrayList(u8)) !void {
138 const tokens = try tokenize(input);
139 defer tokens.deinit();
140 if (tokens.items.len == 1) {
141 return output.resize(0);
142 }
143
144 var token_index: usize = 0;
145 const root = try parse(&tokens, &token_index);
146 defer root.deinit();
147 const last_token = tokens.items[token_index];
148 switch (last_token) {
149 Token.Eof => {},
150 else => return error.InvalidInput,
151 }
152
153 var result_list = ArrayList(ArrayList(u8)).init(global_allocator);
154 defer {
155 for (result_list.items) |*buf| buf.deinit();
156 result_list.deinit();
157 }
158
159 try expandNode(root, &result_list);
160
161 try output.resize(0);
162 for (result_list.items, 0..) |buf, i| {
163 if (i != 0) {
164 try output.append(' ');
165 }
166 try output.appendSlice(buf.items);
167 }
168}
169
170const ExpandNodeError = error{OutOfMemory};
171
172fn expandNode(node: Node, output: *ArrayList(ArrayList(u8))) ExpandNodeError!void {
173 assert(output.items.len == 0);
174 switch (node) {
175 .Scalar => |scalar| {
176 var list = ArrayList(u8).init(global_allocator);
177 errdefer list.deinit();
178 try list.appendSlice(scalar);
179 try output.append(list);
180 },
181 .Combine => |pair| {
182 const a_node = pair[0];
183 const b_node = pair[1];
184
185 var child_list_a = ArrayList(ArrayList(u8)).init(global_allocator);
186 defer {
187 for (child_list_a.items) |*buf| buf.deinit();
188 child_list_a.deinit();
189 }
190 try expandNode(a_node, &child_list_a);
191
192 var child_list_b = ArrayList(ArrayList(u8)).init(global_allocator);
193 defer {
194 for (child_list_b.items) |*buf| buf.deinit();
195 child_list_b.deinit();
196 }
197 try expandNode(b_node, &child_list_b);
198
199 for (child_list_a.items) |buf_a| {
200 for (child_list_b.items) |buf_b| {
201 var combined_buf = ArrayList(u8).init(global_allocator);
202 errdefer combined_buf.deinit();
203
204 try combined_buf.appendSlice(buf_a.items);
205 try combined_buf.appendSlice(buf_b.items);
206 try output.append(combined_buf);
207 }
208 }
209 },
210 .List => |list| {
211 for (list.items) |child_node| {
212 var child_list = ArrayList(ArrayList(u8)).init(global_allocator);
213 errdefer for (child_list.items) |*buf| buf.deinit();
214 defer child_list.deinit();
215
216 try expandNode(child_node, &child_list);
217
218 for (child_list.items) |buf| {
219 try output.append(buf);
220 }
221 }
222 },
223 }
224}
225
226pub fn main() !void {
227 defer _ = gpa.deinit();
228 const stdin_file = io.getStdIn();
229 const stdout_file = io.getStdOut();
230
231 const stdin = try stdin_file.reader().readAllAlloc(global_allocator, std.math.maxInt(usize));
232 defer global_allocator.free(stdin);
233
234 var result_buf = ArrayList(u8).init(global_allocator);
235 defer result_buf.deinit();
236
237 try expandString(stdin.items, &result_buf);
238 try stdout_file.write(result_buf.items);
239}
240
241test "invalid inputs" {
242 global_allocator = std.testing.allocator;
243
244 try expectError("}ABC", error.InvalidInput);
245 try expectError("{ABC", error.InvalidInput);
246 try expectError("}{", error.InvalidInput);
247 try expectError("{}", error.InvalidInput);
248 try expectError("A,B,C", error.InvalidInput);
249 try expectError("{A{B,C}", error.InvalidInput);
250 try expectError("{A,}", error.InvalidInput);
251
252 try expectError("\n", error.InvalidInput);
253}
254
255fn expectError(test_input: []const u8, expected_err: anyerror) !void {
256 var output_buf = ArrayList(u8).init(global_allocator);
257 defer output_buf.deinit();
258
259 try testing.expectError(expected_err, expandString(test_input, &output_buf));
260}
261
262test "valid inputs" {
263 global_allocator = std.testing.allocator;
264
265 try expectExpansion("{x,y,z}", "x y z");
266 try expectExpansion("{A,B}{x,y}", "Ax Ay Bx By");
267 try expectExpansion("{A,B{x,y}}", "A Bx By");
268
269 try expectExpansion("{ABC}", "ABC");
270 try expectExpansion("{A,B,C}", "A B C");
271 try expectExpansion("ABC", "ABC");
272
273 try expectExpansion("", "");
274 try expectExpansion("{A,B}{C,{x,y}}{g,h}", "ACg ACh Axg Axh Ayg Ayh BCg BCh Bxg Bxh Byg Byh");
275 try expectExpansion("{A,B}{C,C{x,y}}{g,h}", "ACg ACh ACxg ACxh ACyg ACyh BCg BCh BCxg BCxh BCyg BCyh");
276 try expectExpansion("{A,B}a", "Aa Ba");
277 try expectExpansion("{C,{x,y}}", "C x y");
278 try expectExpansion("z{C,{x,y}}", "zC zx zy");
279 try expectExpansion("a{b,c{d,e{f,g}}}", "ab acd acef aceg");
280 try expectExpansion("a{x,y}b", "axb ayb");
281 try expectExpansion("z{{a,b}}", "za zb");
282 try expectExpansion("a{b}", "ab");
283}
284
285fn expectExpansion(test_input: []const u8, expected_result: []const u8) !void {
286 var result = ArrayList(u8).init(global_allocator);
287 defer result.deinit();
288
289 expandString(test_input, &result) catch unreachable;
290
291 try testing.expectEqualSlices(u8, expected_result, result.items);
292}
test/standalone/c_compiler/build.zig+18-21
......@@ -1,27 +1,24 @@
11const std = @import("std");
22const builtin = @import("builtin");
3const CrossTarget = std.zig.CrossTarget;
43
5// TODO integrate this with the std.Build executor API
6fn isRunnableTarget(t: CrossTarget) bool {
7 if (t.isNative()) return true;
4pub fn build(b: *std.Build) void {
5 const test_step = b.step("test", "Test it");
6 b.default_step = test_step;
87
9 return (t.getOsTag() == builtin.os.tag and
10 t.getCpuArch() == builtin.cpu.arch);
8 add(b, test_step, .Debug);
9 add(b, test_step, .ReleaseFast);
10 add(b, test_step, .ReleaseSmall);
11 add(b, test_step, .ReleaseSafe);
1112}
1213
13pub fn build(b: *std.Build) void {
14 const optimize = b.standardOptimizeOption(.{});
15 const target = b.standardTargetOptions(.{});
16
17 const test_step = b.step("test", "Test the program");
14fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
15 const target: std.zig.CrossTarget = .{};
1816
1917 const exe_c = b.addExecutable(.{
2018 .name = "test_c",
2119 .optimize = optimize,
2220 .target = target,
2321 });
24 b.default_step.dependOn(&exe_c.step);
2522 exe_c.addCSourceFile("test.c", &[0][]const u8{});
2623 exe_c.linkLibC();
2724
......@@ -47,13 +44,13 @@ pub fn build(b: *std.Build) void {
4744 else => {},
4845 }
4946
50 if (isRunnableTarget(target)) {
51 const run_c_cmd = exe_c.run();
52 test_step.dependOn(&run_c_cmd.step);
53 const run_cpp_cmd = exe_cpp.run();
54 test_step.dependOn(&run_cpp_cmd.step);
55 } else {
56 test_step.dependOn(&exe_c.step);
57 test_step.dependOn(&exe_cpp.step);
58 }
47 const run_c_cmd = b.addRunArtifact(exe_c);
48 run_c_cmd.expectExitCode(0);
49 run_c_cmd.skip_foreign_checks = true;
50 test_step.dependOn(&run_c_cmd.step);
51
52 const run_cpp_cmd = b.addRunArtifact(exe_cpp);
53 run_cpp_cmd.expectExitCode(0);
54 run_cpp_cmd.skip_foreign_checks = true;
55 test_step.dependOn(&run_cpp_cmd.step);
5956}
test/standalone/dep_diamond/build.zig+4-2
......@@ -1,7 +1,10 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
58
69 const shared = b.createModule(.{
710 .source_file = .{ .path = "shared.zig" },
......@@ -23,6 +26,5 @@ pub fn build(b: *std.Build) void {
2326
2427 const run = exe.run();
2528
26 const test_step = b.step("test", "Test it");
2729 test_step.dependOn(&run.step);
2830}
test/standalone/dep_mutually_recursive/build.zig+4-2
......@@ -1,7 +1,10 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
58
69 const foo = b.createModule(.{
710 .source_file = .{ .path = "foo.zig" },
......@@ -21,6 +24,5 @@ pub fn build(b: *std.Build) void {
2124
2225 const run = exe.run();
2326
24 const test_step = b.step("test", "Test it");
2527 test_step.dependOn(&run.step);
2628}
test/standalone/dep_recursive/build.zig+4-2
......@@ -1,7 +1,10 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
58
69 const foo = b.createModule(.{
710 .source_file = .{ .path = "foo.zig" },
......@@ -17,6 +20,5 @@ pub fn build(b: *std.Build) void {
1720
1821 const run = exe.run();
1922
20 const test_step = b.step("test", "Test it");
2123 test_step.dependOn(&run.step);
2224}
test/standalone/dep_shared_builtin/build.zig+4-2
......@@ -1,7 +1,10 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
58
69 const exe = b.addExecutable(.{
710 .name = "test",
......@@ -14,6 +17,5 @@ pub fn build(b: *std.Build) void {
1417
1518 const run = exe.run();
1619
17 const test_step = b.step("test", "Test it");
1820 test_step.dependOn(&run.step);
1921}
test/standalone/dep_triangle/build.zig+4-2
......@@ -1,7 +1,10 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
58
69 const shared = b.createModule(.{
710 .source_file = .{ .path = "shared.zig" },
......@@ -20,6 +23,5 @@ pub fn build(b: *std.Build) void {
2023
2124 const run = exe.run();
2225
23 const test_step = b.step("test", "Test it");
2426 test_step.dependOn(&run.step);
2527}
test/standalone/embed_generated_file/build.zig+3-5
......@@ -1,8 +1,8 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const target = b.standardTargetOptions(.{});
5 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
66
77 const bootloader = b.addExecutable(.{
88 .name = "bootloader",
......@@ -16,13 +16,11 @@ pub fn build(b: *std.Build) void {
1616
1717 const exe = b.addTest(.{
1818 .root_source_file = .{ .path = "main.zig" },
19 .target = target,
20 .optimize = optimize,
19 .optimize = .Debug,
2120 });
2221 exe.addAnonymousModule("bootloader.elf", .{
2322 .source_file = bootloader.getOutputSource(),
2423 });
2524
26 const test_step = b.step("test", "Test the program");
2725 test_step.dependOn(&exe.step);
2826}
test/standalone/emit_asm_and_bin/build.zig+4-2
......@@ -1,6 +1,9 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
47 const main = b.addTest(.{
58 .root_source_file = .{ .path = "main.zig" },
69 .optimize = b.standardOptimizeOption(.{}),
......@@ -8,6 +11,5 @@ pub fn build(b: *std.Build) void {
811 main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") };
912 main.emit_bin = .{ .emit_to = b.pathFromRoot("main") };
1013
11 const test_step = b.step("test", "Run test");
12 test_step.dependOn(&main.step);
14 test_step.dependOn(&main.run().step);
1315}
test/standalone/empty_env/build.zig+13-3
......@@ -1,15 +1,25 @@
11const std = @import("std");
2const builtin = @import("builtin");
23
34pub fn build(b: *std.Build) void {
5 const test_step = b.step("test", "Test it");
6 b.default_step = test_step;
7
8 const optimize: std.builtin.OptimizeMode = .Debug;
9
10 if (builtin.os.tag == .windows and builtin.cpu.arch == .aarch64) {
11 // https://github.com/ziglang/zig/issues/13685
12 return;
13 }
14
415 const main = b.addExecutable(.{
516 .name = "main",
617 .root_source_file = .{ .path = "main.zig" },
7 .optimize = b.standardOptimizeOption(.{}),
18 .optimize = optimize,
819 });
920
10 const run = main.run();
21 const run = b.addRunArtifact(main);
1122 run.clearEnvironment();
1223
13 const test_step = b.step("test", "Test it");
1424 test_step.dependOn(&run.step);
1525}
test/standalone/extern/build.zig+1-1
......@@ -1,7 +1,7 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const optimize: std.builtin.OptimizeMode = .Debug;
55
66 const obj = b.addObject(.{
77 .name = "exports",
test/standalone/global_linkage/build.zig+8-5
......@@ -1,20 +1,24 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test the program");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8 const target: std.zig.CrossTarget = .{};
59
610 const obj1 = b.addStaticLibrary(.{
711 .name = "obj1",
812 .root_source_file = .{ .path = "obj1.zig" },
913 .optimize = optimize,
10 .target = .{},
14 .target = target,
1115 });
1216
1317 const obj2 = b.addStaticLibrary(.{
1418 .name = "obj2",
1519 .root_source_file = .{ .path = "obj2.zig" },
1620 .optimize = optimize,
17 .target = .{},
21 .target = target,
1822 });
1923
2024 const main = b.addTest(.{
......@@ -24,6 +28,5 @@ pub fn build(b: *std.Build) void {
2428 main.linkLibrary(obj1);
2529 main.linkLibrary(obj2);
2630
27 const test_step = b.step("test", "Test it");
28 test_step.dependOn(&main.step);
31 test_step.dependOn(&main.run().step);
2932}
test/standalone/install_raw_hex/build.zig+3-3
......@@ -3,8 +3,8 @@ const std = @import("std");
33const CheckFileStep = std.Build.CheckFileStep;
44
55pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test the program");
7 b.default_step.dependOn(test_step);
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
88
99 const target = .{
1010 .cpu_arch = .thumb,
......@@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void {
1313 .abi = .gnueabihf,
1414 };
1515
16 const optimize = b.standardOptimizeOption(.{});
16 const optimize: std.builtin.OptimizeMode = .Debug;
1717
1818 const elf = b.addExecutable(.{
1919 .name = "zig-nrf52-blink.elf",
test/standalone/issue_11595/build.zig+14-17
......@@ -1,18 +1,17 @@
11const std = @import("std");
22const builtin = @import("builtin");
3const CrossTarget = std.zig.CrossTarget;
43
5// TODO integrate this with the std.Build executor API
6fn isRunnableTarget(t: CrossTarget) bool {
7 if (t.isNative()) return true;
4pub fn build(b: *std.Build) void {
5 const test_step = b.step("test", "Test it");
6 b.default_step = test_step;
87
9 return (t.getOsTag() == builtin.os.tag and
10 t.getCpuArch() == builtin.cpu.arch);
11}
8 const optimize: std.builtin.OptimizeMode = .Debug;
9 const target: std.zig.CrossTarget = .{};
1210
13pub fn build(b: *std.Build) void {
14 const optimize = b.standardOptimizeOption(.{});
15 const target = b.standardTargetOptions(.{});
11 if (builtin.os.tag == .windows) {
12 // https://github.com/ziglang/zig/issues/12419
13 return;
14 }
1615
1716 const exe = b.addExecutable(.{
1817 .name = "zigtest",
......@@ -44,11 +43,9 @@ pub fn build(b: *std.Build) void {
4443
4544 b.default_step.dependOn(&exe.step);
4645
47 const test_step = b.step("test", "Test the program");
48 if (isRunnableTarget(target)) {
49 const run_cmd = exe.run();
50 test_step.dependOn(&run_cmd.step);
51 } else {
52 test_step.dependOn(&exe.step);
53 }
46 const run_cmd = b.addRunArtifact(exe);
47 run_cmd.skip_foreign_checks = true;
48 run_cmd.expectExitCode(0);
49
50 test_step.dependOn(&run_cmd.step);
5451}
test/standalone/issue_12588/build.zig+5-3
......@@ -1,8 +1,11 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target = b.standardTargetOptions(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8 const target: std.zig.CrossTarget = .{};
69
710 const obj = b.addObject(.{
811 .name = "main",
......@@ -15,6 +18,5 @@ pub fn build(b: *std.Build) void {
1518 obj.emit_bin = .no_emit;
1619 b.default_step.dependOn(&obj.step);
1720
18 const test_step = b.step("test", "Test the program");
1921 test_step.dependOn(&obj.step);
2022}
test/standalone/issue_12706/build.zig+9-21
......@@ -2,17 +2,12 @@ const std = @import("std");
22const builtin = @import("builtin");
33const CrossTarget = std.zig.CrossTarget;
44
5// TODO integrate this with the std.Build executor API
6fn isRunnableTarget(t: CrossTarget) bool {
7 if (t.isNative()) return true;
8
9 return (t.getOsTag() == builtin.os.tag and
10 t.getCpuArch() == builtin.cpu.arch);
11}
12
135pub fn build(b: *std.Build) void {
14 const optimize = b.standardOptimizeOption(.{});
15 const target = b.standardTargetOptions(.{});
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
8
9 const optimize: std.builtin.OptimizeMode = .Debug;
10 const target: std.zig.CrossTarget = .{};
1611
1712 const exe = b.addExecutable(.{
1813 .name = "main",
......@@ -20,22 +15,15 @@ pub fn build(b: *std.Build) void {
2015 .optimize = optimize,
2116 .target = target,
2217 });
23 exe.install();
2418
2519 const c_sources = [_][]const u8{
2620 "test.c",
2721 };
28
2922 exe.addCSourceFiles(&c_sources, &.{});
3023 exe.linkLibC();
3124
32 b.default_step.dependOn(&exe.step);
33
34 const test_step = b.step("test", "Test the program");
35 if (isRunnableTarget(target)) {
36 const run_cmd = exe.run();
37 test_step.dependOn(&run_cmd.step);
38 } else {
39 test_step.dependOn(&exe.step);
40 }
25 const run_cmd = b.addRunArtifact(exe);
26 run_cmd.expectExitCode(0);
27 run_cmd.skip_foreign_checks = true;
28 test_step.dependOn(&run_cmd.step);
4129}
test/standalone/issue_13030/build.zig+10-5
......@@ -3,17 +3,22 @@ const builtin = @import("builtin");
33const CrossTarget = std.zig.CrossTarget;
44
55pub fn build(b: *std.Build) void {
6 const optimize = b.standardOptimizeOption(.{});
7 const target = b.standardTargetOptions(.{});
6 const test_step = b.step("test", "Test it");
7 b.default_step = test_step;
88
9 add(b, test_step, .Debug);
10 add(b, test_step, .ReleaseFast);
11 add(b, test_step, .ReleaseSmall);
12 add(b, test_step, .ReleaseSafe);
13}
14
15fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
916 const obj = b.addObject(.{
1017 .name = "main",
1118 .root_source_file = .{ .path = "main.zig" },
1219 .optimize = optimize,
13 .target = target,
20 .target = .{},
1421 });
15 b.default_step.dependOn(&obj.step);
1622
17 const test_step = b.step("test", "Test the program");
1823 test_step.dependOn(&obj.step);
1924}
test/standalone/issue_13970/build.zig+6-4
......@@ -1,6 +1,9 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
47 const test1 = b.addTest(.{
58 .root_source_file = .{ .path = "test_root/empty.zig" },
69 });
......@@ -14,8 +17,7 @@ pub fn build(b: *std.Build) void {
1417 test2.setTestRunner("src/main.zig");
1518 test3.setTestRunner("src/main.zig");
1619
17 const test_step = b.step("test", "Test package path resolution of custom test runner");
18 test_step.dependOn(&test1.step);
19 test_step.dependOn(&test2.step);
20 test_step.dependOn(&test3.step);
20 test_step.dependOn(&test1.run().step);
21 test_step.dependOn(&test2.run().step);
22 test_step.dependOn(&test3.run().step);
2123}
test/standalone/issue_339/build.zig+8-3
......@@ -1,13 +1,18 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8 const target: std.zig.CrossTarget = .{};
9
410 const obj = b.addObject(.{
511 .name = "test",
612 .root_source_file = .{ .path = "test.zig" },
7 .target = b.standardTargetOptions(.{}),
8 .optimize = b.standardOptimizeOption(.{}),
13 .target = target,
14 .optimize = optimize,
915 });
1016
11 const test_step = b.step("test", "Test the program");
1217 test_step.dependOn(&obj.step);
1318}
test/standalone/issue_5825/build.zig+4-2
......@@ -1,12 +1,15 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
47 const target = .{
58 .cpu_arch = .x86_64,
69 .os_tag = .windows,
710 .abi = .msvc,
811 };
9 const optimize = b.standardOptimizeOption(.{});
12 const optimize: std.builtin.OptimizeMode = .Debug;
1013 const obj = b.addObject(.{
1114 .name = "issue_5825",
1215 .root_source_file = .{ .path = "main.zig" },
......@@ -24,6 +27,5 @@ pub fn build(b: *std.Build) void {
2427 exe.linkSystemLibrary("ntdll");
2528 exe.addObject(obj);
2629
27 const test_step = b.step("test", "Test the program");
2830 test_step.dependOn(&exe.step);
2931}
test/standalone/issue_7030.zig created+21
......@@ -0,0 +1,21 @@
1const std = @import("std");
2
3pub const std_options = struct {
4 pub const logFn = log;
5};
6
7pub fn log(
8 comptime message_level: std.log.Level,
9 comptime scope: @Type(.EnumLiteral),
10 comptime format: []const u8,
11 args: anytype,
12) void {
13 _ = message_level;
14 _ = scope;
15 _ = format;
16 _ = args;
17}
18
19pub fn main() anyerror!void {
20 std.log.info("All your codebase are belong to us.", .{});
21}
test/standalone/issue_7030/build.zig deleted-17
......@@ -1,17 +0,0 @@
1const std = @import("std");
2
3pub fn build(b: *std.Build) void {
4 const exe = b.addExecutable(.{
5 .name = "issue_7030",
6 .root_source_file = .{ .path = "main.zig" },
7 .target = .{
8 .cpu_arch = .wasm32,
9 .os_tag = .freestanding,
10 },
11 });
12 exe.install();
13 b.default_step.dependOn(&exe.step);
14
15 const test_step = b.step("test", "Test the program");
16 test_step.dependOn(&exe.step);
17}
test/standalone/issue_7030/main.zig deleted-21
......@@ -1,21 +0,0 @@
1const std = @import("std");
2
3pub const std_options = struct {
4 pub const logFn = log;
5};
6
7pub fn log(
8 comptime message_level: std.log.Level,
9 comptime scope: @Type(.EnumLiteral),
10 comptime format: []const u8,
11 args: anytype,
12) void {
13 _ = message_level;
14 _ = scope;
15 _ = format;
16 _ = args;
17}
18
19pub fn main() anyerror!void {
20 std.log.info("All your codebase are belong to us.", .{});
21}
test/standalone/issue_794/build.zig+3-3
......@@ -1,13 +1,13 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
47 const test_artifact = b.addTest(.{
58 .root_source_file = .{ .path = "main.zig" },
69 });
710 test_artifact.addIncludePath("a_directory");
811
9 b.default_step.dependOn(&test_artifact.step);
10
11 const test_step = b.step("test", "Test the program");
1212 test_step.dependOn(&test_artifact.step);
1313}
test/standalone/issue_8550/build.zig+5-2
......@@ -1,6 +1,10 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) !void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
48 const target = std.zig.CrossTarget{
59 .os_tag = .freestanding,
610 .cpu_arch = .arm,
......@@ -8,7 +12,7 @@ pub fn build(b: *std.Build) !void {
812 .explicit = &std.Target.arm.cpu.arm1176jz_s,
913 },
1014 };
11 const optimize = b.standardOptimizeOption(.{});
15
1216 const kernel = b.addExecutable(.{
1317 .name = "kernel",
1418 .root_source_file = .{ .path = "./main.zig" },
......@@ -19,6 +23,5 @@ pub fn build(b: *std.Build) !void {
1923 kernel.setLinkerScriptPath(.{ .path = "./linker.ld" });
2024 kernel.install();
2125
22 const test_step = b.step("test", "Test it");
2326 test_step.dependOn(&kernel.step);
2427}
test/standalone/issue_9812/build.zig+5-2
......@@ -1,7 +1,11 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) !void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8
59 const zip_add = b.addTest(.{
610 .root_source_file = .{ .path = "main.zig" },
711 .optimize = optimize,
......@@ -13,6 +17,5 @@ pub fn build(b: *std.Build) !void {
1317 zip_add.addIncludePath("vendor/kuba-zip");
1418 zip_add.linkLibC();
1519
16 const test_step = b.step("test", "Test it");
1720 test_step.dependOn(&zip_add.step);
1821}
test/standalone/load_dynamic_library/build.zig+16-4
......@@ -1,8 +1,19 @@
11const std = @import("std");
2const builtin = @import("builtin");
23
34pub fn build(b: *std.Build) void {
4 const target = b.standardTargetOptions(.{});
5 const optimize = b.standardOptimizeOption(.{});
5 const test_step = b.step("test", "Test it");
6 b.default_step = test_step;
7
8 const optimize: std.builtin.OptimizeMode = .Debug;
9 const target: std.zig.CrossTarget = .{};
10
11 const ok = (builtin.os.tag != .wasi and
12 // https://github.com/ziglang/zig/issues/13550
13 (builtin.os.tag != .macos or builtin.cpu.arch != .aarch64) and
14 // https://github.com/ziglang/zig/issues/13686
15 (builtin.os.tag != .windows or builtin.cpu.arch != .aarch64));
16 if (!ok) return;
617
718 const lib = b.addSharedLibrary(.{
819 .name = "add",
......@@ -19,9 +30,10 @@ pub fn build(b: *std.Build) void {
1930 .target = target,
2031 });
2132
22 const run = main.run();
33 const run = b.addRunArtifact(main);
2334 run.addArtifactArg(lib);
35 run.skip_foreign_checks = true;
36 run.expectExitCode(0);
2437
25 const test_step = b.step("test", "Test the program");
2638 test_step.dependOn(&run.step);
2739}
test/standalone/load_dynamic_library/main.zig+1-5
......@@ -11,11 +11,7 @@ pub fn main() !void {
1111 var lib = try std.DynLib.open(dynlib_name);
1212 defer lib.close();
1313
14 const Add = switch (@import("builtin").zig_backend) {
15 .stage1 => fn (i32, i32) callconv(.C) i32,
16 else => *const fn (i32, i32) callconv(.C) i32,
17 };
18
14 const Add = *const fn (i32, i32) callconv(.C) i32;
1915 const addFn = lib.lookup(Add, "add") orelse return error.SymbolNotFound;
2016
2117 const result = addFn(12, 34);
test/standalone/main_pkg_path/build.zig+4-2
......@@ -1,11 +1,13 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
47 const test_exe = b.addTest(.{
58 .root_source_file = .{ .path = "a/test.zig" },
69 });
710 test_exe.setMainPkgPath(".");
811
9 const test_step = b.step("test", "Test the program");
10 test_step.dependOn(&test_exe.step);
12 test_step.dependOn(&test_exe.run().step);
1113}
test/standalone/mix_c_files/build.zig+13-19
......@@ -1,34 +1,28 @@
11const std = @import("std");
2const builtin = @import("builtin");
3const CrossTarget = std.zig.CrossTarget;
42
5// TODO integrate this with the std.Build executor API
6fn isRunnableTarget(t: CrossTarget) bool {
7 if (t.isNative()) return true;
3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
86
9 return (t.getOsTag() == builtin.os.tag and
10 t.getCpuArch() == builtin.cpu.arch);
7 add(b, test_step, .Debug);
8 add(b, test_step, .ReleaseFast);
9 add(b, test_step, .ReleaseSmall);
10 add(b, test_step, .ReleaseSafe);
1111}
1212
13pub fn build(b: *std.Build) void {
14 const optimize = b.standardOptimizeOption(.{});
15 const target = b.standardTargetOptions(.{});
16
13fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
1714 const exe = b.addExecutable(.{
1815 .name = "test",
1916 .root_source_file = .{ .path = "main.zig" },
2017 .optimize = optimize,
21 .target = target,
2218 });
2319 exe.addCSourceFile("test.c", &[_][]const u8{"-std=c11"});
2420 exe.linkLibC();
2521 b.default_step.dependOn(&exe.step);
2622
27 const test_step = b.step("test", "Test the program");
28 if (isRunnableTarget(target)) {
29 const run_cmd = exe.run();
30 test_step.dependOn(&run_cmd.step);
31 } else {
32 test_step.dependOn(&exe.step);
33 }
23 const run_cmd = b.addRunArtifact(exe);
24 run_cmd.skip_foreign_checks = true;
25 run_cmd.expectExitCode(0);
26
27 test_step.dependOn(&run_cmd.step);
3428}
test/standalone/mix_o_files/build.zig+7-3
......@@ -1,18 +1,23 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8 const target: std.zig.CrossTarget = .{};
59
610 const obj = b.addObject(.{
711 .name = "base64",
812 .root_source_file = .{ .path = "base64.zig" },
913 .optimize = optimize,
10 .target = .{},
14 .target = target,
1115 });
1216
1317 const exe = b.addExecutable(.{
1418 .name = "test",
1519 .optimize = optimize,
20 .target = target,
1621 });
1722 exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
1823 exe.addObject(obj);
......@@ -22,6 +27,5 @@ pub fn build(b: *std.Build) void {
2227
2328 const run_cmd = exe.run();
2429
25 const test_step = b.step("test", "Test the program");
2630 test_step.dependOn(&run_cmd.step);
2731}
test/standalone/options/build.zig+1-1
......@@ -20,5 +20,5 @@ pub fn build(b: *std.Build) void {
2020 options.addOption([]const u8, "string", b.option([]const u8, "string", "s").?);
2121
2222 const test_step = b.step("test", "Run unit tests");
23 test_step.dependOn(&main.step);
23 test_step.dependOn(&main.run().step);
2424}
test/standalone/pie/build.zig+14-4
......@@ -1,14 +1,24 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8 const target: std.zig.CrossTarget = .{
9 .os_tag = .linux,
10 .cpu_arch = .x86_64,
11 };
12
413 const main = b.addTest(.{
514 .root_source_file = .{ .path = "main.zig" },
6 .optimize = b.standardOptimizeOption(.{}),
15 .optimize = optimize,
16 .target = target,
717 });
818 main.pie = true;
919
10 const test_step = b.step("test", "Test the program");
11 test_step.dependOn(&main.step);
20 const run = main.run();
21 run.skip_foreign_checks = true;
1222
13 b.default_step.dependOn(test_step);
23 test_step.dependOn(&run.step);
1424}
test/standalone/pkg_import/build.zig+4-2
......@@ -1,7 +1,10 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
58
69 const exe = b.addExecutable(.{
710 .name = "test",
......@@ -12,6 +15,5 @@ pub fn build(b: *std.Build) void {
1215
1316 const run = exe.run();
1417
15 const test_step = b.step("test", "Test it");
1618 test_step.dependOn(&run.step);
1719}
test/standalone/shared_library/build.zig+5-5
......@@ -1,8 +1,11 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const target = b.standardTargetOptions(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8 const target: std.zig.CrossTarget = .{};
69 const lib = b.addSharedLibrary(.{
710 .name = "mathtest",
811 .root_source_file = .{ .path = "mathtest.zig" },
......@@ -20,10 +23,7 @@ pub fn build(b: *std.Build) void {
2023 exe.linkLibrary(lib);
2124 exe.linkSystemLibrary("c");
2225
23 b.default_step.dependOn(&exe.step);
24
2526 const run_cmd = exe.run();
2627
27 const test_step = b.step("test", "Test the program");
2828 test_step.dependOn(&run_cmd.step);
2929}
test/standalone/sigpipe/build.zig+14-5
......@@ -2,7 +2,16 @@ const std = @import("std");
22const os = std.os;
33
44pub fn build(b: *std.build.Builder) !void {
5 const test_step = b.step("test", "Run the tests");
5 const test_step = b.step("test", "Test it");
6 b.default_step = test_step;
7
8 // TODO signal handling code has no business being in a build script.
9 // this logic needs to move to a file called parent.zig which is
10 // added as an executable.
11
12 //if (!std.os.have_sigpipe_support) {
13 // return;
14 //}
615
716 // This test runs "breakpipe" as a child process and that process
817 // depends on inheriting a SIGPIPE disposition of "default".
......@@ -23,12 +32,12 @@ pub fn build(b: *std.build.Builder) !void {
2332 .root_source_file = .{ .path = "breakpipe.zig" },
2433 });
2534 exe.addOptions("build_options", options);
26 const run = exe.run();
35 const run = b.addRunArtifact(exe);
2736 if (keep_sigpipe) {
28 run.expected_term = .{ .Signal = std.os.SIG.PIPE };
37 run.addCheck(.{ .expect_term = .{ .Signal = std.os.SIG.PIPE } });
2938 } else {
30 run.stdout_action = .{ .expect_exact = "BrokenPipe\n" };
31 run.expected_term = .{ .Exited = 123 };
39 run.addCheck(.{ .expect_stdout_exact = "BrokenPipe\n" });
40 run.addCheck(.{ .expect_term = .{ .Exited = 123 } });
3241 }
3342 test_step.dependOn(&run.step);
3443 }
test/standalone/static_c_lib/build.zig+5-3
......@@ -1,7 +1,10 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
58
69 const foo = b.addStaticLibrary(.{
710 .name = "foo",
......@@ -18,6 +21,5 @@ pub fn build(b: *std.Build) void {
1821 test_exe.linkLibrary(foo);
1922 test_exe.addIncludePath(".");
2023
21 const test_step = b.step("test", "Test it");
22 test_step.dependOn(&test_exe.step);
24 test_step.dependOn(&test_exe.run().step);
2325}
test/standalone/test_runner_module_imports/build.zig+1-1
......@@ -15,5 +15,5 @@ pub fn build(b: *std.Build) void {
1515 t.addModule("module2", module2);
1616
1717 const test_step = b.step("test", "Run unit tests");
18 test_step.dependOn(&t.step);
18 test_step.dependOn(&t.run().step);
1919}
test/standalone/test_runner_path/build.zig+5-2
......@@ -1,14 +1,17 @@
11const std = @import("std");
22
3pub const requires_stage2 = true;
4
35pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test the program");
7 b.default_step = test_step;
8
49 const test_exe = b.addTest(.{
510 .root_source_file = .{ .path = "test.zig" },
6 .kind = .test_exe,
711 });
812 test_exe.test_runner = "test_runner.zig";
913
1014 const test_run = test_exe.run();
1115
12 const test_step = b.step("test", "Test the program");
1316 test_step.dependOn(&test_run.step);
1417}
test/standalone/test_runner_path/test_runner.zig+4-36
......@@ -1,51 +1,19 @@
11const std = @import("std");
2const io = std.io;
32const builtin = @import("builtin");
43
5pub const io_mode: io.Mode = builtin.test_io_mode;
6
74pub fn main() void {
8 const test_fn_list = builtin.test_functions;
95 var ok_count: usize = 0;
106 var skip_count: usize = 0;
117 var fail_count: usize = 0;
128
13 var async_frame_buffer: []align(std.Target.stack_align) u8 = undefined;
14 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
15 // ignores the alignment of the slice.
16 async_frame_buffer = &[_]u8{};
17
18 for (test_fn_list) |test_fn| {
19 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
20 .evented => blk: {
21 if (async_frame_buffer.len < size) {
22 std.heap.page_allocator.free(async_frame_buffer);
23 async_frame_buffer = std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size) catch @panic("out of memory");
24 }
25 const casted_fn = @ptrCast(fn () callconv(.Async) anyerror!void, test_fn.func);
26 break :blk await @asyncCall(async_frame_buffer, {}, casted_fn, .{});
27 },
28 .blocking => {
29 skip_count += 1;
30 continue;
31 },
32 } else test_fn.func();
33 if (result) |_| {
9 for (builtin.test_functions) |test_fn| {
10 if (test_fn.func()) |_| {
3411 ok_count += 1;
3512 } else |err| switch (err) {
36 error.SkipZigTest => {
37 skip_count += 1;
38 },
39 else => {
40 fail_count += 1;
41 },
13 error.SkipZigTest => skip_count += 1,
14 else => fail_count += 1,
4215 }
4316 }
44 if (ok_count == test_fn_list.len) {
45 std.debug.print("All {d} tests passed.\n", .{ok_count});
46 } else {
47 std.debug.print("{d} passed; {d} skipped; {d} failed.\n", .{ ok_count, skip_count, fail_count });
48 }
4917 if (ok_count != 1 or skip_count != 1 or fail_count != 1) {
5018 std.process.exit(1);
5119 }
test/standalone/use_alias/build.zig+7-3
......@@ -1,12 +1,16 @@
11const std = @import("std");
22
33pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8
49 const main = b.addTest(.{
510 .root_source_file = .{ .path = "main.zig" },
6 .optimize = b.standardOptimizeOption(.{}),
11 .optimize = optimize,
712 });
813 main.addIncludePath(".");
914
10 const test_step = b.step("test", "Test it");
11 test_step.dependOn(&main.step);
15 test_step.dependOn(&main.run().step);
1216}
test/standalone/windows_spawn/build.zig+13-3
......@@ -1,23 +1,33 @@
11const std = @import("std");
2const builtin = @import("builtin");
23
34pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const test_step = b.step("test", "Test it");
6 b.default_step = test_step;
7
8 const optimize: std.builtin.OptimizeMode = .Debug;
9 const target: std.zig.CrossTarget = .{};
10
11 if (builtin.os.tag != .windows) return;
512
613 const hello = b.addExecutable(.{
714 .name = "hello",
815 .root_source_file = .{ .path = "hello.zig" },
916 .optimize = optimize,
17 .target = target,
1018 });
1119
1220 const main = b.addExecutable(.{
1321 .name = "main",
1422 .root_source_file = .{ .path = "main.zig" },
1523 .optimize = optimize,
24 .target = target,
1625 });
1726
18 const run = main.run();
27 const run = b.addRunArtifact(main);
1928 run.addArtifactArg(hello);
29 run.expectExitCode(0);
30 run.skip_foreign_checks = true;
2031
21 const test_step = b.step("test", "Test it");
2232 test_step.dependOn(&run.step);
2333}
test/tests.zig+449-745
......@@ -1,16 +1,9 @@
11const std = @import("std");
22const builtin = @import("builtin");
3const debug = std.debug;
3const assert = std.debug.assert;
44const CrossTarget = std.zig.CrossTarget;
5const io = std.io;
6const fs = std.fs;
75const mem = std.mem;
8const fmt = std.fmt;
9const ArrayList = std.ArrayList;
106const OptimizeMode = std.builtin.OptimizeMode;
11const CompileStep = std.Build.CompileStep;
12const Allocator = mem.Allocator;
13const ExecError = std.Build.ExecError;
147const Step = std.Build.Step;
158
169// Cases
......@@ -20,13 +13,13 @@ const stack_traces = @import("stack_traces.zig");
2013const assemble_and_link = @import("assemble_and_link.zig");
2114const translate_c = @import("translate_c.zig");
2215const run_translated_c = @import("run_translated_c.zig");
23const gen_h = @import("gen_h.zig");
2416const link = @import("link.zig");
2517
2618// Implementations
2719pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext;
2820pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext;
29pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext;
21pub const CompareOutputContext = @import("src/CompareOutput.zig");
22pub const StackTracesContext = @import("src/StackTrace.zig");
3023
3124const TestTarget = struct {
3225 target: CrossTarget = @as(CrossTarget, .{}),
......@@ -460,10 +453,71 @@ const test_targets = blk: {
460453 };
461454};
462455
463const max_stdout_size = 1 * 1024 * 1024; // 1 MB
456const c_abi_targets = [_]CrossTarget{
457 .{},
458 .{
459 .cpu_arch = .x86_64,
460 .os_tag = .linux,
461 .abi = .musl,
462 },
463 .{
464 .cpu_arch = .x86,
465 .os_tag = .linux,
466 .abi = .musl,
467 },
468 .{
469 .cpu_arch = .aarch64,
470 .os_tag = .linux,
471 .abi = .musl,
472 },
473 .{
474 .cpu_arch = .arm,
475 .os_tag = .linux,
476 .abi = .musleabihf,
477 },
478 .{
479 .cpu_arch = .mips,
480 .os_tag = .linux,
481 .abi = .musl,
482 },
483 .{
484 .cpu_arch = .riscv64,
485 .os_tag = .linux,
486 .abi = .musl,
487 },
488 .{
489 .cpu_arch = .wasm32,
490 .os_tag = .wasi,
491 .abi = .musl,
492 },
493 .{
494 .cpu_arch = .powerpc,
495 .os_tag = .linux,
496 .abi = .musl,
497 },
498 .{
499 .cpu_arch = .powerpc64le,
500 .os_tag = .linux,
501 .abi = .musl,
502 },
503 .{
504 .cpu_arch = .x86,
505 .os_tag = .windows,
506 .abi = .gnu,
507 },
508 .{
509 .cpu_arch = .x86_64,
510 .os_tag = .windows,
511 .abi = .gnu,
512 },
513};
464514
465pub fn addCompareOutputTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step {
466 const cases = b.allocator.create(CompareOutputContext) catch unreachable;
515pub fn addCompareOutputTests(
516 b: *std.Build,
517 test_filter: ?[]const u8,
518 optimize_modes: []const OptimizeMode,
519) *Step {
520 const cases = b.allocator.create(CompareOutputContext) catch @panic("OOM");
467521 cases.* = CompareOutputContext{
468522 .b = b,
469523 .step = b.step("test-compare-output", "Run the compare output tests"),
......@@ -477,14 +531,26 @@ pub fn addCompareOutputTests(b: *std.Build, test_filter: ?[]const u8, optimize_m
477531 return cases.step;
478532}
479533
480pub fn addStackTraceTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step {
481 const cases = b.allocator.create(StackTracesContext) catch unreachable;
482 cases.* = StackTracesContext{
534pub fn addStackTraceTests(
535 b: *std.Build,
536 test_filter: ?[]const u8,
537 optimize_modes: []const OptimizeMode,
538) *Step {
539 const check_exe = b.addExecutable(.{
540 .name = "check-stack-trace",
541 .root_source_file = .{ .path = "test/src/check-stack-trace.zig" },
542 .target = .{},
543 .optimize = .Debug,
544 });
545
546 const cases = b.allocator.create(StackTracesContext) catch @panic("OOM");
547 cases.* = .{
483548 .b = b,
484549 .step = b.step("test-stack-traces", "Run the stack trace tests"),
485550 .test_index = 0,
486551 .test_filter = test_filter,
487552 .optimize_modes = optimize_modes,
553 .check_exe = check_exe,
488554 };
489555
490556 stack_traces.addCases(cases);
......@@ -494,91 +560,302 @@ pub fn addStackTraceTests(b: *std.Build, test_filter: ?[]const u8, optimize_mode
494560
495561pub fn addStandaloneTests(
496562 b: *std.Build,
497 test_filter: ?[]const u8,
498563 optimize_modes: []const OptimizeMode,
499 skip_non_native: bool,
500564 enable_macos_sdk: bool,
501 target: std.zig.CrossTarget,
502565 omit_stage2: bool,
503 enable_darling: bool,
504 enable_qemu: bool,
505 enable_rosetta: bool,
506 enable_wasmtime: bool,
507 enable_wine: bool,
508566 enable_symlinks_windows: bool,
509567) *Step {
510 const cases = b.allocator.create(StandaloneContext) catch unreachable;
511 cases.* = StandaloneContext{
512 .b = b,
513 .step = b.step("test-standalone", "Run the standalone tests"),
514 .test_index = 0,
515 .test_filter = test_filter,
516 .optimize_modes = optimize_modes,
517 .skip_non_native = skip_non_native,
518 .enable_macos_sdk = enable_macos_sdk,
519 .target = target,
520 .omit_stage2 = omit_stage2,
521 .enable_darling = enable_darling,
522 .enable_qemu = enable_qemu,
523 .enable_rosetta = enable_rosetta,
524 .enable_wasmtime = enable_wasmtime,
525 .enable_wine = enable_wine,
526 .enable_symlinks_windows = enable_symlinks_windows,
527 };
568 const step = b.step("test-standalone", "Run the standalone tests");
569 const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows;
570
571 for (standalone.simple_cases) |case| {
572 for (optimize_modes) |optimize| {
573 if (!case.all_modes and optimize != .Debug) continue;
574 if (case.os_filter) |os_tag| {
575 if (os_tag != builtin.os.tag) continue;
576 }
528577
529 standalone.addCases(cases);
578 if (case.is_exe) {
579 const exe = b.addExecutable(.{
580 .name = std.fs.path.stem(case.src_path),
581 .root_source_file = .{ .path = case.src_path },
582 .optimize = optimize,
583 .target = case.target,
584 });
585 if (case.link_libc) exe.linkLibC();
530586
531 return cases.step;
587 step.dependOn(&exe.step);
588 }
589
590 if (case.is_test) {
591 const exe = b.addTest(.{
592 .name = std.fs.path.stem(case.src_path),
593 .root_source_file = .{ .path = case.src_path },
594 .optimize = optimize,
595 .target = case.target,
596 });
597 if (case.link_libc) exe.linkLibC();
598
599 step.dependOn(&exe.run().step);
600 }
601 }
602 }
603
604 inline for (standalone.build_cases) |case| {
605 const requires_stage2 = @hasDecl(case.import, "requires_stage2") and
606 case.import.requires_stage2;
607 const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and
608 case.import.requires_symlinks;
609 const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and
610 case.import.requires_macos_sdk;
611 const bad =
612 (requires_stage2 and omit_stage2) or
613 (requires_symlinks and omit_symlinks) or
614 (requires_macos_sdk and !enable_macos_sdk);
615 if (!bad) {
616 const dep = b.anonymousDependency(case.build_root, case.import, .{});
617 const dep_step = dep.builder.default_step;
618 assert(mem.startsWith(u8, dep.builder.dep_prefix, "test."));
619 const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..];
620 dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name });
621 step.dependOn(dep_step);
622 }
623 }
624
625 return step;
532626}
533627
534628pub fn addLinkTests(
535629 b: *std.Build,
536 test_filter: ?[]const u8,
537 optimize_modes: []const OptimizeMode,
538630 enable_macos_sdk: bool,
539631 omit_stage2: bool,
540632 enable_symlinks_windows: bool,
541633) *Step {
542 const cases = b.allocator.create(StandaloneContext) catch unreachable;
543 cases.* = StandaloneContext{
544 .b = b,
545 .step = b.step("test-link", "Run the linker tests"),
546 .test_index = 0,
547 .test_filter = test_filter,
548 .optimize_modes = optimize_modes,
549 .skip_non_native = true,
550 .enable_macos_sdk = enable_macos_sdk,
551 .target = .{},
552 .omit_stage2 = omit_stage2,
553 .enable_symlinks_windows = enable_symlinks_windows,
554 };
555 link.addCases(cases);
556 return cases.step;
634 const step = b.step("test-link", "Run the linker tests");
635 const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows;
636
637 inline for (link.cases) |case| {
638 const requires_stage2 = @hasDecl(case.import, "requires_stage2") and
639 case.import.requires_stage2;
640 const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and
641 case.import.requires_symlinks;
642 const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and
643 case.import.requires_macos_sdk;
644 const bad =
645 (requires_stage2 and omit_stage2) or
646 (requires_symlinks and omit_symlinks) or
647 (requires_macos_sdk and !enable_macos_sdk);
648 if (!bad) {
649 const dep = b.anonymousDependency(case.build_root, case.import, .{});
650 const dep_step = dep.builder.default_step;
651 assert(mem.startsWith(u8, dep.builder.dep_prefix, "test."));
652 const dep_prefix_adjusted = dep.builder.dep_prefix["test.".len..];
653 dep_step.name = b.fmt("{s}{s}", .{ dep_prefix_adjusted, dep_step.name });
654 step.dependOn(dep_step);
655 }
656 }
657
658 return step;
557659}
558660
559pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step {
560 _ = test_filter;
561 _ = optimize_modes;
661pub fn addCliTests(b: *std.Build) *Step {
562662 const step = b.step("test-cli", "Test the command line interface");
663 const s = std.fs.path.sep_str;
664
665 {
666
667 // Test `zig init-lib`.
668 const tmp_path = b.makeTempPath();
669 const init_lib = b.addSystemCommand(&.{ b.zig_exe, "init-lib" });
670 init_lib.cwd = tmp_path;
671 init_lib.setName("zig init-lib");
672 init_lib.expectStdOutEqual("");
673 init_lib.expectStdErrEqual("info: Created build.zig\n" ++
674 "info: Created src" ++ s ++ "main.zig\n" ++
675 "info: Next, try `zig build --help` or `zig build test`\n");
676
677 const run_test = b.addSystemCommand(&.{ b.zig_exe, "build", "test" });
678 run_test.cwd = tmp_path;
679 run_test.setName("zig build test");
680 run_test.expectStdOutEqual("");
681 run_test.step.dependOn(&init_lib.step);
682
683 const cleanup = b.addRemoveDirTree(tmp_path);
684 cleanup.step.dependOn(&run_test.step);
685
686 step.dependOn(&cleanup.step);
687 }
563688
564 const exe = b.addExecutable(.{
565 .name = "test-cli",
566 .root_source_file = .{ .path = "test/cli.zig" },
567 .target = .{},
568 .optimize = .Debug,
569 });
570 const run_cmd = exe.run();
571 run_cmd.addArgs(&[_][]const u8{
572 fs.realpathAlloc(b.allocator, b.zig_exe) catch unreachable,
573 b.pathFromRoot(b.cache_root.path orelse "."),
574 });
689 {
690 // Test `zig init-exe`.
691 const tmp_path = b.makeTempPath();
692 const init_exe = b.addSystemCommand(&.{ b.zig_exe, "init-exe" });
693 init_exe.cwd = tmp_path;
694 init_exe.setName("zig init-exe");
695 init_exe.expectStdOutEqual("");
696 init_exe.expectStdErrEqual("info: Created build.zig\n" ++
697 "info: Created src" ++ s ++ "main.zig\n" ++
698 "info: Next, try `zig build --help` or `zig build run`\n");
699
700 // Test missing output path.
701 const bad_out_arg = "-femit-bin=does" ++ s ++ "not" ++ s ++ "exist" ++ s ++ "foo.exe";
702 const ok_src_arg = "src" ++ s ++ "main.zig";
703 const expected = "error: unable to open output directory 'does" ++ s ++ "not" ++ s ++ "exist': FileNotFound\n";
704 const run_bad = b.addSystemCommand(&.{ b.zig_exe, "build-exe", ok_src_arg, bad_out_arg });
705 run_bad.setName("zig build-exe error message for bad -femit-bin arg");
706 run_bad.expectExitCode(1);
707 run_bad.expectStdErrEqual(expected);
708 run_bad.expectStdOutEqual("");
709 run_bad.step.dependOn(&init_exe.step);
710
711 const run_test = b.addSystemCommand(&.{ b.zig_exe, "build", "test" });
712 run_test.cwd = tmp_path;
713 run_test.setName("zig build test");
714 run_test.expectStdOutEqual("");
715 run_test.step.dependOn(&init_exe.step);
716
717 const run_run = b.addSystemCommand(&.{ b.zig_exe, "build", "run" });
718 run_run.cwd = tmp_path;
719 run_run.setName("zig build run");
720 run_run.expectStdOutEqual("Run `zig build test` to run the tests.\n");
721 run_run.expectStdErrEqual("All your codebase are belong to us.\n");
722 run_run.step.dependOn(&init_exe.step);
723
724 const cleanup = b.addRemoveDirTree(tmp_path);
725 cleanup.step.dependOn(&run_test.step);
726 cleanup.step.dependOn(&run_run.step);
727 cleanup.step.dependOn(&run_bad.step);
728
729 step.dependOn(&cleanup.step);
730 }
731
732 // Test Godbolt API
733 if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
734 const tmp_path = b.makeTempPath();
735
736 const writefile = b.addWriteFile("example.zig",
737 \\// Type your code here, or load an example.
738 \\export fn square(num: i32) i32 {
739 \\ return num * num;
740 \\}
741 \\extern fn zig_panic() noreturn;
742 \\pub fn panic(msg: []const u8, error_return_trace: ?*@import("std").builtin.StackTrace, _: ?usize) noreturn {
743 \\ _ = msg;
744 \\ _ = error_return_trace;
745 \\ zig_panic();
746 \\}
747 );
748
749 // This is intended to be the exact CLI usage used by godbolt.org.
750 const run = b.addSystemCommand(&.{
751 b.zig_exe, "build-obj",
752 "--cache-dir", tmp_path,
753 "--name", "example",
754 "-fno-emit-bin", "-fno-emit-h",
755 "-fstrip", "-OReleaseFast",
756 });
757 run.addFileSourceArg(writefile.getFileSource("example.zig").?);
758 const example_s = run.addPrefixedOutputFileArg("-femit-asm=", "example.s");
759
760 const checkfile = b.addCheckFile(example_s, .{
761 .expected_matches = &.{
762 "square:",
763 "mov\teax, edi",
764 "imul\teax, edi",
765 },
766 });
767 checkfile.setName("check godbolt.org CLI usage generating valid asm");
768
769 const cleanup = b.addRemoveDirTree(tmp_path);
770 cleanup.step.dependOn(&checkfile.step);
771
772 step.dependOn(&cleanup.step);
773 }
774
775 {
776 // Test `zig fmt`.
777 // This test must use a temporary directory rather than a cache
778 // directory because this test will be mutating the files. The cache
779 // system relies on cache directories being mutated only by their
780 // owners.
781 const tmp_path = b.makeTempPath();
782 const unformatted_code = " // no reason for indent";
783
784 var dir = std.fs.cwd().openDir(tmp_path, .{}) catch @panic("unhandled");
785 defer dir.close();
786 dir.writeFile("fmt1.zig", unformatted_code) catch @panic("unhandled");
787 dir.writeFile("fmt2.zig", unformatted_code) catch @panic("unhandled");
788
789 // Test zig fmt affecting only the appropriate files.
790 const run1 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "fmt1.zig" });
791 run1.setName("run zig fmt one file");
792 run1.cwd = tmp_path;
793 run1.has_side_effects = true;
794 // stdout should be file path + \n
795 run1.expectStdOutEqual("fmt1.zig\n");
796
797 // running it on the dir, only the new file should be changed
798 const run2 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." });
799 run2.setName("run zig fmt the directory");
800 run2.cwd = tmp_path;
801 run2.has_side_effects = true;
802 run2.expectStdOutEqual("." ++ s ++ "fmt2.zig\n");
803 run2.step.dependOn(&run1.step);
804
805 // both files have been formatted, nothing should change now
806 const run3 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." });
807 run3.setName("run zig fmt with nothing to do");
808 run3.cwd = tmp_path;
809 run3.has_side_effects = true;
810 run3.expectStdOutEqual("");
811 run3.step.dependOn(&run2.step);
812
813 const unformatted_code_utf16 = "\xff\xfe \x00 \x00 \x00 \x00/\x00/\x00 \x00n\x00o\x00 \x00r\x00e\x00a\x00s\x00o\x00n\x00";
814 const fmt4_path = std.fs.path.join(b.allocator, &.{ tmp_path, "fmt4.zig" }) catch @panic("OOM");
815 const write4 = b.addWriteFiles();
816 write4.addBytesToSource(unformatted_code_utf16, fmt4_path);
817 write4.step.dependOn(&run3.step);
818
819 // Test `zig fmt` handling UTF-16 decoding.
820 const run4 = b.addSystemCommand(&.{ b.zig_exe, "fmt", "." });
821 run4.setName("run zig fmt convert UTF-16 to UTF-8");
822 run4.cwd = tmp_path;
823 run4.has_side_effects = true;
824 run4.expectStdOutEqual("." ++ s ++ "fmt4.zig\n");
825 run4.step.dependOn(&write4.step);
826
827 // TODO change this to an exact match
828 const check4 = b.addCheckFile(.{ .path = fmt4_path }, .{
829 .expected_matches = &.{
830 "// no reason",
831 },
832 });
833 check4.step.dependOn(&run4.step);
834
835 const cleanup = b.addRemoveDirTree(tmp_path);
836 cleanup.step.dependOn(&check4.step);
837
838 step.dependOn(&cleanup.step);
839 }
840
841 {
842 // TODO this should move to become a CLI test rather than standalone
843 // cases.addBuildFile("test/standalone/options/build.zig", .{
844 // .extra_argv = &.{
845 // "-Dbool_true",
846 // "-Dbool_false=false",
847 // "-Dint=1234",
848 // "-De=two",
849 // "-Dstring=hello",
850 // },
851 // });
852 }
575853
576 step.dependOn(&run_cmd.step);
577854 return step;
578855}
579856
580857pub fn addAssembleAndLinkTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step {
581 const cases = b.allocator.create(CompareOutputContext) catch unreachable;
858 const cases = b.allocator.create(CompareOutputContext) catch @panic("OOM");
582859 cases.* = CompareOutputContext{
583860 .b = b,
584861 .step = b.step("test-asm-link", "Run the assemble and link tests"),
......@@ -593,7 +870,7 @@ pub fn addAssembleAndLinkTests(b: *std.Build, test_filter: ?[]const u8, optimize
593870}
594871
595872pub fn addTranslateCTests(b: *std.Build, test_filter: ?[]const u8) *Step {
596 const cases = b.allocator.create(TranslateCContext) catch unreachable;
873 const cases = b.allocator.create(TranslateCContext) catch @panic("OOM");
597874 cases.* = TranslateCContext{
598875 .b = b,
599876 .step = b.step("test-translate-c", "Run the C translation tests"),
......@@ -611,7 +888,7 @@ pub fn addRunTranslatedCTests(
611888 test_filter: ?[]const u8,
612889 target: std.zig.CrossTarget,
613890) *Step {
614 const cases = b.allocator.create(RunTranslatedCContext) catch unreachable;
891 const cases = b.allocator.create(RunTranslatedCContext) catch @panic("OOM");
615892 cases.* = .{
616893 .b = b,
617894 .step = b.step("test-run-translated-c", "Run the Run-Translated-C tests"),
......@@ -625,22 +902,7 @@ pub fn addRunTranslatedCTests(
625902 return cases.step;
626903}
627904
628pub fn addGenHTests(b: *std.Build, test_filter: ?[]const u8) *Step {
629 const cases = b.allocator.create(GenHContext) catch unreachable;
630 cases.* = GenHContext{
631 .b = b,
632 .step = b.step("test-gen-h", "Run the C header file generation tests"),
633 .test_index = 0,
634 .test_filter = test_filter,
635 };
636
637 gen_h.addCases(cases);
638
639 return cases.step;
640}
641
642pub fn addPkgTests(
643 b: *std.Build,
905const ModuleTestOptions = struct {
644906 test_filter: ?[]const u8,
645907 root_src: []const u8,
646908 name: []const u8,
......@@ -651,14 +913,17 @@ pub fn addPkgTests(
651913 skip_libc: bool,
652914 skip_stage1: bool,
653915 skip_stage2: bool,
654) *Step {
655 const step = b.step(b.fmt("test-{s}", .{name}), desc);
916 max_rss: usize = 0,
917};
918
919pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
920 const step = b.step(b.fmt("test-{s}", .{options.name}), options.desc);
656921
657922 for (test_targets) |test_target| {
658 if (skip_non_native and !test_target.target.isNative())
923 if (options.skip_non_native and !test_target.target.isNative())
659924 continue;
660925
661 if (skip_libc and test_target.link_libc)
926 if (options.skip_libc and test_target.link_libc)
662927 continue;
663928
664929 if (test_target.link_libc and test_target.target.getOs().requiresLibC()) {
......@@ -666,7 +931,7 @@ pub fn addPkgTests(
666931 continue;
667932 }
668933
669 if (skip_single_threaded and test_target.single_threaded)
934 if (options.skip_single_threaded and test_target.single_threaded)
670935 continue;
671936
672937 if (test_target.disable_native and
......@@ -677,12 +942,12 @@ pub fn addPkgTests(
677942 }
678943
679944 if (test_target.backend) |backend| switch (backend) {
680 .stage1 => if (skip_stage1) continue,
945 .stage1 => if (options.skip_stage1) continue,
681946 .stage2_llvm => {},
682 else => if (skip_stage2) continue,
947 else => if (options.skip_stage2) continue,
683948 };
684949
685 const want_this_mode = for (optimize_modes) |m| {
950 const want_this_mode = for (options.optimize_modes) |m| {
686951 if (m == test_target.optimize_mode) break true;
687952 } else false;
688953 if (!want_this_mode) continue;
......@@ -694,25 +959,24 @@ pub fn addPkgTests(
694959 else
695960 "bare";
696961
697 const triple_prefix = test_target.target.zigTriple(b.allocator) catch unreachable;
962 const triple_prefix = test_target.target.zigTriple(b.allocator) catch @panic("OOM");
963
964 // wasm32-wasi builds need more RAM, idk why
965 const max_rss = if (test_target.target.getOs().tag == .wasi)
966 options.max_rss * 2
967 else
968 options.max_rss;
698969
699970 const these_tests = b.addTest(.{
700 .root_source_file = .{ .path = root_src },
971 .root_source_file = .{ .path = options.root_src },
701972 .optimize = test_target.optimize_mode,
702973 .target = test_target.target,
974 .max_rss = max_rss,
703975 });
704976 const single_threaded_txt = if (test_target.single_threaded) "single" else "multi";
705977 const backend_txt = if (test_target.backend) |backend| @tagName(backend) else "default";
706 these_tests.setNamePrefix(b.fmt("{s}-{s}-{s}-{s}-{s}-{s} ", .{
707 name,
708 triple_prefix,
709 @tagName(test_target.optimize_mode),
710 libc_prefix,
711 single_threaded_txt,
712 backend_txt,
713 }));
714978 these_tests.single_threaded = test_target.single_threaded;
715 these_tests.setFilter(test_filter);
979 these_tests.setFilter(options.test_filter);
716980 if (test_target.link_libc) {
717981 these_tests.linkSystemLibrary("c");
718982 }
......@@ -736,654 +1000,94 @@ pub fn addPkgTests(
7361000 },
7371001 };
7381002
739 step.dependOn(&these_tests.step);
1003 const run = these_tests.run();
1004 run.skip_foreign_checks = true;
1005 run.setName(b.fmt("run test {s}-{s}-{s}-{s}-{s}-{s}", .{
1006 options.name,
1007 triple_prefix,
1008 @tagName(test_target.optimize_mode),
1009 libc_prefix,
1010 single_threaded_txt,
1011 backend_txt,
1012 }));
1013
1014 step.dependOn(&run.step);
7401015 }
7411016 return step;
7421017}
7431018
744pub const StackTracesContext = struct {
745 b: *std.Build,
746 step: *Step,
747 test_index: usize,
748 test_filter: ?[]const u8,
749 optimize_modes: []const OptimizeMode,
750
751 const Expect = [@typeInfo(OptimizeMode).Enum.fields.len][]const u8;
752
753 pub fn addCase(self: *StackTracesContext, config: anytype) void {
754 if (@hasField(@TypeOf(config), "exclude")) {
755 if (config.exclude.exclude()) return;
756 }
757 if (@hasField(@TypeOf(config), "exclude_arch")) {
758 const exclude_arch: []const std.Target.Cpu.Arch = &config.exclude_arch;
759 for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return;
760 }
761 if (@hasField(@TypeOf(config), "exclude_os")) {
762 const exclude_os: []const std.Target.Os.Tag = &config.exclude_os;
763 for (exclude_os) |os| if (os == builtin.os.tag) return;
764 }
765 for (self.optimize_modes) |optimize_mode| {
766 switch (optimize_mode) {
767 .Debug => {
768 if (@hasField(@TypeOf(config), "Debug")) {
769 self.addExpect(config.name, config.source, optimize_mode, config.Debug);
770 }
771 },
772 .ReleaseSafe => {
773 if (@hasField(@TypeOf(config), "ReleaseSafe")) {
774 self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSafe);
775 }
776 },
777 .ReleaseFast => {
778 if (@hasField(@TypeOf(config), "ReleaseFast")) {
779 self.addExpect(config.name, config.source, optimize_mode, config.ReleaseFast);
780 }
781 },
782 .ReleaseSmall => {
783 if (@hasField(@TypeOf(config), "ReleaseSmall")) {
784 self.addExpect(config.name, config.source, optimize_mode, config.ReleaseSmall);
785 }
786 },
787 }
788 }
789 }
790
791 fn addExpect(
792 self: *StackTracesContext,
793 name: []const u8,
794 source: []const u8,
795 optimize_mode: OptimizeMode,
796 mode_config: anytype,
797 ) void {
798 if (@hasField(@TypeOf(mode_config), "exclude")) {
799 if (mode_config.exclude.exclude()) return;
800 }
801 if (@hasField(@TypeOf(mode_config), "exclude_arch")) {
802 const exclude_arch: []const std.Target.Cpu.Arch = &mode_config.exclude_arch;
803 for (exclude_arch) |arch| if (arch == builtin.cpu.arch) return;
804 }
805 if (@hasField(@TypeOf(mode_config), "exclude_os")) {
806 const exclude_os: []const std.Target.Os.Tag = &mode_config.exclude_os;
807 for (exclude_os) |os| if (os == builtin.os.tag) return;
808 }
809
810 const annotated_case_name = fmt.allocPrint(self.b.allocator, "{s} {s} ({s})", .{
811 "stack-trace",
812 name,
813 @tagName(optimize_mode),
814 }) catch unreachable;
815 if (self.test_filter) |filter| {
816 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
817 }
818
819 const b = self.b;
820 const src_basename = "source.zig";
821 const write_src = b.addWriteFile(src_basename, source);
822 const exe = b.addExecutable(.{
823 .name = "test",
824 .root_source_file = write_src.getFileSource(src_basename).?,
825 .optimize = optimize_mode,
826 .target = .{},
827 });
828
829 const run_and_compare = RunAndCompareStep.create(
830 self,
831 exe,
832 annotated_case_name,
833 optimize_mode,
834 mode_config.expect,
835 );
836
837 self.step.dependOn(&run_and_compare.step);
838 }
839
840 const RunAndCompareStep = struct {
841 pub const base_id = .custom;
842
843 step: Step,
844 context: *StackTracesContext,
845 exe: *CompileStep,
846 name: []const u8,
847 optimize_mode: OptimizeMode,
848 expect_output: []const u8,
849 test_index: usize,
850
851 pub fn create(
852 context: *StackTracesContext,
853 exe: *CompileStep,
854 name: []const u8,
855 optimize_mode: OptimizeMode,
856 expect_output: []const u8,
857 ) *RunAndCompareStep {
858 const allocator = context.b.allocator;
859 const ptr = allocator.create(RunAndCompareStep) catch unreachable;
860 ptr.* = RunAndCompareStep{
861 .step = Step.init(.custom, "StackTraceCompareOutputStep", allocator, make),
862 .context = context,
863 .exe = exe,
864 .name = name,
865 .optimize_mode = optimize_mode,
866 .expect_output = expect_output,
867 .test_index = context.test_index,
868 };
869 ptr.step.dependOn(&exe.step);
870 context.test_index += 1;
871 return ptr;
872 }
873
874 fn make(step: *Step) !void {
875 const self = @fieldParentPtr(RunAndCompareStep, "step", step);
876 const b = self.context.b;
877
878 const full_exe_path = self.exe.getOutputSource().getPath(b);
879 var args = ArrayList([]const u8).init(b.allocator);
880 defer args.deinit();
881 args.append(full_exe_path) catch unreachable;
882
883 std.debug.print("Test {d}/{d} {s}...", .{ self.test_index + 1, self.context.test_index, self.name });
884
885 if (!std.process.can_spawn) {
886 const cmd = try std.mem.join(b.allocator, " ", args.items);
887 std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{ @tagName(builtin.os.tag), cmd });
888 b.allocator.free(cmd);
889 return ExecError.ExecNotSupported;
890 }
891
892 var child = std.ChildProcess.init(args.items, b.allocator);
893 child.stdin_behavior = .Ignore;
894 child.stdout_behavior = .Pipe;
895 child.stderr_behavior = .Pipe;
896 child.env_map = b.env_map;
897
898 if (b.verbose) {
899 printInvocation(args.items);
900 }
901 child.spawn() catch |err| debug.panic("Unable to spawn {s}: {s}\n", .{ full_exe_path, @errorName(err) });
902
903 const stdout = child.stdout.?.reader().readAllAlloc(b.allocator, max_stdout_size) catch unreachable;
904 defer b.allocator.free(stdout);
905 const stderrFull = child.stderr.?.reader().readAllAlloc(b.allocator, max_stdout_size) catch unreachable;
906 defer b.allocator.free(stderrFull);
907 var stderr = stderrFull;
908
909 const term = child.wait() catch |err| {
910 debug.panic("Unable to spawn {s}: {s}\n", .{ full_exe_path, @errorName(err) });
911 };
912
913 switch (term) {
914 .Exited => |code| {
915 const expect_code: u32 = 1;
916 if (code != expect_code) {
917 std.debug.print("Process {s} exited with error code {d} but expected code {d}\n", .{
918 full_exe_path,
919 code,
920 expect_code,
921 });
922 printInvocation(args.items);
923 return error.TestFailed;
924 }
925 },
926 .Signal => |signum| {
927 std.debug.print("Process {s} terminated on signal {d}\n", .{ full_exe_path, signum });
928 printInvocation(args.items);
929 return error.TestFailed;
930 },
931 .Stopped => |signum| {
932 std.debug.print("Process {s} stopped on signal {d}\n", .{ full_exe_path, signum });
933 printInvocation(args.items);
934 return error.TestFailed;
935 },
936 .Unknown => |code| {
937 std.debug.print("Process {s} terminated unexpectedly with error code {d}\n", .{ full_exe_path, code });
938 printInvocation(args.items);
939 return error.TestFailed;
940 },
941 }
942
943 // process result
944 // - keep only basename of source file path
945 // - replace address with symbolic string
946 // - replace function name with symbolic string when optimize_mode != .Debug
947 // - skip empty lines
948 const got: []const u8 = got_result: {
949 var buf = ArrayList(u8).init(b.allocator);
950 defer buf.deinit();
951 if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1];
952 var it = mem.split(u8, stderr, "\n");
953 process_lines: while (it.next()) |line| {
954 if (line.len == 0) continue;
955
956 // offset search past `[drive]:` on windows
957 var pos: usize = if (builtin.os.tag == .windows) 2 else 0;
958 // locate delims/anchor
959 const delims = [_][]const u8{ ":", ":", ":", " in ", "(", ")" };
960 var marks = [_]usize{0} ** delims.len;
961 for (delims, 0..) |delim, i| {
962 marks[i] = mem.indexOfPos(u8, line, pos, delim) orelse {
963 // unexpected pattern: emit raw line and cont
964 try buf.appendSlice(line);
965 try buf.appendSlice("\n");
966 continue :process_lines;
967 };
968 pos = marks[i] + delim.len;
969 }
970 // locate source basename
971 pos = mem.lastIndexOfScalar(u8, line[0..marks[0]], fs.path.sep) orelse {
972 // unexpected pattern: emit raw line and cont
973 try buf.appendSlice(line);
974 try buf.appendSlice("\n");
975 continue :process_lines;
976 };
977 // end processing if source basename changes
978 if (!mem.eql(u8, "source.zig", line[pos + 1 .. marks[0]])) break;
979 // emit substituted line
980 try buf.appendSlice(line[pos + 1 .. marks[2] + delims[2].len]);
981 try buf.appendSlice(" [address]");
982 if (self.optimize_mode == .Debug) {
983 // On certain platforms (windows) or possibly depending on how we choose to link main
984 // the object file extension may be present so we simply strip any extension.
985 if (mem.indexOfScalar(u8, line[marks[4]..marks[5]], '.')) |idot| {
986 try buf.appendSlice(line[marks[3] .. marks[4] + idot]);
987 try buf.appendSlice(line[marks[5]..]);
988 } else {
989 try buf.appendSlice(line[marks[3]..]);
990 }
991 } else {
992 try buf.appendSlice(line[marks[3] .. marks[3] + delims[3].len]);
993 try buf.appendSlice("[function]");
994 }
995 try buf.appendSlice("\n");
996 }
997 break :got_result try buf.toOwnedSlice();
998 };
999
1000 if (!mem.eql(u8, self.expect_output, got)) {
1001 std.debug.print(
1002 \\
1003 \\========= Expected this output: =========
1004 \\{s}
1005 \\================================================
1006 \\{s}
1007 \\
1008 , .{ self.expect_output, got });
1009 return error.TestFailed;
1010 }
1011 std.debug.print("OK\n", .{});
1012 }
1013 };
1014};
1015
1016pub const StandaloneContext = struct {
1017 b: *std.Build,
1018 step: *Step,
1019 test_index: usize,
1020 test_filter: ?[]const u8,
1021 optimize_modes: []const OptimizeMode,
1022 skip_non_native: bool,
1023 enable_macos_sdk: bool,
1024 target: std.zig.CrossTarget,
1025 omit_stage2: bool,
1026 enable_darling: bool = false,
1027 enable_qemu: bool = false,
1028 enable_rosetta: bool = false,
1029 enable_wasmtime: bool = false,
1030 enable_wine: bool = false,
1031 enable_symlinks_windows: bool,
1032
1033 pub fn addC(self: *StandaloneContext, root_src: []const u8) void {
1034 self.addAllArgs(root_src, true);
1035 }
1036
1037 pub fn add(self: *StandaloneContext, root_src: []const u8) void {
1038 self.addAllArgs(root_src, false);
1039 }
1040
1041 pub fn addBuildFile(self: *StandaloneContext, build_file: []const u8, features: struct {
1042 build_modes: bool = false,
1043 cross_targets: bool = false,
1044 requires_macos_sdk: bool = false,
1045 requires_stage2: bool = false,
1046 use_emulation: bool = false,
1047 requires_symlinks: bool = false,
1048 extra_argv: []const []const u8 = &.{},
1049 }) void {
1050 const b = self.b;
1051
1052 if (features.requires_macos_sdk and !self.enable_macos_sdk) return;
1053 if (features.requires_stage2 and self.omit_stage2) return;
1054 if (features.requires_symlinks and !self.enable_symlinks_windows and builtin.os.tag == .windows) return;
1055
1056 const annotated_case_name = b.fmt("build {s}", .{build_file});
1057 if (self.test_filter) |filter| {
1058 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
1059 }
1060
1061 var zig_args = ArrayList([]const u8).init(b.allocator);
1062 const rel_zig_exe = fs.path.relative(b.allocator, b.build_root.path orelse ".", b.zig_exe) catch unreachable;
1063 zig_args.append(rel_zig_exe) catch unreachable;
1064 zig_args.append("build") catch unreachable;
1065
1066 zig_args.append("--build-file") catch unreachable;
1067 zig_args.append(b.pathFromRoot(build_file)) catch unreachable;
1068
1069 zig_args.appendSlice(features.extra_argv) catch unreachable;
1070
1071 zig_args.append("test") catch unreachable;
1072
1073 if (b.verbose) {
1074 zig_args.append("--verbose") catch unreachable;
1075 }
1076
1077 if (features.cross_targets and !self.target.isNative()) {
1078 const target_triple = self.target.zigTriple(b.allocator) catch unreachable;
1079 const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable;
1080 zig_args.append(target_arg) catch unreachable;
1081 }
1019pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *Step {
1020 const step = b.step("test-c-abi", "Run the C ABI tests");
10821021
1083 if (features.use_emulation) {
1084 if (self.enable_darling) {
1085 zig_args.append("-fdarling") catch unreachable;
1086 }
1087 if (self.enable_qemu) {
1088 zig_args.append("-fqemu") catch unreachable;
1089 }
1090 if (self.enable_rosetta) {
1091 zig_args.append("-frosetta") catch unreachable;
1092 }
1093 if (self.enable_wasmtime) {
1094 zig_args.append("-fwasmtime") catch unreachable;
1095 }
1096 if (self.enable_wine) {
1097 zig_args.append("-fwine") catch unreachable;
1098 }
1099 }
1022 const optimize_modes: [2]OptimizeMode = .{ .Debug, .ReleaseFast };
11001023
1101 const optimize_modes = if (features.build_modes) self.optimize_modes else &[1]OptimizeMode{.Debug};
1102 for (optimize_modes) |optimize_mode| {
1103 const arg = switch (optimize_mode) {
1104 .Debug => "",
1105 .ReleaseFast => "-Doptimize=ReleaseFast",
1106 .ReleaseSafe => "-Doptimize=ReleaseSafe",
1107 .ReleaseSmall => "-Doptimize=ReleaseSmall",
1108 };
1109 const zig_args_base_len = zig_args.items.len;
1110 if (arg.len > 0)
1111 zig_args.append(arg) catch unreachable;
1112 defer zig_args.resize(zig_args_base_len) catch unreachable;
1113
1114 const run_cmd = b.addSystemCommand(zig_args.items);
1115 const log_step = b.addLog("PASS {s} ({s})", .{ annotated_case_name, @tagName(optimize_mode) });
1116 log_step.step.dependOn(&run_cmd.step);
1117
1118 self.step.dependOn(&log_step.step);
1119 }
1120 }
1024 for (optimize_modes) |optimize_mode| {
1025 if (optimize_mode != .Debug and skip_release) continue;
11211026
1122 pub fn addAllArgs(self: *StandaloneContext, root_src: []const u8, link_libc: bool) void {
1123 const b = self.b;
1027 for (c_abi_targets) |c_abi_target| {
1028 if (skip_non_native and !c_abi_target.isNative()) continue;
11241029
1125 for (self.optimize_modes) |optimize| {
1126 const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {s} ({s})", .{
1127 root_src,
1128 @tagName(optimize),
1129 }) catch unreachable;
1130 if (self.test_filter) |filter| {
1131 if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
1030 if (c_abi_target.isWindows() and c_abi_target.getCpuArch() == .aarch64) {
1031 // https://github.com/ziglang/zig/issues/14908
1032 continue;
11321033 }
11331034
1134 const exe = b.addExecutable(.{
1135 .name = "test",
1136 .root_source_file = .{ .path = root_src },
1137 .optimize = optimize,
1138 .target = .{},
1035 const test_step = b.addTest(.{
1036 .root_source_file = .{ .path = "test/c_abi/main.zig" },
1037 .optimize = optimize_mode,
1038 .target = c_abi_target,
11391039 });
1140 if (link_libc) {
1141 exe.linkSystemLibrary("c");
1040 if (c_abi_target.abi != null and c_abi_target.abi.?.isMusl()) {
1041 // TODO NativeTargetInfo insists on dynamically linking musl
1042 // for some reason?
1043 test_step.target_info.dynamic_linker.max_byte = null;
11421044 }
1143
1144 const log_step = b.addLog("PASS {s}", .{annotated_case_name});
1145 log_step.step.dependOn(&exe.step);
1146
1147 self.step.dependOn(&log_step.step);
1148 }
1149 }
1150};
1151
1152pub const GenHContext = struct {
1153 b: *std.Build,
1154 step: *Step,
1155 test_index: usize,
1156 test_filter: ?[]const u8,
1157
1158 const TestCase = struct {
1159 name: []const u8,
1160 sources: ArrayList(SourceFile),
1161 expected_lines: ArrayList([]const u8),
1162
1163 const SourceFile = struct {
1164 filename: []const u8,
1165 source: []const u8,
1166 };
1167
1168 pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void {
1169 self.sources.append(SourceFile{
1170 .filename = filename,
1171 .source = source,
1172 }) catch unreachable;
1173 }
1174
1175 pub fn addExpectedLine(self: *TestCase, text: []const u8) void {
1176 self.expected_lines.append(text) catch unreachable;
1177 }
1178 };
1179
1180 const GenHCmpOutputStep = struct {
1181 step: Step,
1182 context: *GenHContext,
1183 obj: *CompileStep,
1184 name: []const u8,
1185 test_index: usize,
1186 case: *const TestCase,
1187
1188 pub fn create(
1189 context: *GenHContext,
1190 obj: *CompileStep,
1191 name: []const u8,
1192 case: *const TestCase,
1193 ) *GenHCmpOutputStep {
1194 const allocator = context.b.allocator;
1195 const ptr = allocator.create(GenHCmpOutputStep) catch unreachable;
1196 ptr.* = GenHCmpOutputStep{
1197 .step = Step.init(.Custom, "ParseCCmpOutput", allocator, make),
1198 .context = context,
1199 .obj = obj,
1200 .name = name,
1201 .test_index = context.test_index,
1202 .case = case,
1203 };
1204 ptr.step.dependOn(&obj.step);
1205 context.test_index += 1;
1206 return ptr;
1207 }
1208
1209 fn make(step: *Step) !void {
1210 const self = @fieldParentPtr(GenHCmpOutputStep, "step", step);
1211 const b = self.context.b;
1212
1213 std.debug.print("Test {d}/{d} {s}...", .{ self.test_index + 1, self.context.test_index, self.name });
1214
1215 const full_h_path = self.obj.getOutputHPath();
1216 const actual_h = try io.readFileAlloc(b.allocator, full_h_path);
1217
1218 for (self.case.expected_lines.items) |expected_line| {
1219 if (mem.indexOf(u8, actual_h, expected_line) == null) {
1220 std.debug.print(
1221 \\
1222 \\========= Expected this output: ================
1223 \\{s}
1224 \\========= But found: ===========================
1225 \\{s}
1226 \\
1227 , .{ expected_line, actual_h });
1228 return error.TestFailed;
1229 }
1045 test_step.linkLibC();
1046 test_step.addCSourceFile("test/c_abi/cfuncs.c", &.{"-std=c99"});
1047
1048 // test-c-abi should test both with LTO on and with LTO off. Only
1049 // some combinations are passing currently:
1050 // https://github.com/ziglang/zig/issues/14908
1051 if (c_abi_target.isWindows()) {
1052 test_step.want_lto = false;
12301053 }
1231 std.debug.print("OK\n", .{});
1232 }
1233 };
1234
1235 pub fn create(
1236 self: *GenHContext,
1237 filename: []const u8,
1238 name: []const u8,
1239 source: []const u8,
1240 expected_lines: []const []const u8,
1241 ) *TestCase {
1242 const tc = self.b.allocator.create(TestCase) catch unreachable;
1243 tc.* = TestCase{
1244 .name = name,
1245 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
1246 .expected_lines = ArrayList([]const u8).init(self.b.allocator),
1247 };
1248
1249 tc.addSourceFile(filename, source);
1250 var arg_i: usize = 0;
1251 while (arg_i < expected_lines.len) : (arg_i += 1) {
1252 tc.addExpectedLine(expected_lines[arg_i]);
1253 }
1254 return tc;
1255 }
1256
1257 pub fn add(self: *GenHContext, name: []const u8, source: []const u8, expected_lines: []const []const u8) void {
1258 const tc = self.create("test.zig", name, source, expected_lines);
1259 self.addCase(tc);
1260 }
12611054
1262 pub fn addCase(self: *GenHContext, case: *const TestCase) void {
1263 const b = self.b;
1055 const triple_prefix = c_abi_target.zigTriple(b.allocator) catch @panic("OOM");
1056 test_step.setName(b.fmt("test-c-abi-{s}-{s} ", .{
1057 triple_prefix, @tagName(optimize_mode),
1058 }));
12641059
1265 const optimize_mode = std.builtin.OptimizeMode.Debug;
1266 const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {s} ({s})", .{ case.name, @tagName(optimize_mode) }) catch unreachable;
1267 if (self.test_filter) |filter| {
1268 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
1060 const run = test_step.run();
1061 run.skip_foreign_checks = true;
1062 step.dependOn(&run.step);
12691063 }
1270
1271 const write_src = b.addWriteFiles();
1272 for (case.sources.items) |src_file| {
1273 write_src.add(src_file.filename, src_file.source);
1274 }
1275
1276 const obj = b.addObjectFromWriteFileStep("test", write_src, case.sources.items[0].filename);
1277 obj.setBuildMode(optimize_mode);
1278
1279 const cmp_h = GenHCmpOutputStep.create(self, obj, annotated_case_name, case);
1280
1281 self.step.dependOn(&cmp_h.step);
12821064 }
1283};
1284
1285fn printInvocation(args: []const []const u8) void {
1286 for (args) |arg| {
1287 std.debug.print("{s} ", .{arg});
1288 }
1289 std.debug.print("\n", .{});
1065 return step;
12901066}
12911067
1292const c_abi_targets = [_]CrossTarget{
1293 .{},
1294 .{
1295 .cpu_arch = .x86_64,
1296 .os_tag = .linux,
1297 .abi = .musl,
1298 },
1299 .{
1300 .cpu_arch = .x86,
1301 .os_tag = .linux,
1302 .abi = .musl,
1303 },
1304 .{
1305 .cpu_arch = .aarch64,
1306 .os_tag = .linux,
1307 .abi = .musl,
1308 },
1309 .{
1310 .cpu_arch = .arm,
1311 .os_tag = .linux,
1312 .abi = .musleabihf,
1313 },
1314 .{
1315 .cpu_arch = .mips,
1316 .os_tag = .linux,
1317 .abi = .musl,
1318 },
1319 .{
1320 .cpu_arch = .riscv64,
1321 .os_tag = .linux,
1322 .abi = .musl,
1323 },
1324 .{
1325 .cpu_arch = .wasm32,
1326 .os_tag = .wasi,
1327 .abi = .musl,
1328 },
1329 .{
1330 .cpu_arch = .powerpc,
1331 .os_tag = .linux,
1332 .abi = .musl,
1333 },
1334 .{
1335 .cpu_arch = .powerpc64le,
1336 .os_tag = .linux,
1337 .abi = .musl,
1338 },
1339 .{
1340 .cpu_arch = .x86,
1341 .os_tag = .windows,
1342 .abi = .gnu,
1343 },
1344 .{
1345 .cpu_arch = .x86_64,
1346 .os_tag = .windows,
1347 .abi = .gnu,
1348 },
1349};
1350
1351pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *Step {
1352 const step = b.step("test-c-abi", "Run the C ABI tests");
1353
1354 const optimize_modes: [2]OptimizeMode = .{ .Debug, .ReleaseFast };
1355
1356 for (optimize_modes[0 .. @as(u8, 1) + @boolToInt(!skip_release)]) |optimize_mode| for (c_abi_targets) |c_abi_target| {
1357 if (skip_non_native and !c_abi_target.isNative())
1358 continue;
1359
1360 const test_step = b.addTest(.{
1361 .root_source_file = .{ .path = "test/c_abi/main.zig" },
1362 .optimize = optimize_mode,
1363 .target = c_abi_target,
1364 });
1365 if (c_abi_target.abi != null and c_abi_target.abi.?.isMusl()) {
1366 // TODO NativeTargetInfo insists on dynamically linking musl
1367 // for some reason?
1368 test_step.target_info.dynamic_linker.max_byte = null;
1369 }
1370 test_step.linkLibC();
1371 test_step.addCSourceFile("test/c_abi/cfuncs.c", &.{"-std=c99"});
1372
1373 if (c_abi_target.isWindows() and (c_abi_target.getCpuArch() == .x86 or builtin.target.os.tag == .linux)) {
1374 // LTO currently incorrectly strips stdcall name-mangled functions
1375 // LLD crashes in LTO here when cross compiling for windows on linux
1376 test_step.want_lto = false;
1377 }
1378
1379 const triple_prefix = c_abi_target.zigTriple(b.allocator) catch unreachable;
1380 test_step.setNamePrefix(b.fmt("{s}-{s}-{s} ", .{
1381 "test-c-abi",
1382 triple_prefix,
1383 @tagName(optimize_mode),
1384 }));
1385
1386 step.dependOn(&test_step.step);
1387 };
1388 return step;
1068pub fn addCases(
1069 b: *std.Build,
1070 parent_step: *Step,
1071 opt_test_filter: ?[]const u8,
1072 check_case_exe: *std.Build.CompileStep,
1073) !void {
1074 const arena = b.allocator;
1075 const gpa = b.allocator;
1076
1077 var cases = @import("src/Cases.zig").init(gpa, arena);
1078
1079 var dir = try b.build_root.handle.openIterableDir("test/cases", .{});
1080 defer dir.close();
1081
1082 cases.addFromDir(dir);
1083 try @import("cases.zig").addCases(&cases);
1084
1085 const cases_dir_path = try b.build_root.join(b.allocator, &.{ "test", "cases" });
1086 cases.lowerToBuildSteps(
1087 b,
1088 parent_step,
1089 opt_test_filter,
1090 cases_dir_path,
1091 check_case_exe,
1092 );
13891093}