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...@@ -506,7 +506,9 @@ set(ZIG_STAGE2_SOURCES
506 "${CMAKE_SOURCE_DIR}/lib/std/Thread.zig"506 "${CMAKE_SOURCE_DIR}/lib/std/Thread.zig"
507 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Futex.zig"507 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Futex.zig"
508 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.zig"508 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Mutex.zig"
509 "${CMAKE_SOURCE_DIR}/lib/std/Thread/Pool.zig"
509 "${CMAKE_SOURCE_DIR}/lib/std/Thread/ResetEvent.zig"510 "${CMAKE_SOURCE_DIR}/lib/std/Thread/ResetEvent.zig"
511 "${CMAKE_SOURCE_DIR}/lib/std/Thread/WaitGroup.zig"
510 "${CMAKE_SOURCE_DIR}/lib/std/time.zig"512 "${CMAKE_SOURCE_DIR}/lib/std/time.zig"
511 "${CMAKE_SOURCE_DIR}/lib/std/treap.zig"513 "${CMAKE_SOURCE_DIR}/lib/std/treap.zig"
512 "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig"514 "${CMAKE_SOURCE_DIR}/lib/std/unicode.zig"
...@@ -516,6 +518,7 @@ set(ZIG_STAGE2_SOURCES...@@ -516,6 +518,7 @@ set(ZIG_STAGE2_SOURCES
516 "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig"518 "${CMAKE_SOURCE_DIR}/lib/std/zig/c_builtins.zig"
517 "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"519 "${CMAKE_SOURCE_DIR}/lib/std/zig/Parse.zig"
518 "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"520 "${CMAKE_SOURCE_DIR}/lib/std/zig/render.zig"
521 "${CMAKE_SOURCE_DIR}/lib/std/zig/Server.zig"
519 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"522 "${CMAKE_SOURCE_DIR}/lib/std/zig/string_literal.zig"
520 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"523 "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig"
521 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"524 "${CMAKE_SOURCE_DIR}/lib/std/zig/system/NativePaths.zig"
...@@ -530,9 +533,7 @@ set(ZIG_STAGE2_SOURCES...@@ -530,9 +533,7 @@ set(ZIG_STAGE2_SOURCES
530 "${CMAKE_SOURCE_DIR}/src/Package.zig"533 "${CMAKE_SOURCE_DIR}/src/Package.zig"
531 "${CMAKE_SOURCE_DIR}/src/RangeSet.zig"534 "${CMAKE_SOURCE_DIR}/src/RangeSet.zig"
532 "${CMAKE_SOURCE_DIR}/src/Sema.zig"535 "${CMAKE_SOURCE_DIR}/src/Sema.zig"
533 "${CMAKE_SOURCE_DIR}/src/ThreadPool.zig"
534 "${CMAKE_SOURCE_DIR}/src/TypedValue.zig"536 "${CMAKE_SOURCE_DIR}/src/TypedValue.zig"
535 "${CMAKE_SOURCE_DIR}/src/WaitGroup.zig"
536 "${CMAKE_SOURCE_DIR}/src/Zir.zig"537 "${CMAKE_SOURCE_DIR}/src/Zir.zig"
537 "${CMAKE_SOURCE_DIR}/src/arch/aarch64/CodeGen.zig"538 "${CMAKE_SOURCE_DIR}/src/arch/aarch64/CodeGen.zig"
538 "${CMAKE_SOURCE_DIR}/src/arch/aarch64/Emit.zig"539 "${CMAKE_SOURCE_DIR}/src/arch/aarch64/Emit.zig"
build.zig+109-108
...@@ -31,6 +31,11 @@ pub fn build(b: *std.Build) !void {...@@ -31,6 +31,11 @@ pub fn build(b: *std.Build) !void {
31 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;31 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
33 const test_step = b.step("test", "Run all the tests");33 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
35 const docgen_exe = b.addExecutable(.{40 const docgen_exe = b.addExecutable(.{
36 .name = "docgen",41 .name = "docgen",
...@@ -40,28 +45,32 @@ pub fn build(b: *std.Build) !void {...@@ -40,28 +45,32 @@ pub fn build(b: *std.Build) !void {
40 });45 });
41 docgen_exe.single_threaded = single_threaded;46 docgen_exe.single_threaded = single_threaded;
4247
43 const langref_out_path = try b.cache_root.join(b.allocator, &.{"langref.html"});48 const docgen_cmd = b.addRunArtifact(docgen_exe);
44 const docgen_cmd = docgen_exe.run();49 docgen_cmd.addArgs(&.{ "--zig", b.zig_exe });
45 docgen_cmd.addArgs(&[_][]const u8{50 docgen_cmd.addFileSourceArg(.{ .path = "doc/langref.html.in" });
46 "--zig",51 const langref_file = docgen_cmd.addOutputFileArg("langref.html");
47 b.zig_exe,52 const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "doc/langref.html");
48 "doc" ++ fs.path.sep_str ++ "langref.html.in",53 if (!skip_install_lib_files) {
49 langref_out_path,54 b.getInstallStep().dependOn(&install_langref.step);
50 });55 }
51 docgen_cmd.step.dependOn(&docgen_exe.step);
5256
53 const docs_step = b.step("docs", "Build documentation");57 const docs_step = b.step("docs", "Build documentation");
54 docs_step.dependOn(&docgen_cmd.step);58 docs_step.dependOn(&docgen_cmd.step);
5559
56 const test_cases = b.addTest(.{60 // This is for legacy reasons, to be removed after our CI scripts are upgraded to use
57 .root_source_file = .{ .path = "src/test.zig" },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" },
58 .optimize = optimize,69 .optimize = optimize,
59 });70 });
60 test_cases.main_pkg_path = ".";71 check_case_exe.main_pkg_path = ".";
61 test_cases.stack_size = stack_size;72 check_case_exe.stack_size = stack_size;
62 test_cases.single_threaded = single_threaded;73 check_case_exe.single_threaded = single_threaded;
63
64 const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"});
6574
66 const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false;75 const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false;
67 const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false;76 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 {...@@ -74,11 +83,6 @@ pub fn build(b: *std.Build) !void {
74 const skip_stage1 = b.option(bool, "skip-stage1", "Main test suite skips stage1 compile error tests") orelse false;83 const skip_stage1 = b.option(bool, "skip-stage1", "Main test suite skips stage1 compile error tests") orelse false;
75 const skip_run_translated_c = b.option(bool, "skip-run-translated-c", "Main test suite skips run-translated-c tests") orelse false;84 const skip_run_translated_c = b.option(bool, "skip-run-translated-c", "Main test suite skips run-translated-c tests") orelse false;
76 const skip_stage2_tests = b.option(bool, "skip-stage2-tests", "Main test suite skips self-hosted compiler tests") orelse false;85 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
83 const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;87 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 {...@@ -175,13 +179,12 @@ pub fn build(b: *std.Build) !void {
175 test_step.dependOn(&exe.step);179 test_step.dependOn(&exe.step);
176 }180 }
177181
178 b.default_step.dependOn(&exe.step);
179 exe.single_threaded = single_threaded;182 exe.single_threaded = single_threaded;
180183
181 if (target.isWindows() and target.getAbi() == .gnu) {184 if (target.isWindows() and target.getAbi() == .gnu) {
182 // LTO is currently broken on mingw, this can be removed when it's fixed.185 // LTO is currently broken on mingw, this can be removed when it's fixed.
183 exe.want_lto = false;186 exe.want_lto = false;
184 test_cases.want_lto = false;187 check_case_exe.want_lto = false;
185 }188 }
186189
187 const exe_options = b.addOptions();190 const exe_options = b.addOptions();
...@@ -195,11 +198,11 @@ pub fn build(b: *std.Build) !void {...@@ -195,11 +198,11 @@ pub fn build(b: *std.Build) !void {
195 exe_options.addOption(bool, "llvm_has_arc", llvm_has_arc);198 exe_options.addOption(bool, "llvm_has_arc", llvm_has_arc);
196 exe_options.addOption(bool, "force_gpa", force_gpa);199 exe_options.addOption(bool, "force_gpa", force_gpa);
197 exe_options.addOption(bool, "only_c", only_c);200 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
200 if (link_libc) {203 if (link_libc) {
201 exe.linkLibC();204 exe.linkLibC();
202 test_cases.linkLibC();205 check_case_exe.linkLibC();
203 }206 }
204207
205 const is_debug = optimize == .Debug;208 const is_debug = optimize == .Debug;
...@@ -285,14 +288,14 @@ pub fn build(b: *std.Build) !void {...@@ -285,14 +288,14 @@ pub fn build(b: *std.Build) !void {
285 }288 }
286289
287 try addCmakeCfgOptionsToExe(b, cfg, exe, use_zig_libcxx);290 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);
289 } else {292 } else {
290 // Here we are -Denable-llvm but no cmake integration.293 // Here we are -Denable-llvm but no cmake integration.
291 try addStaticLlvmOptionsToExe(exe);294 try addStaticLlvmOptionsToExe(exe);
292 try addStaticLlvmOptionsToExe(test_cases);295 try addStaticLlvmOptionsToExe(check_case_exe);
293 }296 }
294 if (target.isWindows()) {297 if (target.isWindows()) {
295 inline for (.{ exe, test_cases }) |artifact| {298 inline for (.{ exe, check_case_exe }) |artifact| {
296 artifact.linkSystemLibrary("version");299 artifact.linkSystemLibrary("version");
297 artifact.linkSystemLibrary("uuid");300 artifact.linkSystemLibrary("uuid");
298 artifact.linkSystemLibrary("ole32");301 artifact.linkSystemLibrary("ole32");
...@@ -337,8 +340,9 @@ pub fn build(b: *std.Build) !void {...@@ -337,8 +340,9 @@ pub fn build(b: *std.Build) !void {
337 const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");340 const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
338341
339 const test_cases_options = b.addOptions();342 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);
342 test_cases_options.addOption(bool, "enable_logging", enable_logging);346 test_cases_options.addOption(bool, "enable_logging", enable_logging);
343 test_cases_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);347 test_cases_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);
344 test_cases_options.addOption(bool, "skip_non_native", skip_non_native);348 test_cases_options.addOption(bool, "skip_non_native", skip_non_native);
...@@ -361,12 +365,6 @@ pub fn build(b: *std.Build) !void {...@@ -361,12 +365,6 @@ pub fn build(b: *std.Build) !void {
361 test_cases_options.addOption(std.SemanticVersion, "semver", semver);365 test_cases_options.addOption(std.SemanticVersion, "semver", semver);
362 test_cases_options.addOption(?[]const u8, "test_filter", test_filter);366 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
370 var chosen_opt_modes_buf: [4]builtin.Mode = undefined;368 var chosen_opt_modes_buf: [4]builtin.Mode = undefined;
371 var chosen_mode_index: usize = 0;369 var chosen_mode_index: usize = 0;
372 if (!skip_debug) {370 if (!skip_debug) {
...@@ -387,96 +385,101 @@ pub fn build(b: *std.Build) !void {...@@ -387,96 +385,101 @@ pub fn build(b: *std.Build) !void {
387 }385 }
388 const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index];386 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 works388 const fmt_include_paths = &.{ "doc", "lib", "src", "test", "tools", "build.zig" };
391 test_step.dependOn(&fmt_build_zig.step);389 const fmt_exclude_paths = &.{"test/cases"};
392 const fmt_step = b.step("test-fmt", "Run zig fmt against build.zig to make sure it works");390 const do_fmt = b.addFmt(.{
393 fmt_step.dependOn(&fmt_build_zig.step);391 .paths = fmt_include_paths,
394392 .exclude_paths = fmt_exclude_paths,
395 test_step.dependOn(tests.addPkgTests(393 });
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 ));
408394
409 test_step.dependOn(tests.addPkgTests(395 b.step("test-fmt", "Check source files having conforming formatting").dependOn(&b.addFmt(.{
410 b,396 .paths = fmt_include_paths,
411 test_filter,397 .exclude_paths = fmt_exclude_paths,
412 "lib/compiler_rt.zig",398 .check = true,
413 "compiler-rt",399 }).step);
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 ));
422400
423 test_step.dependOn(tests.addPkgTests(401 const test_cases_step = b.step("test-cases", "Run the main compiler test cases");
424 b,402 try tests.addCases(b, test_cases_step, test_filter, check_case_exe);
425 test_filter,403 if (!skip_stage2_tests) test_step.dependOn(test_cases_step);
426 "lib/c.zig",404
427 "universal-libc",405 test_step.dependOn(tests.addModuleTests(b, .{
428 "Run the universal libc tests",406 .test_filter = test_filter,
429 optimization_modes,407 .root_src = "test/behavior.zig",
430 true, // skip_single_threaded408 .name = "behavior",
431 skip_non_native,409 .desc = "Run the behavior tests",
432 true, // skip_libc410 .optimize_modes = optimization_modes,
433 skip_stage1,411 .skip_single_threaded = skip_single_threaded,
434 skip_stage2_tests or true, // TODO get these all passing412 .skip_non_native = skip_non_native,
435 ));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
437 test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes));445 test_step.dependOn(tests.addCompareOutputTests(b, test_filter, optimization_modes));
438 test_step.dependOn(tests.addStandaloneTests(446 test_step.dependOn(tests.addStandaloneTests(
439 b,447 b,
440 test_filter,
441 optimization_modes,448 optimization_modes,
442 skip_non_native,
443 enable_macos_sdk,449 enable_macos_sdk,
444 target,
445 skip_stage2_tests,450 skip_stage2_tests,
446 b.enable_darling,
447 b.enable_qemu,
448 b.enable_rosetta,
449 b.enable_wasmtime,
450 b.enable_wine,
451 enable_symlinks_windows,451 enable_symlinks_windows,
452 ));452 ));
453 test_step.dependOn(tests.addCAbiTests(b, skip_non_native, skip_release));453 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));
455 test_step.dependOn(tests.addStackTraceTests(b, test_filter, optimization_modes));455 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));
457 test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes));457 test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, optimization_modes));
458 test_step.dependOn(tests.addTranslateCTests(b, test_filter));458 test_step.dependOn(tests.addTranslateCTests(b, test_filter));
459 if (!skip_run_translated_c) {459 if (!skip_run_translated_c) {
460 test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter, target));460 test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter, target));
461 }461 }
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(463 test_step.dependOn(tests.addModuleTests(b, .{
466 b,464 .test_filter = test_filter,
467 test_filter,465 .root_src = "lib/std/std.zig",
468 "lib/std/std.zig",466 .name = "std",
469 "std",467 .desc = "Run the standard library tests",
470 "Run the standard library tests",468 .optimize_modes = optimization_modes,
471 optimization_modes,469 .skip_single_threaded = skip_single_threaded,
472 skip_single_threaded,470 .skip_non_native = skip_non_native,
473 skip_non_native,471 .skip_libc = skip_libc,
474 skip_libc,472 .skip_stage1 = skip_stage1,
475 skip_stage1,473 .skip_stage2 = true, // TODO get all these passing
476 true, // TODO get these all passing474 // I observed a value of 3398275072 on my M1, and multiplied by 1.1 to
477 ));475 // get this amount:
476 .max_rss = 3738102579,
477 }));
478478
479 try addWasiUpdateStep(b, version);479 try addWasiUpdateStep(b, version);
480
481 b.step("fmt", "Modify source files in place to have conforming formatting")
482 .dependOn(&do_fmt.step);
480}483}
481484
482fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {485fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
...@@ -505,6 +508,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {...@@ -505,6 +508,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
505 exe_options.addOption(bool, "enable_tracy_callstack", false);508 exe_options.addOption(bool, "enable_tracy_callstack", false);
506 exe_options.addOption(bool, "enable_tracy_allocation", false);509 exe_options.addOption(bool, "enable_tracy_allocation", false);
507 exe_options.addOption(bool, "value_tracing", false);510 exe_options.addOption(bool, "value_tracing", false);
511 exe_options.addOption(bool, "omit_pkg_fetching_code", true);
508512
509 const run_opt = b.addSystemCommand(&.{ "wasm-opt", "-Oz", "--enable-bulk-memory" });513 const run_opt = b.addSystemCommand(&.{ "wasm-opt", "-Oz", "--enable-bulk-memory" });
510 run_opt.addArtifactArg(exe);514 run_opt.addArtifactArg(exe);
...@@ -676,10 +680,7 @@ fn addCxxKnownPath(...@@ -676,10 +680,7 @@ fn addCxxKnownPath(
676) !void {680) !void {
677 if (!std.process.can_spawn)681 if (!std.process.can_spawn)
678 return error.RequiredLibraryNotFound;682 return error.RequiredLibraryNotFound;
679 const path_padded = try b.exec(&[_][]const u8{683 const path_padded = b.exec(&.{ ctx.cxx_compiler, b.fmt("-print-file-name={s}", .{objname}) });
680 ctx.cxx_compiler,
681 b.fmt("-print-file-name={s}", .{objname}),
682 });
683 var tokenizer = mem.tokenize(u8, path_padded, "\r\n");684 var tokenizer = mem.tokenize(u8, path_padded, "\r\n");
684 const path_unpadded = tokenizer.next().?;685 const path_unpadded = tokenizer.next().?;
685 if (mem.eql(u8, path_unpadded, objname)) {686 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 \...@@ -67,7 +67,7 @@ stage3-debug/bin/zig build test docs \
67 --zig-lib-dir "$(pwd)/../lib"67 --zig-lib-dir "$(pwd)/../lib"
6868
69# Look for HTML errors.69# 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
72# Produce the experimental std lib documentation.72# Produce the experimental std lib documentation.
73stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib73stage3-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 \...@@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \
67 --zig-lib-dir "$(pwd)/../lib"67 --zig-lib-dir "$(pwd)/../lib"
6868
69# Look for HTML errors.69# 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
72# Produce the experimental std lib documentation.72# Produce the experimental std lib documentation.
73stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib73stage3-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 \...@@ -66,7 +66,7 @@ stage3-debug/bin/zig build test docs \
66 --zig-lib-dir "$(pwd)/../lib"66 --zig-lib-dir "$(pwd)/../lib"
6767
68# Look for HTML errors.68# 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
71# Produce the experimental std lib documentation.71# Produce the experimental std lib documentation.
72stage3-debug/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib72stage3-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 \...@@ -67,7 +67,7 @@ stage3-release/bin/zig build test docs \
67 --zig-lib-dir "$(pwd)/../lib"67 --zig-lib-dir "$(pwd)/../lib"
6868
69# Look for HTML errors.69# 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
72# Produce the experimental std lib documentation.72# Produce the experimental std lib documentation.
73stage3-release/bin/zig test ../lib/std/std.zig -femit-docs -fno-emit-bin --zig-lib-dir ../lib73stage3-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(...@@ -1270,7 +1270,7 @@ fn genHtml(
1270 zig_exe: []const u8,1270 zig_exe: []const u8,
1271 do_code_tests: bool,1271 do_code_tests: bool,
1272) !void {1272) !void {
1273 var progress = Progress{};1273 var progress = Progress{ .dont_print_on_dumb = true };
1274 const root_node = progress.start("Generating docgen examples", toc.nodes.len);1274 const root_node = progress.start("Generating docgen examples", toc.nodes.len);
1275 defer root_node.end();1275 defer root_node.end();
12761276
lib/build_runner.zig+702-53
...@@ -1,12 +1,14 @@...@@ -1,12 +1,14 @@
1const root = @import("@build");1const root = @import("@build");
2const std = @import("std");2const std = @import("std");
3const builtin = @import("builtin");3const builtin = @import("builtin");
4const assert = std.debug.assert;
4const io = std.io;5const io = std.io;
5const fmt = std.fmt;6const fmt = std.fmt;
6const mem = std.mem;7const mem = std.mem;
7const process = std.process;8const process = std.process;
8const ArrayList = std.ArrayList;9const ArrayList = std.ArrayList;
9const File = std.fs.File;10const File = std.fs.File;
11const Step = std.Build.Step;
1012
11pub const dependencies = @import("@dependencies");13pub const dependencies = @import("@dependencies");
1214
...@@ -14,12 +16,15 @@ pub fn main() !void {...@@ -14,12 +16,15 @@ pub fn main() !void {
14 // Here we use an ArenaAllocator backed by a DirectAllocator because a build is a short-lived,16 // Here we use an ArenaAllocator backed by a DirectAllocator because a build is a short-lived,
15 // one shot program. We don't need to waste time freeing memory and finding places to squish17 // one shot program. We don't need to waste time freeing memory and finding places to squish
16 // bytes into. So we free everything all at once at the very end.18 // bytes into. So we free everything all at once at the very end.
17 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);19 var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
18 defer arena.deinit();20 defer single_threaded_arena.deinit();
1921
20 const allocator = arena.allocator();22 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
21 var args = try process.argsAlloc(allocator);23 .child_allocator = single_threaded_arena.allocator(),
22 defer process.argsFree(allocator, args);24 };
25 const arena = thread_safe_arena.allocator();
26
27 var args = try process.argsAlloc(arena);
2328
24 // skip my own exe name29 // skip my own exe name
25 var arg_idx: usize = 1;30 var arg_idx: usize = 1;
...@@ -59,18 +64,17 @@ pub fn main() !void {...@@ -59,18 +64,17 @@ pub fn main() !void {
59 };64 };
6065
61 var cache: std.Build.Cache = .{66 var cache: std.Build.Cache = .{
62 .gpa = allocator,67 .gpa = arena,
63 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),68 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
64 };69 };
65 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });70 cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
66 cache.addPrefix(build_root_directory);71 cache.addPrefix(build_root_directory);
67 cache.addPrefix(local_cache_directory);72 cache.addPrefix(local_cache_directory);
68 cache.addPrefix(global_cache_directory);73 cache.addPrefix(global_cache_directory);
6974 cache.hash.addBytes(builtin.zig_version_string);
70 //cache.hash.addBytes(builtin.zig_version);
7175
72 const builder = try std.Build.create(76 const builder = try std.Build.create(
73 allocator,77 arena,
74 zig_exe,78 zig_exe,
75 build_root_directory,79 build_root_directory,
76 local_cache_directory,80 local_cache_directory,
...@@ -80,35 +84,34 @@ pub fn main() !void {...@@ -80,35 +84,34 @@ pub fn main() !void {
80 );84 );
81 defer builder.destroy();85 defer builder.destroy();
8286
83 var targets = ArrayList([]const u8).init(allocator);87 var targets = ArrayList([]const u8).init(arena);
84 var debug_log_scopes = ArrayList([]const u8).init(allocator);88 var debug_log_scopes = ArrayList([]const u8).init(arena);
8589 var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena };
86 const stderr_stream = io.getStdErr().writer();
87 const stdout_stream = io.getStdOut().writer();
8890
89 var install_prefix: ?[]const u8 = null;91 var install_prefix: ?[]const u8 = null;
90 var dir_list = std.Build.DirList{};92 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 variable97 const stderr_stream = io.getStdErr().writer();
93 // if it exists, default the color setting to .off98 const stdout_stream = io.getStdOut().writer();
94 // explicit --color arguments will still override this setting.
95 builder.color = if (std.process.hasEnvVarConstant("NO_COLOR")) .off else .auto;
9699
97 while (nextArg(args, &arg_idx)) |arg| {100 while (nextArg(args, &arg_idx)) |arg| {
98 if (mem.startsWith(u8, arg, "-D")) {101 if (mem.startsWith(u8, arg, "-D")) {
99 const option_contents = arg[2..];102 const option_contents = arg[2..];
100 if (option_contents.len == 0) {103 if (option_contents.len == 0) {
101 std.debug.print("Expected option name after '-D'\n\n", .{});104 std.debug.print("Expected option name after '-D'\n\n", .{});
102 return usageAndErr(builder, false, stderr_stream);105 usageAndErr(builder, false, stderr_stream);
103 }106 }
104 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {107 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
105 const option_name = option_contents[0..name_end];108 const option_name = option_contents[0..name_end];
106 const option_value = option_contents[name_end + 1 ..];109 const option_value = option_contents[name_end + 1 ..];
107 if (try builder.addUserInputOption(option_name, option_value))110 if (try builder.addUserInputOption(option_name, option_value))
108 return usageAndErr(builder, false, stderr_stream);111 usageAndErr(builder, false, stderr_stream);
109 } else {112 } else {
110 if (try builder.addUserInputFlag(option_contents))113 if (try builder.addUserInputFlag(option_contents))
111 return usageAndErr(builder, false, stderr_stream);114 usageAndErr(builder, false, stderr_stream);
112 }115 }
113 } else if (mem.startsWith(u8, arg, "-")) {116 } else if (mem.startsWith(u8, arg, "-")) {
114 if (mem.eql(u8, arg, "--verbose")) {117 if (mem.eql(u8, arg, "--verbose")) {
...@@ -118,69 +121,83 @@ pub fn main() !void {...@@ -118,69 +121,83 @@ pub fn main() !void {
118 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {121 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
119 install_prefix = nextArg(args, &arg_idx) orelse {122 install_prefix = nextArg(args, &arg_idx) orelse {
120 std.debug.print("Expected argument after {s}\n\n", .{arg});123 std.debug.print("Expected argument after {s}\n\n", .{arg});
121 return usageAndErr(builder, false, stderr_stream);124 usageAndErr(builder, false, stderr_stream);
122 };125 };
123 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {126 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
124 return steps(builder, false, stdout_stream);127 return steps(builder, false, stdout_stream);
125 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {128 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
126 dir_list.lib_dir = nextArg(args, &arg_idx) orelse {129 dir_list.lib_dir = nextArg(args, &arg_idx) orelse {
127 std.debug.print("Expected argument after {s}\n\n", .{arg});130 std.debug.print("Expected argument after {s}\n\n", .{arg});
128 return usageAndErr(builder, false, stderr_stream);131 usageAndErr(builder, false, stderr_stream);
129 };132 };
130 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {133 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
131 dir_list.exe_dir = nextArg(args, &arg_idx) orelse {134 dir_list.exe_dir = nextArg(args, &arg_idx) orelse {
132 std.debug.print("Expected argument after {s}\n\n", .{arg});135 std.debug.print("Expected argument after {s}\n\n", .{arg});
133 return usageAndErr(builder, false, stderr_stream);136 usageAndErr(builder, false, stderr_stream);
134 };137 };
135 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {138 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
136 dir_list.include_dir = nextArg(args, &arg_idx) orelse {139 dir_list.include_dir = nextArg(args, &arg_idx) orelse {
137 std.debug.print("Expected argument after {s}\n\n", .{arg});140 std.debug.print("Expected argument after {s}\n\n", .{arg});
138 return usageAndErr(builder, false, stderr_stream);141 usageAndErr(builder, false, stderr_stream);
139 };142 };
140 } else if (mem.eql(u8, arg, "--sysroot")) {143 } else if (mem.eql(u8, arg, "--sysroot")) {
141 const sysroot = nextArg(args, &arg_idx) orelse {144 const sysroot = nextArg(args, &arg_idx) orelse {
142 std.debug.print("Expected argument after --sysroot\n\n", .{});145 std.debug.print("Expected argument after --sysroot\n\n", .{});
143 return usageAndErr(builder, false, stderr_stream);146 usageAndErr(builder, false, stderr_stream);
144 };147 };
145 builder.sysroot = sysroot;148 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 };
146 } else if (mem.eql(u8, arg, "--search-prefix")) {161 } else if (mem.eql(u8, arg, "--search-prefix")) {
147 const search_prefix = nextArg(args, &arg_idx) orelse {162 const search_prefix = nextArg(args, &arg_idx) orelse {
148 std.debug.print("Expected argument after --search-prefix\n\n", .{});163 std.debug.print("Expected argument after --search-prefix\n\n", .{});
149 return usageAndErr(builder, false, stderr_stream);164 usageAndErr(builder, false, stderr_stream);
150 };165 };
151 builder.addSearchPrefix(search_prefix);166 builder.addSearchPrefix(search_prefix);
152 } else if (mem.eql(u8, arg, "--libc")) {167 } else if (mem.eql(u8, arg, "--libc")) {
153 const libc_file = nextArg(args, &arg_idx) orelse {168 const libc_file = nextArg(args, &arg_idx) orelse {
154 std.debug.print("Expected argument after --libc\n\n", .{});169 std.debug.print("Expected argument after --libc\n\n", .{});
155 return usageAndErr(builder, false, stderr_stream);170 usageAndErr(builder, false, stderr_stream);
156 };171 };
157 builder.libc_file = libc_file;172 builder.libc_file = libc_file;
158 } else if (mem.eql(u8, arg, "--color")) {173 } else if (mem.eql(u8, arg, "--color")) {
159 const next_arg = nextArg(args, &arg_idx) orelse {174 const next_arg = nextArg(args, &arg_idx) orelse {
160 std.debug.print("expected [auto|on|off] after --color", .{});175 std.debug.print("expected [auto|on|off] after --color", .{});
161 return usageAndErr(builder, false, stderr_stream);176 usageAndErr(builder, false, stderr_stream);
162 };177 };
163 builder.color = std.meta.stringToEnum(@TypeOf(builder.color), next_arg) orelse {178 color = std.meta.stringToEnum(Color, next_arg) orelse {
164 std.debug.print("expected [auto|on|off] after --color, found '{s}'", .{next_arg});179 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);
166 };181 };
167 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {182 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
168 builder.zig_lib_dir = nextArg(args, &arg_idx) orelse {183 builder.zig_lib_dir = nextArg(args, &arg_idx) orelse {
169 std.debug.print("Expected argument after --zig-lib-dir\n\n", .{});184 std.debug.print("Expected argument after --zig-lib-dir\n\n", .{});
170 return usageAndErr(builder, false, stderr_stream);185 usageAndErr(builder, false, stderr_stream);
171 };186 };
172 } else if (mem.eql(u8, arg, "--debug-log")) {187 } else if (mem.eql(u8, arg, "--debug-log")) {
173 const next_arg = nextArg(args, &arg_idx) orelse {188 const next_arg = nextArg(args, &arg_idx) orelse {
174 std.debug.print("Expected argument after {s}\n\n", .{arg});189 std.debug.print("Expected argument after {s}\n\n", .{arg});
175 return usageAndErr(builder, false, stderr_stream);190 usageAndErr(builder, false, stderr_stream);
176 };191 };
177 try debug_log_scopes.append(next_arg);192 try debug_log_scopes.append(next_arg);
193 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
194 builder.debug_pkg_config = true;
178 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {195 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
179 builder.debug_compile_errors = true;196 builder.debug_compile_errors = true;
180 } else if (mem.eql(u8, arg, "--glibc-runtimes")) {197 } else if (mem.eql(u8, arg, "--glibc-runtimes")) {
181 builder.glibc_runtimes_dir = nextArg(args, &arg_idx) orelse {198 builder.glibc_runtimes_dir = nextArg(args, &arg_idx) orelse {
182 std.debug.print("Expected argument after --glibc-runtimes\n\n", .{});199 std.debug.print("Expected argument after --glibc-runtimes\n\n", .{});
183 return usageAndErr(builder, false, stderr_stream);200 usageAndErr(builder, false, stderr_stream);
184 };201 };
185 } else if (mem.eql(u8, arg, "--verbose-link")) {202 } else if (mem.eql(u8, arg, "--verbose-link")) {
186 builder.verbose_link = true;203 builder.verbose_link = true;
...@@ -194,8 +211,6 @@ pub fn main() !void {...@@ -194,8 +211,6 @@ pub fn main() !void {
194 builder.verbose_cc = true;211 builder.verbose_cc = true;
195 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {212 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
196 builder.verbose_llvm_cpu_features = true;213 builder.verbose_llvm_cpu_features = true;
197 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
198 builder.prominent_compile_errors = true;
199 } else if (mem.eql(u8, arg, "-fwine")) {214 } else if (mem.eql(u8, arg, "-fwine")) {
200 builder.enable_wine = true;215 builder.enable_wine = true;
201 } else if (mem.eql(u8, arg, "-fno-wine")) {216 } else if (mem.eql(u8, arg, "-fno-wine")) {
...@@ -216,6 +231,10 @@ pub fn main() !void {...@@ -216,6 +231,10 @@ pub fn main() !void {
216 builder.enable_darling = true;231 builder.enable_darling = true;
217 } else if (mem.eql(u8, arg, "-fno-darling")) {232 } else if (mem.eql(u8, arg, "-fno-darling")) {
218 builder.enable_darling = false;233 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;
219 } else if (mem.eql(u8, arg, "-freference-trace")) {238 } else if (mem.eql(u8, arg, "-freference-trace")) {
220 builder.reference_trace = 256;239 builder.reference_trace = 256;
221 } else if (mem.startsWith(u8, arg, "-freference-trace=")) {240 } else if (mem.startsWith(u8, arg, "-freference-trace=")) {
...@@ -226,39 +245,639 @@ pub fn main() !void {...@@ -226,39 +245,639 @@ pub fn main() !void {
226 };245 };
227 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {246 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
228 builder.reference_trace = null;247 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;
229 } else if (mem.eql(u8, arg, "--")) {261 } else if (mem.eql(u8, arg, "--")) {
230 builder.args = argsRest(args, arg_idx);262 builder.args = argsRest(args, arg_idx);
231 break;263 break;
232 } else {264 } else {
233 std.debug.print("Unrecognized argument: {s}\n\n", .{arg});265 std.debug.print("Unrecognized argument: {s}\n\n", .{arg});
234 return usageAndErr(builder, false, stderr_stream);266 usageAndErr(builder, false, stderr_stream);
235 }267 }
236 } else {268 } else {
237 try targets.append(arg);269 try targets.append(arg);
238 }270 }
239 }271 }
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
241 builder.debug_log_scopes = debug_log_scopes.items;284 builder.debug_log_scopes = debug_log_scopes.items;
242 builder.resolveInstallPrefix(install_prefix, dir_list);285 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
245 if (builder.validateUserInputDidItFail())292 if (builder.validateUserInputDidItFail())
246 return usageAndErr(builder, true, stderr_stream);293 usageAndErr(builder, true, stderr_stream);
247294
248 builder.make(targets.items) catch |err| {295 var run: Run = .{
249 switch (err) {296 .max_rss = max_rss,
250 error.InvalidStepName => {297 .max_rss_is_default = false,
251 return usageAndErr(builder, true, stderr_stream);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 }
252 },463 },
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,
260 }464 }
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 }
262}881}
263882
264fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !void {883fn 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...@@ -269,7 +888,7 @@ fn steps(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
269 }888 }
270889
271 const allocator = builder.allocator;890 const allocator = builder.allocator;
272 for (builder.top_level_steps.items) |top_level_step| {891 for (builder.top_level_steps.values()) |top_level_step| {
273 const name = if (&top_level_step.step == builder.default_step)892 const name = if (&top_level_step.step == builder.default_step)
274 try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})893 try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
275 else894 else
...@@ -327,6 +946,10 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi...@@ -327,6 +946,10 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
327 \\ --verbose Print commands before executing them946 \\ --verbose Print commands before executing them
328 \\ --color [auto|off|on] Enable or disable colored error messages947 \\ --color [auto|off|on] Enable or disable colored error messages
329 \\ --prominent-compile-errors Output compile errors formatted for a human to read948 \\ --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)
330 \\953 \\
331 \\Project-Specific Options:954 \\Project-Specific Options:
332 \\955 \\
...@@ -364,6 +987,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi...@@ -364,6 +987,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
364 \\ --zig-lib-dir [arg] Override path to Zig lib directory987 \\ --zig-lib-dir [arg] Override path to Zig lib directory
365 \\ --build-runner [file] Override path to build runner988 \\ --build-runner [file] Override path to build runner
366 \\ --debug-log [scope] Enable debugging the compiler989 \\ --debug-log [scope] Enable debugging the compiler
990 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
367 \\ --verbose-link Enable compiler debug output for linking991 \\ --verbose-link Enable compiler debug output for linking
368 \\ --verbose-air Enable compiler debug output for Zig AIR992 \\ --verbose-air Enable compiler debug output for Zig AIR
369 \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR993 \\ --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...@@ -374,7 +998,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
374 );998 );
375}999}
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 {
378 usage(builder, already_ran_build, out_stream) catch {};1002 usage(builder, already_ran_build, out_stream) catch {};
379 process.exit(1);1003 process.exit(1);
380}1004}
...@@ -389,3 +1013,28 @@ fn argsRest(args: [][]const u8, idx: usize) ?[][]const u8 {...@@ -389,3 +1013,28 @@ fn argsRest(args: [][]const u8, idx: usize) ?[][]const u8 {
389 if (idx >= args.len) return null;1013 if (idx >= args.len) return null;
390 return args[idx..];1014 return args[idx..];
391}1015}
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");...@@ -32,14 +32,12 @@ pub const Step = @import("Build/Step.zig");
32pub const CheckFileStep = @import("Build/CheckFileStep.zig");32pub const CheckFileStep = @import("Build/CheckFileStep.zig");
33pub const CheckObjectStep = @import("Build/CheckObjectStep.zig");33pub const CheckObjectStep = @import("Build/CheckObjectStep.zig");
34pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig");34pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig");
35pub const EmulatableRunStep = @import("Build/EmulatableRunStep.zig");
36pub const FmtStep = @import("Build/FmtStep.zig");35pub const FmtStep = @import("Build/FmtStep.zig");
37pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig");36pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig");
38pub const InstallDirStep = @import("Build/InstallDirStep.zig");37pub const InstallDirStep = @import("Build/InstallDirStep.zig");
39pub const InstallFileStep = @import("Build/InstallFileStep.zig");38pub const InstallFileStep = @import("Build/InstallFileStep.zig");
40pub const ObjCopyStep = @import("Build/ObjCopyStep.zig");39pub const ObjCopyStep = @import("Build/ObjCopyStep.zig");
41pub const CompileStep = @import("Build/CompileStep.zig");40pub const CompileStep = @import("Build/CompileStep.zig");
42pub const LogStep = @import("Build/LogStep.zig");
43pub const OptionsStep = @import("Build/OptionsStep.zig");41pub const OptionsStep = @import("Build/OptionsStep.zig");
44pub const RemoveDirStep = @import("Build/RemoveDirStep.zig");42pub const RemoveDirStep = @import("Build/RemoveDirStep.zig");
45pub const RunStep = @import("Build/RunStep.zig");43pub const RunStep = @import("Build/RunStep.zig");
...@@ -59,15 +57,12 @@ verbose_air: bool,...@@ -59,15 +57,12 @@ verbose_air: bool,
59verbose_llvm_ir: bool,57verbose_llvm_ir: bool,
60verbose_cimport: bool,58verbose_cimport: bool,
61verbose_llvm_cpu_features: bool,59verbose_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,
65reference_trace: ?u32 = null,60reference_trace: ?u32 = null,
66invalid_user_input: bool,61invalid_user_input: bool,
67zig_exe: []const u8,62zig_exe: []const u8,
68default_step: *Step,63default_step: *Step,
69env_map: *EnvMap,64env_map: *EnvMap,
70top_level_steps: ArrayList(*TopLevelStep),65top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep),
71install_prefix: []const u8,66install_prefix: []const u8,
72dest_dir: ?[]const u8,67dest_dir: ?[]const u8,
73lib_dir: []const u8,68lib_dir: []const u8,
...@@ -90,6 +85,7 @@ pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,...@@ -90,6 +85,7 @@ pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
90args: ?[][]const u8 = null,85args: ?[][]const u8 = null,
91debug_log_scopes: []const []const u8 = &.{},86debug_log_scopes: []const []const u8 = &.{},
92debug_compile_errors: bool = false,87debug_compile_errors: bool = false,
88debug_pkg_config: bool = false,
9389
94/// Experimental. Use system Darling installation to run cross compiled macOS build artifacts.90/// Experimental. Use system Darling installation to run cross compiled macOS build artifacts.
95enable_darling: bool = false,91enable_darling: bool = false,
...@@ -198,7 +194,7 @@ pub fn create(...@@ -198,7 +194,7 @@ pub fn create(
198 env_map.* = try process.getEnvMap(allocator);194 env_map.* = try process.getEnvMap(allocator);
199195
200 const self = try allocator.create(Build);196 const self = try allocator.create(Build);
201 self.* = Build{197 self.* = .{
202 .zig_exe = zig_exe,198 .zig_exe = zig_exe,
203 .build_root = build_root,199 .build_root = build_root,
204 .cache_root = cache_root,200 .cache_root = cache_root,
...@@ -211,13 +207,12 @@ pub fn create(...@@ -211,13 +207,12 @@ pub fn create(
211 .verbose_llvm_ir = false,207 .verbose_llvm_ir = false,
212 .verbose_cimport = false,208 .verbose_cimport = false,
213 .verbose_llvm_cpu_features = false,209 .verbose_llvm_cpu_features = false,
214 .prominent_compile_errors = false,
215 .invalid_user_input = false,210 .invalid_user_input = false,
216 .allocator = allocator,211 .allocator = allocator,
217 .user_input_options = UserInputOptionsMap.init(allocator),212 .user_input_options = UserInputOptionsMap.init(allocator),
218 .available_options_map = AvailableOptionsMap.init(allocator),213 .available_options_map = AvailableOptionsMap.init(allocator),
219 .available_options_list = ArrayList(AvailableOption).init(allocator),214 .available_options_list = ArrayList(AvailableOption).init(allocator),
220 .top_level_steps = ArrayList(*TopLevelStep).init(allocator),215 .top_level_steps = .{},
221 .default_step = undefined,216 .default_step = undefined,
222 .env_map = env_map,217 .env_map = env_map,
223 .search_prefixes = ArrayList([]const u8).init(allocator),218 .search_prefixes = ArrayList([]const u8).init(allocator),
...@@ -227,12 +222,21 @@ pub fn create(...@@ -227,12 +222,21 @@ pub fn create(
227 .h_dir = undefined,222 .h_dir = undefined,
228 .dest_dir = env_map.get("DESTDIR"),223 .dest_dir = env_map.get("DESTDIR"),
229 .installed_files = ArrayList(InstalledFile).init(allocator),224 .installed_files = ArrayList(InstalledFile).init(allocator),
230 .install_tls = TopLevelStep{225 .install_tls = .{
231 .step = Step.initNoOp(.top_level, "install", allocator),226 .step = Step.init(.{
227 .id = .top_level,
228 .name = "install",
229 .owner = self,
230 }),
232 .description = "Copy build artifacts to prefix path",231 .description = "Copy build artifacts to prefix path",
233 },232 },
234 .uninstall_tls = TopLevelStep{233 .uninstall_tls = .{
235 .step = Step.init(.top_level, "uninstall", allocator, makeUninstall),234 .step = Step.init(.{
235 .id = .top_level,
236 .name = "uninstall",
237 .owner = self,
238 .makeFn = makeUninstall,
239 }),
236 .description = "Remove build artifacts from prefix path",240 .description = "Remove build artifacts from prefix path",
237 },241 },
238 .zig_lib_dir = null,242 .zig_lib_dir = null,
...@@ -241,8 +245,8 @@ pub fn create(...@@ -241,8 +245,8 @@ pub fn create(
241 .host = host,245 .host = host,
242 .modules = std.StringArrayHashMap(*Module).init(allocator),246 .modules = std.StringArrayHashMap(*Module).init(allocator),
243 };247 };
244 try self.top_level_steps.append(&self.install_tls);248 try self.top_level_steps.put(allocator, self.install_tls.step.name, &self.install_tls);
245 try self.top_level_steps.append(&self.uninstall_tls);249 try self.top_level_steps.put(allocator, self.uninstall_tls.step.name, &self.uninstall_tls);
246 self.default_step = &self.install_tls.step;250 self.default_step = &self.install_tls.step;
247 return self;251 return self;
248}252}
...@@ -264,11 +268,20 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc...@@ -264,11 +268,20 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
264 child.* = .{268 child.* = .{
265 .allocator = allocator,269 .allocator = allocator,
266 .install_tls = .{270 .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 }),
268 .description = "Copy build artifacts to prefix path",276 .description = "Copy build artifacts to prefix path",
269 },277 },
270 .uninstall_tls = .{278 .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 }),
272 .description = "Remove build artifacts from prefix path",285 .description = "Remove build artifacts from prefix path",
273 },286 },
274 .user_input_options = UserInputOptionsMap.init(allocator),287 .user_input_options = UserInputOptionsMap.init(allocator),
...@@ -281,14 +294,12 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc...@@ -281,14 +294,12 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
281 .verbose_llvm_ir = parent.verbose_llvm_ir,294 .verbose_llvm_ir = parent.verbose_llvm_ir,
282 .verbose_cimport = parent.verbose_cimport,295 .verbose_cimport = parent.verbose_cimport,
283 .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,296 .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features,
284 .prominent_compile_errors = parent.prominent_compile_errors,
285 .color = parent.color,
286 .reference_trace = parent.reference_trace,297 .reference_trace = parent.reference_trace,
287 .invalid_user_input = false,298 .invalid_user_input = false,
288 .zig_exe = parent.zig_exe,299 .zig_exe = parent.zig_exe,
289 .default_step = undefined,300 .default_step = undefined,
290 .env_map = parent.env_map,301 .env_map = parent.env_map,
291 .top_level_steps = ArrayList(*TopLevelStep).init(allocator),302 .top_level_steps = .{},
292 .install_prefix = undefined,303 .install_prefix = undefined,
293 .dest_dir = parent.dest_dir,304 .dest_dir = parent.dest_dir,
294 .lib_dir = parent.lib_dir,305 .lib_dir = parent.lib_dir,
...@@ -306,6 +317,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc...@@ -306,6 +317,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
306 .zig_lib_dir = parent.zig_lib_dir,317 .zig_lib_dir = parent.zig_lib_dir,
307 .debug_log_scopes = parent.debug_log_scopes,318 .debug_log_scopes = parent.debug_log_scopes,
308 .debug_compile_errors = parent.debug_compile_errors,319 .debug_compile_errors = parent.debug_compile_errors,
320 .debug_pkg_config = parent.debug_pkg_config,
309 .enable_darling = parent.enable_darling,321 .enable_darling = parent.enable_darling,
310 .enable_qemu = parent.enable_qemu,322 .enable_qemu = parent.enable_qemu,
311 .enable_rosetta = parent.enable_rosetta,323 .enable_rosetta = parent.enable_rosetta,
...@@ -316,8 +328,8 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc...@@ -316,8 +328,8 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: Cache.Direc
316 .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),328 .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),
317 .modules = std.StringArrayHashMap(*Module).init(allocator),329 .modules = std.StringArrayHashMap(*Module).init(allocator),
318 };330 };
319 try child.top_level_steps.append(&child.install_tls);331 try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls);
320 try child.top_level_steps.append(&child.uninstall_tls);332 try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls);
321 child.default_step = &child.install_tls.step;333 child.default_step = &child.install_tls.step;
322 return child;334 return child;
323}335}
...@@ -372,27 +384,24 @@ fn applyArgs(b: *Build, args: anytype) !void {...@@ -372,27 +384,24 @@ fn applyArgs(b: *Build, args: anytype) !void {
372 },384 },
373 }385 }
374 }386 }
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;
376 // Random bytes to make unique. Refresh this with new random bytes when391 // Random bytes to make unique. Refresh this with new random bytes when
377 // implementation is modified in a non-backwards-compatible way.392 // implementation is modified in a non-backwards-compatible way.
378 var hash = Hasher.init("ZaEsvQ5ClaA2IdH9");393 hash.add(@as(u32, 0xd8cb0055));
379 hash.update(b.dep_prefix);394 hash.addBytes(b.dep_prefix);
380 // TODO additionally update the hash with `args`.395 // TODO additionally update the hash with `args`.
381396 const digest = hash.final();
382 var digest: [16]u8 = undefined;397 const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest });
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 });
389 b.resolveInstallPrefix(install_prefix, .{});398 b.resolveInstallPrefix(install_prefix, .{});
390}399}
391400
392pub fn destroy(self: *Build) void {401pub fn destroy(b: *Build) void {
393 self.env_map.deinit();402 b.env_map.deinit();
394 self.top_level_steps.deinit();403 b.top_level_steps.deinit(b.allocator);
395 self.allocator.destroy(self);404 b.allocator.destroy(b);
396}405}
397406
398/// This function is intended to be called by lib/build_runner.zig, not a build.zig file.407/// 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 {...@@ -441,6 +450,7 @@ pub const ExecutableOptions = struct {
441 target: CrossTarget = .{},450 target: CrossTarget = .{},
442 optimize: std.builtin.Mode = .Debug,451 optimize: std.builtin.Mode = .Debug,
443 linkage: ?CompileStep.Linkage = null,452 linkage: ?CompileStep.Linkage = null,
453 max_rss: usize = 0,
444};454};
445455
446pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {456pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {
...@@ -452,6 +462,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {...@@ -452,6 +462,7 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *CompileStep {
452 .optimize = options.optimize,462 .optimize = options.optimize,
453 .kind = .exe,463 .kind = .exe,
454 .linkage = options.linkage,464 .linkage = options.linkage,
465 .max_rss = options.max_rss,
455 });466 });
456}467}
457468
...@@ -460,6 +471,7 @@ pub const ObjectOptions = struct {...@@ -460,6 +471,7 @@ pub const ObjectOptions = struct {
460 root_source_file: ?FileSource = null,471 root_source_file: ?FileSource = null,
461 target: CrossTarget,472 target: CrossTarget,
462 optimize: std.builtin.Mode,473 optimize: std.builtin.Mode,
474 max_rss: usize = 0,
463};475};
464476
465pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {477pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {
...@@ -469,6 +481,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {...@@ -469,6 +481,7 @@ pub fn addObject(b: *Build, options: ObjectOptions) *CompileStep {
469 .target = options.target,481 .target = options.target,
470 .optimize = options.optimize,482 .optimize = options.optimize,
471 .kind = .obj,483 .kind = .obj,
484 .max_rss = options.max_rss,
472 });485 });
473}486}
474487
...@@ -478,6 +491,7 @@ pub const SharedLibraryOptions = struct {...@@ -478,6 +491,7 @@ pub const SharedLibraryOptions = struct {
478 version: ?std.builtin.Version = null,491 version: ?std.builtin.Version = null,
479 target: CrossTarget,492 target: CrossTarget,
480 optimize: std.builtin.Mode,493 optimize: std.builtin.Mode,
494 max_rss: usize = 0,
481};495};
482496
483pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {497pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {
...@@ -489,6 +503,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {...@@ -489,6 +503,7 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *CompileStep {
489 .version = options.version,503 .version = options.version,
490 .target = options.target,504 .target = options.target,
491 .optimize = options.optimize,505 .optimize = options.optimize,
506 .max_rss = options.max_rss,
492 });507 });
493}508}
494509
...@@ -498,6 +513,7 @@ pub const StaticLibraryOptions = struct {...@@ -498,6 +513,7 @@ pub const StaticLibraryOptions = struct {
498 target: CrossTarget,513 target: CrossTarget,
499 optimize: std.builtin.Mode,514 optimize: std.builtin.Mode,
500 version: ?std.builtin.Version = null,515 version: ?std.builtin.Version = null,
516 max_rss: usize = 0,
501};517};
502518
503pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {519pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
...@@ -509,25 +525,27 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {...@@ -509,25 +525,27 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *CompileStep {
509 .version = options.version,525 .version = options.version,
510 .target = options.target,526 .target = options.target,
511 .optimize = options.optimize,527 .optimize = options.optimize,
528 .max_rss = options.max_rss,
512 });529 });
513}530}
514531
515pub const TestOptions = struct {532pub const TestOptions = struct {
516 name: []const u8 = "test",533 name: []const u8 = "test",
517 kind: CompileStep.Kind = .@"test",
518 root_source_file: FileSource,534 root_source_file: FileSource,
519 target: CrossTarget = .{},535 target: CrossTarget = .{},
520 optimize: std.builtin.Mode = .Debug,536 optimize: std.builtin.Mode = .Debug,
521 version: ?std.builtin.Version = null,537 version: ?std.builtin.Version = null,
538 max_rss: usize = 0,
522};539};
523540
524pub fn addTest(b: *Build, options: TestOptions) *CompileStep {541pub fn addTest(b: *Build, options: TestOptions) *CompileStep {
525 return CompileStep.create(b, .{542 return CompileStep.create(b, .{
526 .name = options.name,543 .name = options.name,
527 .kind = options.kind,544 .kind = .@"test",
528 .root_source_file = options.root_source_file,545 .root_source_file = options.root_source_file,
529 .target = options.target,546 .target = options.target,
530 .optimize = options.optimize,547 .optimize = options.optimize,
548 .max_rss = options.max_rss,
531 });549 });
532}550}
533551
...@@ -536,6 +554,7 @@ pub const AssemblyOptions = struct {...@@ -536,6 +554,7 @@ pub const AssemblyOptions = struct {
536 source_file: FileSource,554 source_file: FileSource,
537 target: CrossTarget,555 target: CrossTarget,
538 optimize: std.builtin.Mode,556 optimize: std.builtin.Mode,
557 max_rss: usize = 0,
539};558};
540559
541pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {560pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
...@@ -545,6 +564,7 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {...@@ -545,6 +564,7 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
545 .root_source_file = null,564 .root_source_file = null,
546 .target = options.target,565 .target = options.target,
547 .optimize = options.optimize,566 .optimize = options.optimize,
567 .max_rss = options.max_rss,
548 });568 });
549 obj_step.addAssemblyFileSource(options.source_file.dupe(b));569 obj_step.addAssemblyFileSource(options.source_file.dupe(b));
550 return obj_step;570 return obj_step;
...@@ -605,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep {...@@ -605,16 +625,15 @@ pub fn addSystemCommand(self: *Build, argv: []const []const u8) *RunStep {
605/// Creates a `RunStep` with an executable built with `addExecutable`.625/// Creates a `RunStep` with an executable built with `addExecutable`.
606/// Add command line arguments with methods of `RunStep`.626/// Add command line arguments with methods of `RunStep`.
607pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep {627pub fn addRunArtifact(b: *Build, exe: *CompileStep) *RunStep {
608 assert(exe.kind == .exe or exe.kind == .test_exe);
609
610 // It doesn't have to be native. We catch that if you actually try to run it.628 // It doesn't have to be native. We catch that if you actually try to run it.
611 // Consider that this is declarative; the run step may not be run unless a user629 // Consider that this is declarative; the run step may not be run unless a user
612 // option is supplied.630 // 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}));
614 run_step.addArtifactArg(exe);632 run_step.addArtifactArg(exe);
615633
616 if (exe.kind == .test_exe) {634 if (exe.kind == .@"test") {
617 run_step.addArg(b.zig_exe);635 run_step.stdio = .zig_test;
636 run_step.addArgs(&.{"--listen=-"});
618 }637 }
619638
620 if (exe.vcpkg_bin_path) |path| {639 if (exe.vcpkg_bin_path) |path| {
...@@ -634,7 +653,11 @@ pub fn addConfigHeader(...@@ -634,7 +653,11 @@ pub fn addConfigHeader(
634 options: ConfigHeaderStep.Options,653 options: ConfigHeaderStep.Options,
635 values: anytype,654 values: anytype,
636) *ConfigHeaderStep {655) *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);
638 config_header_step.addValues(values);661 config_header_step.addValues(values);
639 return config_header_step;662 return config_header_step;
640}663}
...@@ -671,17 +694,8 @@ pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *Writ...@@ -671,17 +694,8 @@ pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *Writ
671 return write_file_step;694 return write_file_step;
672}695}
673696
674pub fn addWriteFiles(self: *Build) *WriteFileStep {697pub fn addWriteFiles(b: *Build) *WriteFileStep {
675 const write_file_step = self.allocator.create(WriteFileStep) catch @panic("OOM");698 return WriteFileStep.create(b);
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;
685}699}
686700
687pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {701pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {
...@@ -690,32 +704,14 @@ pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {...@@ -690,32 +704,14 @@ pub fn addRemoveDirTree(self: *Build, dir_path: []const u8) *RemoveDirStep {
690 return remove_dir_step;704 return remove_dir_step;
691}705}
692706
693pub fn addFmt(self: *Build, paths: []const []const u8) *FmtStep {707pub fn addFmt(b: *Build, options: FmtStep.Options) *FmtStep {
694 return FmtStep.create(self, paths);708 return FmtStep.create(b, options);
695}709}
696710
697pub fn addTranslateC(self: *Build, options: TranslateCStep.Options) *TranslateCStep {711pub fn addTranslateC(self: *Build, options: TranslateCStep.Options) *TranslateCStep {
698 return TranslateCStep.create(self, options);712 return TranslateCStep.create(self, options);
699}713}
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
719pub fn getInstallStep(self: *Build) *Step {715pub fn getInstallStep(self: *Build) *Step {
720 return &self.install_tls.step;716 return &self.install_tls.step;
721}717}
...@@ -724,7 +720,8 @@ pub fn getUninstallStep(self: *Build) *Step {...@@ -724,7 +720,8 @@ pub fn getUninstallStep(self: *Build) *Step {
724 return &self.uninstall_tls.step;720 return &self.uninstall_tls.step;
725}721}
726722
727fn makeUninstall(uninstall_step: *Step) anyerror!void {723fn makeUninstall(uninstall_step: *Step, prog_node: *std.Progress.Node) anyerror!void {
724 _ = prog_node;
728 const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step);725 const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step);
729 const self = @fieldParentPtr(Build, "uninstall_tls", uninstall_tls);726 const self = @fieldParentPtr(Build, "uninstall_tls", uninstall_tls);
730727
...@@ -739,37 +736,6 @@ fn makeUninstall(uninstall_step: *Step) anyerror!void {...@@ -739,37 +736,6 @@ fn makeUninstall(uninstall_step: *Step) anyerror!void {
739 // TODO remove empty directories736 // TODO remove empty directories
740}737}
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
773pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T {739pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T {
774 const name = self.dupe(name_raw);740 const name = self.dupe(name_raw);
775 const description = self.dupe(description_raw);741 const description = self.dupe(description_raw);
...@@ -906,11 +872,15 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_...@@ -906,11 +872,15 @@ pub fn option(self: *Build, comptime T: type, name_raw: []const u8, description_
906872
907pub fn step(self: *Build, name: []const u8, description: []const u8) *Step {873pub fn step(self: *Build, name: []const u8, description: []const u8) *Step {
908 const step_info = self.allocator.create(TopLevelStep) catch @panic("OOM");874 const step_info = self.allocator.create(TopLevelStep) catch @panic("OOM");
909 step_info.* = TopLevelStep{875 step_info.* = .{
910 .step = Step.initNoOp(.top_level, name, self.allocator),876 .step = Step.init(.{
877 .id = .top_level,
878 .name = name,
879 .owner = self,
880 }),
911 .description = self.dupe(description),881 .description = self.dupe(description),
912 };882 };
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");
914 return &step_info.step;884 return &step_info.step;
915}885}
916886
...@@ -1178,50 +1148,18 @@ pub fn validateUserInputDidItFail(self: *Build) bool {...@@ -1178,50 +1148,18 @@ pub fn validateUserInputDidItFail(self: *Build) bool {
1178 return self.invalid_user_input;1148 return self.invalid_user_input;
1179}1149}
11801150
1181pub fn spawnChild(self: *Build, argv: []const []const u8) !void {1151fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) ![]u8 {
1182 return self.spawnChildEnvMap(null, self.env_map, argv);1152 var buf = ArrayList(u8).init(ally);
1183}1153 if (opt_cwd) |cwd| try buf.writer().print("cd {s} && ", .{cwd});
1184
1185fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void {
1186 if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd});
1187 for (argv) |arg| {1154 for (argv) |arg| {
1188 std.debug.print("{s} ", .{arg});1155 try buf.writer().print("{s} ", .{arg});
1189 }1156 }
1190 std.debug.print("\n", .{});1157 return buf.toOwnedSlice();
1191}1158}
11921159
1193pub fn spawnChildEnvMap(self: *Build, cwd: ?[]const u8, env_map: *const EnvMap, argv: []const []const u8) !void {1160fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {
1194 if (self.verbose) {1161 const text = allocPrintCmd(ally, cwd, argv) catch @panic("OOM");
1195 printCmd(cwd, argv);1162 std.debug.print("{s}\n", .{text});
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 }
1225}1163}
12261164
1227pub fn installArtifact(self: *Build, artifact: *CompileStep) void {1165pub fn installArtifact(self: *Build, artifact: *CompileStep) void {
...@@ -1280,12 +1218,7 @@ pub fn addInstallFileWithDir(...@@ -1280,12 +1218,7 @@ pub fn addInstallFileWithDir(
1280 install_dir: InstallDir,1218 install_dir: InstallDir,
1281 dest_rel_path: []const u8,1219 dest_rel_path: []const u8,
1282) *InstallFileStep {1220) *InstallFileStep {
1283 if (dest_rel_path.len == 0) {1221 return InstallFileStep.create(self, source.dupe(self), install_dir, dest_rel_path);
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;
1289}1222}
12901223
1291pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *InstallDirStep {1224pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *InstallDirStep {
...@@ -1294,6 +1227,14 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Inst...@@ -1294,6 +1227,14 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Inst
1294 return install_step;1227 return install_step;
1295}1228}
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
1297pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void {1238pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void {
1298 const file = InstalledFile{1239 const file = InstalledFile{
1299 .dir = dir,1240 .dir = dir,
...@@ -1302,18 +1243,6 @@ pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u...@@ -1302,18 +1243,6 @@ pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u
1302 self.installed_files.append(file.dupe(self)) catch @panic("OOM");1243 self.installed_files.append(file.dupe(self)) catch @panic("OOM");
1303}1244}
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
1317pub fn truncateFile(self: *Build, dest_path: []const u8) !void {1246pub fn truncateFile(self: *Build, dest_path: []const u8) !void {
1318 if (self.verbose) {1247 if (self.verbose) {
1319 log.info("truncate {s}", .{dest_path});1248 log.info("truncate {s}", .{dest_path});
...@@ -1397,7 +1326,7 @@ pub fn execAllowFail(...@@ -1397,7 +1326,7 @@ pub fn execAllowFail(
1397) ExecError![]u8 {1326) ExecError![]u8 {
1398 assert(argv.len != 0);1327 assert(argv.len != 0);
13991328
1400 if (!std.process.can_spawn)1329 if (!process.can_spawn)
1401 return error.ExecNotSupported;1330 return error.ExecNotSupported;
14021331
1403 const max_output_size = 400 * 1024;1332 const max_output_size = 400 * 1024;
...@@ -1430,59 +1359,27 @@ pub fn execAllowFail(...@@ -1430,59 +1359,27 @@ pub fn execAllowFail(
1430 }1359 }
1431}1360}
14321361
1433pub fn execFromStep(self: *Build, argv: []const []const u8, src_step: ?*Step) ![]u8 {1362/// This is a helper function to be called from build.zig scripts, *not* from
1434 assert(argv.len != 0);1363/// inside step make() functions. If any errors occur, it fails the build with
14351364/// a helpful message.
1436 if (self.verbose) {1365pub fn exec(b: *Build, argv: []const []const u8) []u8 {
1437 printCmd(null, argv);1366 if (!process.can_spawn) {
1438 }1367 std.debug.print("unable to spawn the following command: cannot spawn child process\n{s}\n", .{
14391368 try allocPrintCmd(b.allocator, null, argv),
1440 if (!std.process.can_spawn) {1369 });
1441 if (src_step) |s| log.err("{s}...", .{s.name});1370 process.exit(1);
1442 log.err("Unable to spawn the following command: cannot spawn child process", .{});
1443 printCmd(null, argv);
1444 std.os.abort();
1445 }1371 }
14461372
1447 var code: u8 = undefined;1373 var code: u8 = undefined;
1448 return self.execAllowFail(argv, &code, .Inherit) catch |err| switch (err) {1374 return b.execAllowFail(argv, &code, .Inherit) catch |err| {
1449 error.ExecNotSupported => {1375 const printed_cmd = allocPrintCmd(b.allocator, null, argv) catch @panic("OOM");
1450 if (src_step) |s| log.err("{s}...", .{s.name});1376 std.debug.print("unable to spawn the following command: {s}\n{s}\n", .{
1451 log.err("Unable to spawn the following command: cannot spawn child process", .{});1377 @errorName(err), printed_cmd,
1452 printCmd(null, argv);1378 });
1453 std.os.abort();1379 process.exit(1);
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,
1479 };1380 };
1480}1381}
14811382
1482pub fn exec(self: *Build, argv: []const []const u8) ![]u8 {
1483 return self.execFromStep(argv, null);
1484}
1485
1486pub fn addSearchPrefix(self: *Build, search_prefix: []const u8) void {1383pub fn addSearchPrefix(self: *Build, search_prefix: []const u8) void {
1487 self.search_prefixes.append(self.dupePath(search_prefix)) catch @panic("OOM");1384 self.search_prefixes.append(self.dupePath(search_prefix)) catch @panic("OOM");
1488}1385}
...@@ -1547,10 +1444,29 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {...@@ -1547,10 +1444,29 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
15471444
1548 const full_path = b.pathFromRoot("build.zig.zon");1445 const full_path = b.pathFromRoot("build.zig.zon");
1549 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 });1446 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);
1551}1467}
15521468
1553fn dependencyInner(1469pub fn dependencyInner(
1554 b: *Build,1470 b: *Build,
1555 name: []const u8,1471 name: []const u8,
1556 build_root_string: []const u8,1472 build_root_string: []const u8,
...@@ -1563,7 +1479,7 @@ fn dependencyInner(...@@ -1563,7 +1479,7 @@ fn dependencyInner(
1563 std.debug.print("unable to open '{s}': {s}\n", .{1479 std.debug.print("unable to open '{s}': {s}\n", .{
1564 build_root_string, @errorName(err),1480 build_root_string, @errorName(err),
1565 });1481 });
1566 std.process.exit(1);1482 process.exit(1);
1567 },1483 },
1568 };1484 };
1569 const sub_builder = b.createChild(name, build_root, args) catch @panic("unhandled error");1485 const sub_builder = b.createChild(name, build_root, args) catch @panic("unhandled error");
...@@ -1607,7 +1523,7 @@ pub const GeneratedFile = struct {...@@ -1607,7 +1523,7 @@ pub const GeneratedFile = struct {
16071523
1608 pub fn getPath(self: GeneratedFile) []const u8 {1524 pub fn getPath(self: GeneratedFile) []const u8 {
1609 return self.path orelse std.debug.panic(1525 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}'?",
1611 .{self.step.name},1527 .{self.step.name},
1612 );1528 );
1613 }1529 }
...@@ -1647,12 +1563,23 @@ pub const FileSource = union(enum) {...@@ -1647,12 +1563,23 @@ pub const FileSource = union(enum) {
1647 }1563 }
16481564
1649 /// Should only be called during make(), returns a path relative to the build root or absolute.1565 /// 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 {1566 pub fn getPath(self: FileSource, src_builder: *Build) []const u8 {
1651 const path = switch (self) {1567 return getPath2(self, src_builder, null);
1652 .path => |p| builder.pathFromRoot(p),1568 }
1653 .generated => |gen| gen.getPath(),1569
1654 };1570 /// Should only be called during make(), returns a path relative to the build root or absolute.
1655 return path;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 }
1656 }1583 }
16571584
1658 /// Duplicates the file source for a given builder.1585 /// Duplicates the file source for a given builder.
...@@ -1664,6 +1591,54 @@ pub const FileSource = union(enum) {...@@ -1664,6 +1591,54 @@ pub const FileSource = union(enum) {
1664 }1591 }
1665};1592};
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
1667/// Allocates a new string for assigning a value to a named macro.1642/// Allocates a new string for assigning a value to a named macro.
1668/// If the value is omitted, it is set to 1.1643/// If the value is omitted, it is set to 1.
1669/// `name` and `value` need not live longer than the function call.1644/// `name` and `value` need not live longer than the function call.
...@@ -1703,9 +1678,7 @@ pub const InstallDir = union(enum) {...@@ -1703,9 +1678,7 @@ pub const InstallDir = union(enum) {
1703 /// Duplicates the install directory including the path if set to custom.1678 /// Duplicates the install directory including the path if set to custom.
1704 pub fn dupe(self: InstallDir, builder: *Build) InstallDir {1679 pub fn dupe(self: InstallDir, builder: *Build) InstallDir {
1705 if (self == .custom) {1680 if (self == .custom) {
1706 // Written with this temporary to avoid RLS problems1681 return .{ .custom = builder.dupe(self.custom) };
1707 const duped_path = builder.dupe(self.custom);
1708 return .{ .custom = duped_path };
1709 } else {1682 } else {
1710 return self;1683 return self;
1711 }1684 }
...@@ -1753,17 +1726,45 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {...@@ -1753,17 +1726,45 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {
1753 }1726 }
1754}1727}
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
1756test {1759test {
1757 _ = CheckFileStep;1760 _ = CheckFileStep;
1758 _ = CheckObjectStep;1761 _ = CheckObjectStep;
1759 _ = EmulatableRunStep;
1760 _ = FmtStep;1762 _ = FmtStep;
1761 _ = InstallArtifactStep;1763 _ = InstallArtifactStep;
1762 _ = InstallDirStep;1764 _ = InstallDirStep;
1763 _ = InstallFileStep;1765 _ = InstallFileStep;
1764 _ = ObjCopyStep;1766 _ = ObjCopyStep;
1765 _ = CompileStep;1767 _ = CompileStep;
1766 _ = LogStep;
1767 _ = OptionsStep;1768 _ = OptionsStep;
1768 _ = RemoveDirStep;1769 _ = RemoveDirStep;
1769 _ = RunStep;1770 _ = RunStep;
lib/std/Build/Cache.zig+74-61
...@@ -7,27 +7,27 @@ pub const Directory = struct {...@@ -7,27 +7,27 @@ pub const Directory = struct {
7 /// directly, but it is needed when passing the directory to a child process.7 /// directly, but it is needed when passing the directory to a child process.
8 /// `null` means cwd.8 /// `null` means cwd.
9 path: ?[]const u8,9 path: ?[]const u8,
10 handle: std.fs.Dir,10 handle: fs.Dir,
1111
12 pub fn join(self: Directory, allocator: Allocator, paths: []const []const u8) ![]u8 {12 pub fn join(self: Directory, allocator: Allocator, paths: []const []const u8) ![]u8 {
13 if (self.path) |p| {13 if (self.path) |p| {
14 // TODO clean way to do this with only 1 allocation14 // 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);
16 defer allocator.free(part2);16 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 });
18 } else {18 } else {
19 return std.fs.path.join(allocator, paths);19 return fs.path.join(allocator, paths);
20 }20 }
21 }21 }
2222
23 pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) ![:0]u8 {23 pub fn joinZ(self: Directory, allocator: Allocator, paths: []const []const u8) ![:0]u8 {
24 if (self.path) |p| {24 if (self.path) |p| {
25 // TODO clean way to do this with only 1 allocation25 // 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);
27 defer allocator.free(part2);27 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 });
29 } else {29 } else {
30 return std.fs.path.joinZ(allocator, paths);30 return fs.path.joinZ(allocator, paths);
31 }31 }
32 }32 }
3333
...@@ -39,6 +39,20 @@ pub const Directory = struct {...@@ -39,6 +39,20 @@ pub const Directory = struct {
39 if (self.path) |p| gpa.free(p);39 if (self.path) |p| gpa.free(p);
40 self.* = undefined;40 self.* = undefined;
41 }41 }
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 }
42};56};
4357
44gpa: Allocator,58gpa: Allocator,
...@@ -243,10 +257,10 @@ pub const HashHelper = struct {...@@ -243,10 +257,10 @@ pub const HashHelper = struct {
243 hh.hasher.final(&bin_digest);257 hh.hasher.final(&bin_digest);
244258
245 var out_digest: [hex_digest_len]u8 = undefined;259 var out_digest: [hex_digest_len]u8 = undefined;
246 _ = std.fmt.bufPrint(260 _ = fmt.bufPrint(
247 &out_digest,261 &out_digest,
248 "{s}",262 "{s}",
249 .{std.fmt.fmtSliceHexLower(&bin_digest)},263 .{fmt.fmtSliceHexLower(&bin_digest)},
250 ) catch unreachable;264 ) catch unreachable;
251 return out_digest;265 return out_digest;
252 }266 }
...@@ -365,10 +379,10 @@ pub const Manifest = struct {...@@ -365,10 +379,10 @@ pub const Manifest = struct {
365 var bin_digest: BinDigest = undefined;379 var bin_digest: BinDigest = undefined;
366 self.hash.hasher.final(&bin_digest);380 self.hash.hasher.final(&bin_digest);
367381
368 _ = std.fmt.bufPrint(382 _ = fmt.bufPrint(
369 &self.hex_digest,383 &self.hex_digest,
370 "{s}",384 "{s}",
371 .{std.fmt.fmtSliceHexLower(&bin_digest)},385 .{fmt.fmtSliceHexLower(&bin_digest)},
372 ) catch unreachable;386 ) catch unreachable;
373387
374 self.hash.hasher = hasher_init;388 self.hash.hasher = hasher_init;
...@@ -408,7 +422,11 @@ pub const Manifest = struct {...@@ -408,7 +422,11 @@ pub const Manifest = struct {
408 self.have_exclusive_lock = true;422 self.have_exclusive_lock = true;
409 return false; // cache miss; exclusive lock already held423 return false; // cache miss; exclusive lock already held
410 } else |err| switch (err) {424 } 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,
412 else => |e| return e,430 else => |e| return e,
413 }431 }
414 },432 },
...@@ -425,7 +443,10 @@ pub const Manifest = struct {...@@ -425,7 +443,10 @@ pub const Manifest = struct {
425 self.manifest_file = manifest_file;443 self.manifest_file = manifest_file;
426 self.have_exclusive_lock = true;444 self.have_exclusive_lock = true;
427 } else |err| switch (err) {445 } 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 => {
429 self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{450 self.manifest_file = try self.cache.manifest_dir.openFile(&manifest_file_path, .{
430 .lock = .Shared,451 .lock = .Shared,
431 });452 });
...@@ -469,7 +490,7 @@ pub const Manifest = struct {...@@ -469,7 +490,7 @@ pub const Manifest = struct {
469 cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;490 cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat;
470 cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat;491 cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat;
471 cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat;492 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;
473 const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat;494 const prefix = fmt.parseInt(u8, prefix_str, 10) catch return error.InvalidFormat;
474 if (prefix >= self.cache.prefixes_len) return error.InvalidFormat;495 if (prefix >= self.cache.prefixes_len) return error.InvalidFormat;
475496
...@@ -806,10 +827,10 @@ pub const Manifest = struct {...@@ -806,10 +827,10 @@ pub const Manifest = struct {
806 self.hash.hasher.final(&bin_digest);827 self.hash.hasher.final(&bin_digest);
807828
808 var out_digest: [hex_digest_len]u8 = undefined;829 var out_digest: [hex_digest_len]u8 = undefined;
809 _ = std.fmt.bufPrint(830 _ = fmt.bufPrint(
810 &out_digest,831 &out_digest,
811 "{s}",832 "{s}",
812 .{std.fmt.fmtSliceHexLower(&bin_digest)},833 .{fmt.fmtSliceHexLower(&bin_digest)},
813 ) catch unreachable;834 ) catch unreachable;
814835
815 return out_digest;836 return out_digest;
...@@ -831,10 +852,10 @@ pub const Manifest = struct {...@@ -831,10 +852,10 @@ pub const Manifest = struct {
831 var encoded_digest: [hex_digest_len]u8 = undefined;852 var encoded_digest: [hex_digest_len]u8 = undefined;
832853
833 for (self.files.items) |file| {854 for (self.files.items) |file| {
834 _ = std.fmt.bufPrint(855 _ = fmt.bufPrint(
835 &encoded_digest,856 &encoded_digest,
836 "{s}",857 "{s}",
837 .{std.fmt.fmtSliceHexLower(&file.bin_digest)},858 .{fmt.fmtSliceHexLower(&file.bin_digest)},
838 ) catch unreachable;859 ) catch unreachable;
839 try writer.print("{d} {d} {d} {s} {d} {s}\n", .{860 try writer.print("{d} {d} {d} {s} {d} {s}\n", .{
840 file.stat.size,861 file.stat.size,
...@@ -955,16 +976,16 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void {...@@ -955,16 +976,16 @@ fn hashFile(file: fs.File, bin_digest: *[Hasher.mac_length]u8) !void {
955}976}
956977
957// Create/Write a file, close it, then grab its stat.mtime timestamp.978// Create/Write a file, close it, then grab its stat.mtime timestamp.
958fn testGetCurrentFileTimestamp() !i128 {979fn testGetCurrentFileTimestamp(dir: fs.Dir) !i128 {
959 const test_out_file = "test-filetimestamp.tmp";980 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, .{
962 .read = true,983 .read = true,
963 .truncate = true,984 .truncate = true,
964 });985 });
965 defer {986 defer {
966 file.close();987 file.close();
967 fs.cwd().deleteFile(test_out_file) catch {};988 dir.deleteFile(test_out_file) catch {};
968 }989 }
969990
970 return (try file.stat()).mtime;991 return (try file.stat()).mtime;
...@@ -976,16 +997,17 @@ test "cache file and then recall it" {...@@ -976,16 +997,17 @@ test "cache file and then recall it" {
976 return error.SkipZigTest;997 return error.SkipZigTest;
977 }998 }
978999
979 const cwd = fs.cwd();1000 var tmp = testing.tmpDir(.{});
1001 defer tmp.cleanup();
9801002
981 const temp_file = "test.txt";1003 const temp_file = "test.txt";
982 const temp_manifest_dir = "temp_manifest_dir";1004 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
986 // Wait for file timestamps to tick1008 // Wait for file timestamps to tick
987 const initial_time = try testGetCurrentFileTimestamp();1009 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
988 while ((try testGetCurrentFileTimestamp()) == initial_time) {1010 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
989 std.time.sleep(1);1011 std.time.sleep(1);
990 }1012 }
9911013
...@@ -995,9 +1017,9 @@ test "cache file and then recall it" {...@@ -995,9 +1017,9 @@ test "cache file and then recall it" {
995 {1017 {
996 var cache = Cache{1018 var cache = Cache{
997 .gpa = testing.allocator,1019 .gpa = testing.allocator,
998 .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),1020 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
999 };1021 };
1000 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });1022 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1001 defer cache.manifest_dir.close();1023 defer cache.manifest_dir.close();
10021024
1003 {1025 {
...@@ -1033,9 +1055,6 @@ test "cache file and then recall it" {...@@ -1033,9 +1055,6 @@ test "cache file and then recall it" {
10331055
1034 try testing.expectEqual(digest1, digest2);1056 try testing.expectEqual(digest1, digest2);
1035 }1057 }
1036
1037 try cwd.deleteTree(temp_manifest_dir);
1038 try cwd.deleteFile(temp_file);
1039}1058}
10401059
1041test "check that changing a file makes cache fail" {1060test "check that changing a file makes cache fail" {
...@@ -1043,21 +1062,19 @@ test "check that changing a file makes cache fail" {...@@ -1043,21 +1062,19 @@ test "check that changing a file makes cache fail" {
1043 // https://github.com/ziglang/zig/issues/54371062 // https://github.com/ziglang/zig/issues/5437
1044 return error.SkipZigTest;1063 return error.SkipZigTest;
1045 }1064 }
1046 const cwd = fs.cwd();1065 var tmp = testing.tmpDir(.{});
1066 defer tmp.cleanup();
10471067
1048 const temp_file = "cache_hash_change_file_test.txt";1068 const temp_file = "cache_hash_change_file_test.txt";
1049 const temp_manifest_dir = "cache_hash_change_file_manifest_dir";1069 const temp_manifest_dir = "cache_hash_change_file_manifest_dir";
1050 const original_temp_file_contents = "Hello, world!\n";1070 const original_temp_file_contents = "Hello, world!\n";
1051 const updated_temp_file_contents = "Hello, world; but updated!\n";1071 const updated_temp_file_contents = "Hello, world; but updated!\n";
10521072
1053 try cwd.deleteTree(temp_manifest_dir);1073 try tmp.dir.writeFile(temp_file, original_temp_file_contents);
1054 try cwd.deleteTree(temp_file);
1055
1056 try cwd.writeFile(temp_file, original_temp_file_contents);
10571074
1058 // Wait for file timestamps to tick1075 // Wait for file timestamps to tick
1059 const initial_time = try testGetCurrentFileTimestamp();1076 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1060 while ((try testGetCurrentFileTimestamp()) == initial_time) {1077 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
1061 std.time.sleep(1);1078 std.time.sleep(1);
1062 }1079 }
10631080
...@@ -1067,9 +1084,9 @@ test "check that changing a file makes cache fail" {...@@ -1067,9 +1084,9 @@ test "check that changing a file makes cache fail" {
1067 {1084 {
1068 var cache = Cache{1085 var cache = Cache{
1069 .gpa = testing.allocator,1086 .gpa = testing.allocator,
1070 .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),1087 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1071 };1088 };
1072 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });1089 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1073 defer cache.manifest_dir.close();1090 defer cache.manifest_dir.close();
10741091
1075 {1092 {
...@@ -1089,7 +1106,7 @@ test "check that changing a file makes cache fail" {...@@ -1089,7 +1106,7 @@ test "check that changing a file makes cache fail" {
1089 try ch.writeManifest();1106 try ch.writeManifest();
1090 }1107 }
10911108
1092 try cwd.writeFile(temp_file, updated_temp_file_contents);1109 try tmp.dir.writeFile(temp_file, updated_temp_file_contents);
10931110
1094 {1111 {
1095 var ch = cache.obtain();1112 var ch = cache.obtain();
...@@ -1111,9 +1128,6 @@ test "check that changing a file makes cache fail" {...@@ -1111,9 +1128,6 @@ test "check that changing a file makes cache fail" {
11111128
1112 try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));1129 try testing.expect(!mem.eql(u8, digest1[0..], digest2[0..]));
1113 }1130 }
1114
1115 try cwd.deleteTree(temp_manifest_dir);
1116 try cwd.deleteTree(temp_file);
1117}1131}
11181132
1119test "no file inputs" {1133test "no file inputs" {
...@@ -1121,18 +1135,20 @@ test "no file inputs" {...@@ -1121,18 +1135,20 @@ test "no file inputs" {
1121 // https://github.com/ziglang/zig/issues/54371135 // https://github.com/ziglang/zig/issues/5437
1122 return error.SkipZigTest;1136 return error.SkipZigTest;
1123 }1137 }
1124 const cwd = fs.cwd();1138
1139 var tmp = testing.tmpDir(.{});
1140 defer tmp.cleanup();
1141
1125 const temp_manifest_dir = "no_file_inputs_manifest_dir";1142 const temp_manifest_dir = "no_file_inputs_manifest_dir";
1126 defer cwd.deleteTree(temp_manifest_dir) catch {};
11271143
1128 var digest1: [hex_digest_len]u8 = undefined;1144 var digest1: [hex_digest_len]u8 = undefined;
1129 var digest2: [hex_digest_len]u8 = undefined;1145 var digest2: [hex_digest_len]u8 = undefined;
11301146
1131 var cache = Cache{1147 var cache = Cache{
1132 .gpa = testing.allocator,1148 .gpa = testing.allocator,
1133 .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),1149 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1134 };1150 };
1135 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });1151 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1136 defer cache.manifest_dir.close();1152 defer cache.manifest_dir.close();
11371153
1138 {1154 {
...@@ -1167,18 +1183,19 @@ test "Manifest with files added after initial hash work" {...@@ -1167,18 +1183,19 @@ test "Manifest with files added after initial hash work" {
1167 // https://github.com/ziglang/zig/issues/54371183 // https://github.com/ziglang/zig/issues/5437
1168 return error.SkipZigTest;1184 return error.SkipZigTest;
1169 }1185 }
1170 const cwd = fs.cwd();1186 var tmp = testing.tmpDir(.{});
1187 defer tmp.cleanup();
11711188
1172 const temp_file1 = "cache_hash_post_file_test1.txt";1189 const temp_file1 = "cache_hash_post_file_test1.txt";
1173 const temp_file2 = "cache_hash_post_file_test2.txt";1190 const temp_file2 = "cache_hash_post_file_test2.txt";
1174 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";1191 const temp_manifest_dir = "cache_hash_post_file_manifest_dir";
11751192
1176 try cwd.writeFile(temp_file1, "Hello, world!\n");1193 try tmp.dir.writeFile(temp_file1, "Hello, world!\n");
1177 try cwd.writeFile(temp_file2, "Hello world the second!\n");1194 try tmp.dir.writeFile(temp_file2, "Hello world the second!\n");
11781195
1179 // Wait for file timestamps to tick1196 // Wait for file timestamps to tick
1180 const initial_time = try testGetCurrentFileTimestamp();1197 const initial_time = try testGetCurrentFileTimestamp(tmp.dir);
1181 while ((try testGetCurrentFileTimestamp()) == initial_time) {1198 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time) {
1182 std.time.sleep(1);1199 std.time.sleep(1);
1183 }1200 }
11841201
...@@ -1189,9 +1206,9 @@ test "Manifest with files added after initial hash work" {...@@ -1189,9 +1206,9 @@ test "Manifest with files added after initial hash work" {
1189 {1206 {
1190 var cache = Cache{1207 var cache = Cache{
1191 .gpa = testing.allocator,1208 .gpa = testing.allocator,
1192 .manifest_dir = try cwd.makeOpenPath(temp_manifest_dir, .{}),1209 .manifest_dir = try tmp.dir.makeOpenPath(temp_manifest_dir, .{}),
1193 };1210 };
1194 cache.addPrefix(.{ .path = null, .handle = fs.cwd() });1211 cache.addPrefix(.{ .path = null, .handle = tmp.dir });
1195 defer cache.manifest_dir.close();1212 defer cache.manifest_dir.close();
11961213
1197 {1214 {
...@@ -1224,11 +1241,11 @@ test "Manifest with files added after initial hash work" {...@@ -1224,11 +1241,11 @@ test "Manifest with files added after initial hash work" {
1224 try testing.expect(mem.eql(u8, &digest1, &digest2));1241 try testing.expect(mem.eql(u8, &digest1, &digest2));
12251242
1226 // Modify the file added after initial hash1243 // 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
1229 // Wait for file timestamps to tick1246 // Wait for file timestamps to tick
1230 const initial_time2 = try testGetCurrentFileTimestamp();1247 const initial_time2 = try testGetCurrentFileTimestamp(tmp.dir);
1231 while ((try testGetCurrentFileTimestamp()) == initial_time2) {1248 while ((try testGetCurrentFileTimestamp(tmp.dir)) == initial_time2) {
1232 std.time.sleep(1);1249 std.time.sleep(1);
1233 }1250 }
12341251
...@@ -1251,8 +1268,4 @@ test "Manifest with files added after initial hash work" {...@@ -1251,8 +1268,4 @@ test "Manifest with files added after initial hash work" {
12511268
1252 try testing.expect(!mem.eql(u8, &digest1, &digest3));1269 try testing.expect(!mem.eql(u8, &digest1, &digest3));
1253 }1270 }
1254
1255 try cwd.deleteTree(temp_manifest_dir);
1256 try cwd.deleteFile(temp_file1);
1257 try cwd.deleteFile(temp_file2);
1258}1271}
lib/std/Build/CheckFileStep.zig+62-25
...@@ -1,51 +1,88 @@...@@ -1,51 +1,88 @@
1const std = @import("../std.zig");1//! Fail the build step if a file does not match certain checks.
2const Step = std.Build.Step;2//! TODO: make this more flexible, supporting more kinds of checks.
3const fs = std.fs;3//! TODO: generalize the code in std.testing.expectEqualStrings and make this
4const mem = std.mem;4//! CheckFileStep produce those helpful diagnostics when there is not a match.
5
6const CheckFileStep = @This();
7
8pub const base_id = .check_file;
95
10step: Step,6step: Step,
11builder: *std.Build,
12expected_matches: []const []const u8,7expected_matches: []const []const u8,
8expected_exact: ?[]const u8,
13source: std.Build.FileSource,9source: std.Build.FileSource,
14max_bytes: usize = 20 * 1024 * 1024,10max_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
16pub fn create(19pub fn create(
17 builder: *std.Build,20 owner: *std.Build,
18 source: std.Build.FileSource,21 source: std.Build.FileSource,
19 expected_matches: []const []const u8,22 options: Options,
20) *CheckFileStep {23) *CheckFileStep {
21 const self = builder.allocator.create(CheckFileStep) catch @panic("OOM");24 const self = owner.allocator.create(CheckFileStep) catch @panic("OOM");
22 self.* = CheckFileStep{25 self.* = .{
23 .builder = builder,26 .step = Step.init(.{
24 .step = Step.init(.check_file, "CheckFile", builder.allocator, make),27 .id = .check_file,
25 .source = source.dupe(builder),28 .name = "CheckFile",
26 .expected_matches = builder.dupeStrings(expected_matches),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,
27 };35 };
28 self.source.addStepDependencies(&self.step);36 self.source.addStepDependencies(&self.step);
29 return self;37 return self;
30}38}
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;
33 const self = @fieldParentPtr(CheckFileStep, "step", step);47 const self = @fieldParentPtr(CheckFileStep, "step", step);
3448
35 const src_path = self.source.getPath(self.builder);49 const src_path = self.source.getPath(b);
36 const contents = try fs.cwd().readFileAlloc(self.builder.allocator, src_path, self.max_bytes);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
38 for (self.expected_matches) |expected_match| {56 for (self.expected_matches) |expected_match| {
39 if (mem.indexOf(u8, contents, expected_match) == null) {57 if (mem.indexOf(u8, contents, expected_match) == null) {
40 std.debug.print(58 return step.fail(
41 \\59 \\
42 \\========= Expected to find: ===================60 \\========= expected to find: ===================
43 \\{s}61 \\{s}
44 \\========= But file does not contain it: =======62 \\========= but file does not contain it: =======
45 \\{s}63 \\{s}
46 \\64 \\===============================================
47 , .{ expected_match, contents });65 , .{ 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 });
49 }80 }
50 }81 }
51}82}
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();...@@ -10,25 +10,31 @@ const CheckObjectStep = @This();
1010
11const Allocator = mem.Allocator;11const Allocator = mem.Allocator;
12const Step = std.Build.Step;12const Step = std.Build.Step;
13const EmulatableRunStep = std.Build.EmulatableRunStep;
1413
15pub const base_id = .check_object;14pub const base_id = .check_object;
1615
17step: Step,16step: Step,
18builder: *std.Build,
19source: std.Build.FileSource,17source: std.Build.FileSource,
20max_bytes: usize = 20 * 1024 * 1024,18max_bytes: usize = 20 * 1024 * 1024,
21checks: std.ArrayList(Check),19checks: std.ArrayList(Check),
22dump_symtab: bool = false,20dump_symtab: bool = false,
23obj_format: std.Target.ObjectFormat,21obj_format: std.Target.ObjectFormat,
2422
25pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep {23pub fn create(
26 const gpa = builder.allocator;24 owner: *std.Build,
25 source: std.Build.FileSource,
26 obj_format: std.Target.ObjectFormat,
27) *CheckObjectStep {
28 const gpa = owner.allocator;
27 const self = gpa.create(CheckObjectStep) catch @panic("OOM");29 const self = gpa.create(CheckObjectStep) catch @panic("OOM");
28 self.* = .{30 self.* = .{
29 .builder = builder,31 .step = Step.init(.{
30 .step = Step.init(.check_file, "CheckObject", gpa, make),32 .id = .check_file,
31 .source = source.dupe(builder),33 .name = "CheckObject",
34 .owner = owner,
35 .makeFn = make,
36 }),
37 .source = source.dupe(owner),
32 .checks = std.ArrayList(Check).init(gpa),38 .checks = std.ArrayList(Check).init(gpa),
33 .obj_format = obj_format,39 .obj_format = obj_format,
34 };40 };
...@@ -38,14 +44,18 @@ pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std...@@ -38,14 +44,18 @@ pub fn create(builder: *std.Build, source: std.Build.FileSource, obj_format: std
3844
39/// Runs and (optionally) compares the output of a binary.45/// Runs and (optionally) compares the output of a binary.
40/// Asserts `self` was generated from an executable step.46/// 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 {
42 const dependencies_len = self.step.dependencies.items.len;51 const dependencies_len = self.step.dependencies.items.len;
43 assert(dependencies_len > 0);52 assert(dependencies_len > 0);
44 const exe_step = self.step.dependencies.items[dependencies_len - 1];53 const exe_step = self.step.dependencies.items[dependencies_len - 1];
45 const exe = exe_step.cast(std.Build.CompileStep).?;54 const exe = exe_step.cast(std.Build.CompileStep).?;
46 const emulatable_step = EmulatableRunStep.create(self.builder, "EmulatableRun", exe);55 const run = self.step.owner.addRunArtifact(exe);
47 emulatable_step.step.dependOn(&self.step);56 run.skip_foreign_checks = true;
48 return emulatable_step;57 run.step.dependOn(&self.step);
58 return run;
49}59}
5060
51/// There two types of actions currently suported:61/// There two types of actions currently suported:
...@@ -123,7 +133,8 @@ const Action = struct {...@@ -123,7 +133,8 @@ const Action = struct {
123 /// Will return true if the `phrase` is correctly parsed into an RPN program and133 /// Will return true if the `phrase` is correctly parsed into an RPN program and
124 /// its reduced, computed value compares using `op` with the expected value, either134 /// its reduced, computed value compares using `op` with the expected value, either
125 /// a literal or another extracted variable.135 /// 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;
127 var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa);138 var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa);
128 var values = std.ArrayList(u64).init(gpa);139 var values = std.ArrayList(u64).init(gpa);
129140
...@@ -140,11 +151,11 @@ const Action = struct {...@@ -140,11 +151,11 @@ const Action = struct {
140 } else {151 } else {
141 const val = std.fmt.parseInt(u64, next, 0) catch blk: {152 const val = std.fmt.parseInt(u64, next, 0) catch blk: {
142 break :blk global_vars.get(next) orelse {153 break :blk global_vars.get(next) orelse {
143 std.debug.print(154 try step.addError(
144 \\155 \\
145 \\========= Variable was not extracted: ===========156 \\========= variable was not extracted: ===========
146 \\{s}157 \\{s}
147 \\158 \\=================================================
148 , .{next});159 , .{next});
149 return error.UnknownVariable;160 return error.UnknownVariable;
150 };161 };
...@@ -176,11 +187,11 @@ const Action = struct {...@@ -176,11 +187,11 @@ const Action = struct {
176187
177 const exp_value = switch (act.expected.?.value) {188 const exp_value = switch (act.expected.?.value) {
178 .variable => |name| global_vars.get(name) orelse {189 .variable => |name| global_vars.get(name) orelse {
179 std.debug.print(190 try step.addError(
180 \\191 \\
181 \\========= Variable was not extracted: ===========192 \\========= variable was not extracted: ===========
182 \\{s}193 \\{s}
183 \\194 \\=================================================
184 , .{name});195 , .{name});
185 return error.UnknownVariable;196 return error.UnknownVariable;
186 },197 },
...@@ -249,7 +260,7 @@ const Check = struct {...@@ -249,7 +260,7 @@ const Check = struct {
249260
250/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase.261/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase.
251pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void {262pub 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);
253 new_check.match(phrase);264 new_check.match(phrase);
254 self.checks.append(new_check) catch @panic("OOM");265 self.checks.append(new_check) catch @panic("OOM");
255}266}
...@@ -291,34 +302,34 @@ pub fn checkComputeCompare(...@@ -291,34 +302,34 @@ pub fn checkComputeCompare(
291 program: []const u8,302 program: []const u8,
292 expected: ComputeCompareExpected,303 expected: ComputeCompareExpected,
293) void {304) void {
294 var new_check = Check.create(self.builder);305 var new_check = Check.create(self.step.owner);
295 new_check.computeCmp(program, expected);306 new_check.computeCmp(program, expected);
296 self.checks.append(new_check) catch @panic("OOM");307 self.checks.append(new_check) catch @panic("OOM");
297}308}
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;
300 const self = @fieldParentPtr(CheckObjectStep, "step", step);314 const self = @fieldParentPtr(CheckObjectStep, "step", step);
301315
302 const gpa = self.builder.allocator;316 const src_path = self.source.getPath(b);
303 const src_path = self.source.getPath(self.builder);317 const contents = fs.cwd().readFileAllocOptions(
304 const contents = try fs.cwd().readFileAllocOptions(
305 gpa,318 gpa,
306 src_path,319 src_path,
307 self.max_bytes,320 self.max_bytes,
308 null,321 null,
309 @alignOf(u64),322 @alignOf(u64),
310 null,323 null,
311 );324 ) catch |err| return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err) });
312325
313 const output = switch (self.obj_format) {326 const output = switch (self.obj_format) {
314 .macho => try MachODumper.parseAndDump(contents, .{327 .macho => try MachODumper.parseAndDump(step, contents, .{
315 .gpa = gpa,
316 .dump_symtab = self.dump_symtab,328 .dump_symtab = self.dump_symtab,
317 }),329 }),
318 .elf => @panic("TODO elf parser"),330 .elf => @panic("TODO elf parser"),
319 .coff => @panic("TODO coff parser"),331 .coff => @panic("TODO coff parser"),
320 .wasm => try WasmDumper.parseAndDump(contents, .{332 .wasm => try WasmDumper.parseAndDump(step, contents, .{
321 .gpa = gpa,
322 .dump_symtab = self.dump_symtab,333 .dump_symtab = self.dump_symtab,
323 }),334 }),
324 else => unreachable,335 else => unreachable,
...@@ -334,54 +345,50 @@ fn make(step: *Step) !void {...@@ -334,54 +345,50 @@ fn make(step: *Step) !void {
334 while (it.next()) |line| {345 while (it.next()) |line| {
335 if (try act.match(line, &vars)) break;346 if (try act.match(line, &vars)) break;
336 } else {347 } else {
337 std.debug.print(348 return step.fail(
338 \\349 \\
339 \\========= Expected to find: ==========================350 \\========= expected to find: ==========================
340 \\{s}351 \\{s}
341 \\========= But parsed file does not contain it: =======352 \\========= but parsed file does not contain it: =======
342 \\{s}353 \\{s}
343 \\354 \\======================================================
344 , .{ act.phrase, output });355 , .{ act.phrase, output });
345 return error.TestFailed;
346 }356 }
347 },357 },
348 .not_present => {358 .not_present => {
349 while (it.next()) |line| {359 while (it.next()) |line| {
350 if (try act.match(line, &vars)) {360 if (try act.match(line, &vars)) {
351 std.debug.print(361 return step.fail(
352 \\362 \\
353 \\========= Expected not to find: ===================363 \\========= expected not to find: ===================
354 \\{s}364 \\{s}
355 \\========= But parsed file does contain it: ========365 \\========= but parsed file does contain it: ========
356 \\{s}366 \\{s}
357 \\367 \\===================================================
358 , .{ act.phrase, output });368 , .{ act.phrase, output });
359 return error.TestFailed;
360 }369 }
361 }370 }
362 },371 },
363 .compute_cmp => {372 .compute_cmp => {
364 const res = act.computeCmp(gpa, vars) catch |err| switch (err) {373 const res = act.computeCmp(step, vars) catch |err| switch (err) {
365 error.UnknownVariable => {374 error.UnknownVariable => {
366 std.debug.print(375 return step.fail(
367 \\========= From parsed file: =====================376 \\========= from parsed file: =====================
368 \\{s}377 \\{s}
369 \\378 \\=================================================
370 , .{output});379 , .{output});
371 return error.TestFailed;
372 },380 },
373 else => |e| return e,381 else => |e| return e,
374 };382 };
375 if (!res) {383 if (!res) {
376 std.debug.print(384 return step.fail(
377 \\385 \\
378 \\========= Comparison failed for action: ===========386 \\========= comparison failed for action: ===========
379 \\{s} {}387 \\{s} {}
380 \\========= From parsed file: =======================388 \\========= from parsed file: =======================
381 \\{s}389 \\{s}
382 \\390 \\===================================================
383 , .{ act.phrase, act.expected.?, output });391 , .{ act.phrase, act.expected.?, output });
384 return error.TestFailed;
385 }392 }
386 },393 },
387 }394 }
...@@ -390,7 +397,6 @@ fn make(step: *Step) !void {...@@ -390,7 +397,6 @@ fn make(step: *Step) !void {
390}397}
391398
392const Opts = struct {399const Opts = struct {
393 gpa: ?Allocator = null,
394 dump_symtab: bool = false,400 dump_symtab: bool = false,
395};401};
396402
...@@ -398,8 +404,8 @@ const MachODumper = struct {...@@ -398,8 +404,8 @@ const MachODumper = struct {
398 const LoadCommandIterator = macho.LoadCommandIterator;404 const LoadCommandIterator = macho.LoadCommandIterator;
399 const symtab_label = "symtab";405 const symtab_label = "symtab";
400406
401 fn parseAndDump(bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {407 fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 {
402 const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator408 const gpa = step.owner.allocator;
403 var stream = std.io.fixedBufferStream(bytes);409 var stream = std.io.fixedBufferStream(bytes);
404 const reader = stream.reader();410 const reader = stream.reader();
405411
...@@ -681,8 +687,8 @@ const MachODumper = struct {...@@ -681,8 +687,8 @@ const MachODumper = struct {
681const WasmDumper = struct {687const WasmDumper = struct {
682 const symtab_label = "symbols";688 const symtab_label = "symbols";
683689
684 fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 {690 fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 {
685 const gpa = opts.gpa orelse unreachable; // Wasm dumper requires an allocator691 const gpa = step.owner.allocator;
686 if (opts.dump_symtab) {692 if (opts.dump_symtab) {
687 @panic("TODO: Implement symbol table parsing and dumping");693 @panic("TODO: Implement symbol table parsing and dumping");
688 }694 }
...@@ -703,20 +709,24 @@ const WasmDumper = struct {...@@ -703,20 +709,24 @@ const WasmDumper = struct {
703 const writer = output.writer();709 const writer = output.writer();
704710
705 while (reader.readByte()) |current_byte| {711 while (reader.readByte()) |current_byte| {
706 const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch |err| {712 const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch {
707 std.debug.print("Found invalid section id '{d}'\n", .{current_byte});713 return step.fail("Found invalid section id '{d}'", .{current_byte});
708 return err;
709 };714 };
710715
711 const section_length = try std.leb.readULEB128(u32, reader);716 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);
713 fbs.pos += section_length;718 fbs.pos += section_length;
714 } else |_| {} // reached end of stream719 } else |_| {} // reached end of stream
715720
716 return output.toOwnedSlice();721 return output.toOwnedSlice();
717 }722 }
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 {
720 var fbs = std.io.fixedBufferStream(data);730 var fbs = std.io.fixedBufferStream(data);
721 const reader = fbs.reader();731 const reader = fbs.reader();
722732
...@@ -739,7 +749,7 @@ const WasmDumper = struct {...@@ -739,7 +749,7 @@ const WasmDumper = struct {
739 => {749 => {
740 const entries = try std.leb.readULEB128(u32, reader);750 const entries = try std.leb.readULEB128(u32, reader);
741 try writer.print("\nentries {d}\n", .{entries});751 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);
743 },753 },
744 .custom => {754 .custom => {
745 const name_length = try std.leb.readULEB128(u32, reader);755 const name_length = try std.leb.readULEB128(u32, reader);
...@@ -748,7 +758,7 @@ const WasmDumper = struct {...@@ -748,7 +758,7 @@ const WasmDumper = struct {
748 try writer.print("\nname {s}\n", .{name});758 try writer.print("\nname {s}\n", .{name});
749759
750 if (mem.eql(u8, name, "name")) {760 if (mem.eql(u8, name, "name")) {
751 try parseDumpNames(reader, writer, data);761 try parseDumpNames(step, reader, writer, data);
752 } else if (mem.eql(u8, name, "producers")) {762 } else if (mem.eql(u8, name, "producers")) {
753 try parseDumpProducers(reader, writer, data);763 try parseDumpProducers(reader, writer, data);
754 } else if (mem.eql(u8, name, "target_features")) {764 } else if (mem.eql(u8, name, "target_features")) {
...@@ -764,7 +774,7 @@ const WasmDumper = struct {...@@ -764,7 +774,7 @@ const WasmDumper = struct {
764 }774 }
765 }775 }
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 {
768 var fbs = std.io.fixedBufferStream(data);778 var fbs = std.io.fixedBufferStream(data);
769 const reader = fbs.reader();779 const reader = fbs.reader();
770780
...@@ -774,19 +784,18 @@ const WasmDumper = struct {...@@ -774,19 +784,18 @@ const WasmDumper = struct {
774 while (i < entries) : (i += 1) {784 while (i < entries) : (i += 1) {
775 const func_type = try reader.readByte();785 const func_type = try reader.readByte();
776 if (func_type != std.wasm.function_type) {786 if (func_type != std.wasm.function_type) {
777 std.debug.print("Expected function type, found byte '{d}'\n", .{func_type});787 return step.fail("expected function type, found byte '{d}'", .{func_type});
778 return error.UnexpectedByte;
779 }788 }
780 const params = try std.leb.readULEB128(u32, reader);789 const params = try std.leb.readULEB128(u32, reader);
781 try writer.print("params {d}\n", .{params});790 try writer.print("params {d}\n", .{params});
782 var index: u32 = 0;791 var index: u32 = 0;
783 while (index < params) : (index += 1) {792 while (index < params) : (index += 1) {
784 try parseDumpType(std.wasm.Valtype, reader, writer);793 try parseDumpType(step, std.wasm.Valtype, reader, writer);
785 } else index = 0;794 } else index = 0;
786 const returns = try std.leb.readULEB128(u32, reader);795 const returns = try std.leb.readULEB128(u32, reader);
787 try writer.print("returns {d}\n", .{returns});796 try writer.print("returns {d}\n", .{returns});
788 while (index < returns) : (index += 1) {797 while (index < returns) : (index += 1) {
789 try parseDumpType(std.wasm.Valtype, reader, writer);798 try parseDumpType(step, std.wasm.Valtype, reader, writer);
790 }799 }
791 }800 }
792 },801 },
...@@ -800,9 +809,8 @@ const WasmDumper = struct {...@@ -800,9 +809,8 @@ const WasmDumper = struct {
800 const name = data[fbs.pos..][0..name_len];809 const name = data[fbs.pos..][0..name_len];
801 fbs.pos += name_len;810 fbs.pos += name_len;
802811
803 const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch |err| {812 const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch {
804 std.debug.print("Invalid import kind\n", .{});813 return step.fail("invalid import kind", .{});
805 return err;
806 };814 };
807815
808 try writer.print(816 try writer.print(
...@@ -819,11 +827,11 @@ const WasmDumper = struct {...@@ -819,11 +827,11 @@ const WasmDumper = struct {
819 try parseDumpLimits(reader, writer);827 try parseDumpLimits(reader, writer);
820 },828 },
821 .global => {829 .global => {
822 try parseDumpType(std.wasm.Valtype, reader, writer);830 try parseDumpType(step, std.wasm.Valtype, reader, writer);
823 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)});831 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)});
824 },832 },
825 .table => {833 .table => {
826 try parseDumpType(std.wasm.RefType, reader, writer);834 try parseDumpType(step, std.wasm.RefType, reader, writer);
827 try parseDumpLimits(reader, writer);835 try parseDumpLimits(reader, writer);
828 },836 },
829 }837 }
...@@ -838,7 +846,7 @@ const WasmDumper = struct {...@@ -838,7 +846,7 @@ const WasmDumper = struct {
838 .table => {846 .table => {
839 var i: u32 = 0;847 var i: u32 = 0;
840 while (i < entries) : (i += 1) {848 while (i < entries) : (i += 1) {
841 try parseDumpType(std.wasm.RefType, reader, writer);849 try parseDumpType(step, std.wasm.RefType, reader, writer);
842 try parseDumpLimits(reader, writer);850 try parseDumpLimits(reader, writer);
843 }851 }
844 },852 },
...@@ -851,9 +859,9 @@ const WasmDumper = struct {...@@ -851,9 +859,9 @@ const WasmDumper = struct {
851 .global => {859 .global => {
852 var i: u32 = 0;860 var i: u32 = 0;
853 while (i < entries) : (i += 1) {861 while (i < entries) : (i += 1) {
854 try parseDumpType(std.wasm.Valtype, reader, writer);862 try parseDumpType(step, std.wasm.Valtype, reader, writer);
855 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)});863 try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)});
856 try parseDumpInit(reader, writer);864 try parseDumpInit(step, reader, writer);
857 }865 }
858 },866 },
859 .@"export" => {867 .@"export" => {
...@@ -863,9 +871,8 @@ const WasmDumper = struct {...@@ -863,9 +871,8 @@ const WasmDumper = struct {
863 const name = data[fbs.pos..][0..name_len];871 const name = data[fbs.pos..][0..name_len];
864 fbs.pos += name_len;872 fbs.pos += name_len;
865 const kind_byte = try std.leb.readULEB128(u8, reader);873 const kind_byte = try std.leb.readULEB128(u8, reader);
866 const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch |err| {874 const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch {
867 std.debug.print("invalid export kind value '{d}'\n", .{kind_byte});875 return step.fail("invalid export kind value '{d}'", .{kind_byte});
868 return err;
869 };876 };
870 const index = try std.leb.readULEB128(u32, reader);877 const index = try std.leb.readULEB128(u32, reader);
871 try writer.print(878 try writer.print(
...@@ -880,7 +887,7 @@ const WasmDumper = struct {...@@ -880,7 +887,7 @@ const WasmDumper = struct {
880 var i: u32 = 0;887 var i: u32 = 0;
881 while (i < entries) : (i += 1) {888 while (i < entries) : (i += 1) {
882 try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)});889 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
885 const function_indexes = try std.leb.readULEB128(u32, reader);892 const function_indexes = try std.leb.readULEB128(u32, reader);
886 var function_index: u32 = 0;893 var function_index: u32 = 0;
...@@ -896,7 +903,7 @@ const WasmDumper = struct {...@@ -896,7 +903,7 @@ const WasmDumper = struct {
896 while (i < entries) : (i += 1) {903 while (i < entries) : (i += 1) {
897 const index = try std.leb.readULEB128(u32, reader);904 const index = try std.leb.readULEB128(u32, reader);
898 try writer.print("memory index 0x{x}\n", .{index});905 try writer.print("memory index 0x{x}\n", .{index});
899 try parseDumpInit(reader, writer);906 try parseDumpInit(step, reader, writer);
900 const size = try std.leb.readULEB128(u32, reader);907 const size = try std.leb.readULEB128(u32, reader);
901 try writer.print("size {d}\n", .{size});908 try writer.print("size {d}\n", .{size});
902 try reader.skipBytes(size, .{}); // we do not care about the content of the segments909 try reader.skipBytes(size, .{}); // we do not care about the content of the segments
...@@ -906,11 +913,10 @@ const WasmDumper = struct {...@@ -906,11 +913,10 @@ const WasmDumper = struct {
906 }913 }
907 }914 }
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 {
910 const type_byte = try reader.readByte();917 const type_byte = try reader.readByte();
911 const valtype = std.meta.intToEnum(WasmType, type_byte) catch |err| {918 const valtype = std.meta.intToEnum(WasmType, type_byte) catch {
912 std.debug.print("Invalid wasm type value '{d}'\n", .{type_byte});919 return step.fail("Invalid wasm type value '{d}'", .{type_byte});
913 return err;
914 };920 };
915 try writer.print("type {s}\n", .{@tagName(valtype)});921 try writer.print("type {s}\n", .{@tagName(valtype)});
916 }922 }
...@@ -925,11 +931,10 @@ const WasmDumper = struct {...@@ -925,11 +931,10 @@ const WasmDumper = struct {
925 }931 }
926 }932 }
927933
928 fn parseDumpInit(reader: anytype, writer: anytype) !void {934 fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void {
929 const byte = try std.leb.readULEB128(u8, reader);935 const byte = try std.leb.readULEB128(u8, reader);
930 const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch |err| {936 const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch {
931 std.debug.print("invalid wasm opcode '{d}'\n", .{byte});937 return step.fail("invalid wasm opcode '{d}'", .{byte});
932 return err;
933 };938 };
934 switch (opcode) {939 switch (opcode) {
935 .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}),940 .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}),
...@@ -941,14 +946,13 @@ const WasmDumper = struct {...@@ -941,14 +946,13 @@ const WasmDumper = struct {
941 }946 }
942 const end_opcode = try std.leb.readULEB128(u8, reader);947 const end_opcode = try std.leb.readULEB128(u8, reader);
943 if (end_opcode != std.wasm.opcode(.end)) {948 if (end_opcode != std.wasm.opcode(.end)) {
944 std.debug.print("expected 'end' opcode in init expression\n", .{});949 return step.fail("expected 'end' opcode in init expression", .{});
945 return error.MissingEndOpcode;
946 }950 }
947 }951 }
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 {
950 while (reader.context.pos < data.len) {954 while (reader.context.pos < data.len) {
951 try parseDumpType(std.wasm.NameSubsection, reader, writer);955 try parseDumpType(step, std.wasm.NameSubsection, reader, writer);
952 const size = try std.leb.readULEB128(u32, reader);956 const size = try std.leb.readULEB128(u32, reader);
953 const entries = try std.leb.readULEB128(u32, reader);957 const entries = try std.leb.readULEB128(u32, reader);
954 try writer.print(958 try writer.print(
lib/std/Build/CompileStep.zig+443-376
...@@ -1,7 +1,6 @@...@@ -1,7 +1,6 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("../std.zig");2const std = @import("../std.zig");
3const mem = std.mem;3const mem = std.mem;
4const log = std.log;
5const fs = std.fs;4const fs = std.fs;
6const assert = std.debug.assert;5const assert = std.debug.assert;
7const panic = std.debug.panic;6const panic = std.debug.panic;
...@@ -22,7 +21,6 @@ const InstallDir = std.Build.InstallDir;...@@ -22,7 +21,6 @@ const InstallDir = std.Build.InstallDir;
22const InstallArtifactStep = std.Build.InstallArtifactStep;21const InstallArtifactStep = std.Build.InstallArtifactStep;
23const GeneratedFile = std.Build.GeneratedFile;22const GeneratedFile = std.Build.GeneratedFile;
24const ObjCopyStep = std.Build.ObjCopyStep;23const ObjCopyStep = std.Build.ObjCopyStep;
25const EmulatableRunStep = std.Build.EmulatableRunStep;
26const CheckObjectStep = std.Build.CheckObjectStep;24const CheckObjectStep = std.Build.CheckObjectStep;
27const RunStep = std.Build.RunStep;25const RunStep = std.Build.RunStep;
28const OptionsStep = std.Build.OptionsStep;26const OptionsStep = std.Build.OptionsStep;
...@@ -32,7 +30,6 @@ const CompileStep = @This();...@@ -32,7 +30,6 @@ const CompileStep = @This();
32pub const base_id: Step.Id = .compile;30pub const base_id: Step.Id = .compile;
3331
34step: Step,32step: Step,
35builder: *std.Build,
36name: []const u8,33name: []const u8,
37target: CrossTarget,34target: CrossTarget,
38target_info: NativeTargetInfo,35target_info: NativeTargetInfo,
...@@ -49,9 +46,9 @@ strip: ?bool,...@@ -49,9 +46,9 @@ strip: ?bool,
49unwind_tables: ?bool,46unwind_tables: ?bool,
50// keep in sync with src/link.zig:CompressDebugSections47// keep in sync with src/link.zig:CompressDebugSections
51compress_debug_sections: enum { none, zlib } = .none,48compress_debug_sections: enum { none, zlib } = .none,
52lib_paths: ArrayList([]const u8),49lib_paths: ArrayList(FileSource),
53rpaths: ArrayList([]const u8),50rpaths: ArrayList(FileSource),
54framework_dirs: ArrayList([]const u8),51framework_dirs: ArrayList(FileSource),
55frameworks: StringHashMap(FrameworkLinkInfo),52frameworks: StringHashMap(FrameworkLinkInfo),
56verbose_link: bool,53verbose_link: bool,
57verbose_cc: bool,54verbose_cc: bool,
...@@ -86,7 +83,6 @@ c_std: std.Build.CStd,...@@ -86,7 +83,6 @@ c_std: std.Build.CStd,
86zig_lib_dir: ?[]const u8,83zig_lib_dir: ?[]const u8,
87main_pkg_path: ?[]const u8,84main_pkg_path: ?[]const u8,
88exec_cmd_args: ?[]const ?[]const u8,85exec_cmd_args: ?[]const ?[]const u8,
89name_prefix: []const u8,
90filter: ?[]const u8,86filter: ?[]const u8,
91test_evented_io: bool = false,87test_evented_io: bool = false,
92test_runner: ?[]const u8,88test_runner: ?[]const u8,
...@@ -210,10 +206,17 @@ want_lto: ?bool = null,...@@ -210,10 +206,17 @@ want_lto: ?bool = null,
210use_llvm: ?bool = null,206use_llvm: ?bool = null,
211use_lld: ?bool = null,207use_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
213output_path_source: GeneratedFile,215output_path_source: GeneratedFile,
214output_lib_path_source: GeneratedFile,216output_lib_path_source: GeneratedFile,
215output_h_path_source: GeneratedFile,217output_h_path_source: GeneratedFile,
216output_pdb_path_source: GeneratedFile,218output_pdb_path_source: GeneratedFile,
219output_dirname_source: GeneratedFile,
217220
218pub const CSourceFiles = struct {221pub const CSourceFiles = struct {
219 files: []const []const u8,222 files: []const []const u8,
...@@ -277,6 +280,7 @@ pub const Options = struct {...@@ -277,6 +280,7 @@ pub const Options = struct {
277 kind: Kind,280 kind: Kind,
278 linkage: ?Linkage = null,281 linkage: ?Linkage = null,
279 version: ?std.builtin.Version = null,282 version: ?std.builtin.Version = null,
283 max_rss: usize = 0,
280};284};
281285
282pub const Kind = enum {286pub const Kind = enum {
...@@ -284,7 +288,6 @@ pub const Kind = enum {...@@ -284,7 +288,6 @@ pub const Kind = enum {
284 lib,288 lib,
285 obj,289 obj,
286 @"test",290 @"test",
287 test_exe,
288};291};
289292
290pub const Linkage = enum { dynamic, static };293pub const Linkage = enum { dynamic, static };
...@@ -305,18 +308,35 @@ pub const EmitOption = union(enum) {...@@ -305,18 +308,35 @@ pub const EmitOption = union(enum) {
305 }308 }
306};309};
307310
308pub fn create(builder: *std.Build, options: Options) *CompileStep {311pub fn create(owner: *std.Build, options: Options) *CompileStep {
309 const name = builder.dupe(options.name);312 const name = owner.dupe(options.name);
310 const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(builder) else null;313 const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null;
311 if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) {314 if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) {
312 panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name});315 panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name});
313 }316 }
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");
316 self.* = CompileStep{337 self.* = CompileStep{
317 .strip = null,338 .strip = null,
318 .unwind_tables = null,339 .unwind_tables = null,
319 .builder = builder,
320 .verbose_link = false,340 .verbose_link = false,
321 .verbose_cc = false,341 .verbose_cc = false,
322 .optimize = options.optimize,342 .optimize = options.optimize,
...@@ -325,29 +345,34 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {...@@ -325,29 +345,34 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {
325 .kind = options.kind,345 .kind = options.kind,
326 .root_src = root_src,346 .root_src = root_src,
327 .name = name,347 .name = name,
328 .frameworks = StringHashMap(FrameworkLinkInfo).init(builder.allocator),348 .frameworks = StringHashMap(FrameworkLinkInfo).init(owner.allocator),
329 .step = Step.init(base_id, name, builder.allocator, make),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 }),
330 .version = options.version,356 .version = options.version,
331 .out_filename = undefined,357 .out_filename = undefined,
332 .out_h_filename = builder.fmt("{s}.h", .{name}),358 .out_h_filename = owner.fmt("{s}.h", .{name}),
333 .out_lib_filename = undefined,359 .out_lib_filename = undefined,
334 .out_pdb_filename = builder.fmt("{s}.pdb", .{name}),360 .out_pdb_filename = owner.fmt("{s}.pdb", .{name}),
335 .major_only_filename = null,361 .major_only_filename = null,
336 .name_only_filename = null,362 .name_only_filename = null,
337 .modules = std.StringArrayHashMap(*Module).init(builder.allocator),363 .modules = std.StringArrayHashMap(*Module).init(owner.allocator),
338 .include_dirs = ArrayList(IncludeDir).init(builder.allocator),364 .include_dirs = ArrayList(IncludeDir).init(owner.allocator),
339 .link_objects = ArrayList(LinkObject).init(builder.allocator),365 .link_objects = ArrayList(LinkObject).init(owner.allocator),
340 .c_macros = ArrayList([]const u8).init(builder.allocator),366 .c_macros = ArrayList([]const u8).init(owner.allocator),
341 .lib_paths = ArrayList([]const u8).init(builder.allocator),367 .lib_paths = ArrayList(FileSource).init(owner.allocator),
342 .rpaths = ArrayList([]const u8).init(builder.allocator),368 .rpaths = ArrayList(FileSource).init(owner.allocator),
343 .framework_dirs = ArrayList([]const u8).init(builder.allocator),369 .framework_dirs = ArrayList(FileSource).init(owner.allocator),
344 .installed_headers = ArrayList(*Step).init(builder.allocator),370 .installed_headers = ArrayList(*Step).init(owner.allocator),
345 .object_src = undefined,371 .object_src = undefined,
346 .c_std = std.Build.CStd.C99,372 .c_std = std.Build.CStd.C99,
347 .zig_lib_dir = null,373 .zig_lib_dir = null,
348 .main_pkg_path = null,374 .main_pkg_path = null,
349 .exec_cmd_args = null,375 .exec_cmd_args = null,
350 .name_prefix = "",
351 .filter = null,376 .filter = null,
352 .test_runner = null,377 .test_runner = null,
353 .disable_stack_probing = false,378 .disable_stack_probing = false,
...@@ -363,6 +388,7 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {...@@ -363,6 +388,7 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {
363 .output_lib_path_source = GeneratedFile{ .step = &self.step },388 .output_lib_path_source = GeneratedFile{ .step = &self.step },
364 .output_h_path_source = GeneratedFile{ .step = &self.step },389 .output_h_path_source = GeneratedFile{ .step = &self.step },
365 .output_pdb_path_source = GeneratedFile{ .step = &self.step },390 .output_pdb_path_source = GeneratedFile{ .step = &self.step },
391 .output_dirname_source = GeneratedFile{ .step = &self.step },
366392
367 .target_info = NativeTargetInfo.detect(self.target) catch @panic("unhandled error"),393 .target_info = NativeTargetInfo.detect(self.target) catch @panic("unhandled error"),
368 };394 };
...@@ -372,15 +398,16 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {...@@ -372,15 +398,16 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {
372}398}
373399
374fn computeOutFileNames(self: *CompileStep) void {400fn computeOutFileNames(self: *CompileStep) void {
401 const b = self.step.owner;
375 const target = self.target_info.target;402 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, .{
378 .root_name = self.name,405 .root_name = self.name,
379 .target = target,406 .target = target,
380 .output_mode = switch (self.kind) {407 .output_mode = switch (self.kind) {
381 .lib => .Lib,408 .lib => .Lib,
382 .obj => .Obj,409 .obj => .Obj,
383 .exe, .@"test", .test_exe => .Exe,410 .exe, .@"test" => .Exe,
384 },411 },
385 .link_mode = if (self.linkage) |some| @as(std.builtin.LinkMode, switch (some) {412 .link_mode = if (self.linkage) |some| @as(std.builtin.LinkMode, switch (some) {
386 .dynamic => .Dynamic,413 .dynamic => .Dynamic,
...@@ -394,30 +421,30 @@ fn computeOutFileNames(self: *CompileStep) void {...@@ -394,30 +421,30 @@ fn computeOutFileNames(self: *CompileStep) void {
394 self.out_lib_filename = self.out_filename;421 self.out_lib_filename = self.out_filename;
395 } else if (self.version) |version| {422 } else if (self.version) |version| {
396 if (target.isDarwin()) {423 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", .{
398 self.name,425 self.name,
399 version.major,426 version.major,
400 });427 });
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});
402 self.out_lib_filename = self.out_filename;429 self.out_lib_filename = self.out_filename;
403 } else if (target.os.tag == .windows) {430 } 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});
405 } else {432 } else {
406 self.major_only_filename = self.builder.fmt("lib{s}.so.{d}", .{ self.name, version.major });433 self.major_only_filename = b.fmt("lib{s}.so.{d}", .{ self.name, version.major });
407 self.name_only_filename = self.builder.fmt("lib{s}.so", .{self.name});434 self.name_only_filename = b.fmt("lib{s}.so", .{self.name});
408 self.out_lib_filename = self.out_filename;435 self.out_lib_filename = self.out_filename;
409 }436 }
410 } else {437 } else {
411 if (target.isDarwin()) {438 if (target.isDarwin()) {
412 self.out_lib_filename = self.out_filename;439 self.out_lib_filename = self.out_filename;
413 } else if (target.os.tag == .windows) {440 } 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});
415 } else {442 } else {
416 self.out_lib_filename = self.out_filename;443 self.out_lib_filename = self.out_filename;
417 }444 }
418 }445 }
419 if (self.output_dir != null) {446 if (self.output_dir != null) {
420 self.output_lib_path_source.path = self.builder.pathJoin(447 self.output_lib_path_source.path = b.pathJoin(
421 &.{ self.output_dir.?, self.out_lib_filename },448 &.{ self.output_dir.?, self.out_lib_filename },
422 );449 );
423 }450 }
...@@ -425,17 +452,20 @@ fn computeOutFileNames(self: *CompileStep) void {...@@ -425,17 +452,20 @@ fn computeOutFileNames(self: *CompileStep) void {
425}452}
426453
427pub fn setOutputDir(self: *CompileStep, dir: []const u8) void {454pub 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);
429}457}
430458
431pub fn install(self: *CompileStep) void {459pub fn install(self: *CompileStep) void {
432 self.builder.installArtifact(self);460 const b = self.step.owner;
461 b.installArtifact(self);
433}462}
434463
435pub fn installHeader(a: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void {464pub fn installHeader(cs: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void {
436 const install_file = a.builder.addInstallHeaderFile(src_path, dest_rel_path);465 const b = cs.step.owner;
437 a.builder.getInstallStep().dependOn(&install_file.step);466 const install_file = b.addInstallHeaderFile(src_path, dest_rel_path);
438 a.installed_headers.append(&install_file.step) catch @panic("OOM");467 b.getInstallStep().dependOn(&install_file.step);
468 cs.installed_headers.append(&install_file.step) catch @panic("OOM");
439}469}
440470
441pub const InstallConfigHeaderOptions = struct {471pub const InstallConfigHeaderOptions = struct {
...@@ -449,13 +479,14 @@ pub fn installConfigHeader(...@@ -449,13 +479,14 @@ pub fn installConfigHeader(
449 options: InstallConfigHeaderOptions,479 options: InstallConfigHeaderOptions,
450) void {480) void {
451 const dest_rel_path = options.dest_rel_path orelse config_header.include_path;481 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(
453 .{ .generated = &config_header.output_file },484 .{ .generated = &config_header.output_file },
454 options.install_dir,485 options.install_dir,
455 dest_rel_path,486 dest_rel_path,
456 );487 );
457 install_file.step.dependOn(&config_header.step);488 install_file.step.dependOn(&config_header.step);
458 cs.builder.getInstallStep().dependOn(&install_file.step);489 b.getInstallStep().dependOn(&install_file.step);
459 cs.installed_headers.append(&install_file.step) catch @panic("OOM");490 cs.installed_headers.append(&install_file.step) catch @panic("OOM");
460}491}
461492
...@@ -472,91 +503,83 @@ pub fn installHeadersDirectory(...@@ -472,91 +503,83 @@ pub fn installHeadersDirectory(
472}503}
473504
474pub fn installHeadersDirectoryOptions(505pub fn installHeadersDirectoryOptions(
475 a: *CompileStep,506 cs: *CompileStep,
476 options: std.Build.InstallDirStep.Options,507 options: std.Build.InstallDirStep.Options,
477) void {508) void {
478 const install_dir = a.builder.addInstallDirectory(options);509 const b = cs.step.owner;
479 a.builder.getInstallStep().dependOn(&install_dir.step);510 const install_dir = b.addInstallDirectory(options);
480 a.installed_headers.append(&install_dir.step) catch @panic("OOM");511 b.getInstallStep().dependOn(&install_dir.step);
512 cs.installed_headers.append(&install_dir.step) catch @panic("OOM");
481}513}
482514
483pub fn installLibraryHeaders(a: *CompileStep, l: *CompileStep) void {515pub fn installLibraryHeaders(cs: *CompileStep, l: *CompileStep) void {
484 assert(l.kind == .lib);516 assert(l.kind == .lib);
485 const install_step = a.builder.getInstallStep();517 const b = cs.step.owner;
518 const install_step = b.getInstallStep();
486 // Copy each element from installed_headers, modifying the builder519 // Copy each element from installed_headers, modifying the builder
487 // to be the new parent's builder.520 // to be the new parent's builder.
488 for (l.installed_headers.items) |step| {521 for (l.installed_headers.items) |step| {
489 const step_copy = switch (step.id) {522 const step_copy = switch (step.id) {
490 inline .install_file, .install_dir => |id| blk: {523 inline .install_file, .install_dir => |id| blk: {
491 const T = id.Type();524 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");
493 ptr.* = step.cast(T).?.*;526 ptr.* = step.cast(T).?.*;
494 ptr.override_source_builder = ptr.builder;527 ptr.dest_builder = b;
495 ptr.builder = a.builder;
496 break :blk &ptr.step;528 break :blk &ptr.step;
497 },529 },
498 else => unreachable,530 else => unreachable,
499 };531 };
500 a.installed_headers.append(step_copy) catch @panic("OOM");532 cs.installed_headers.append(step_copy) catch @panic("OOM");
501 install_step.dependOn(step_copy);533 install_step.dependOn(step_copy);
502 }534 }
503 a.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM");535 cs.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM");
504}536}
505537
506pub fn addObjCopy(cs: *CompileStep, options: ObjCopyStep.Options) *ObjCopyStep {538pub fn addObjCopy(cs: *CompileStep, options: ObjCopyStep.Options) *ObjCopyStep {
539 const b = cs.step.owner;
507 var copy = options;540 var copy = options;
508 if (copy.basename == null) {541 if (copy.basename == null) {
509 if (options.format) |f| {542 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) });
511 } else {544 } else {
512 copy.basename = cs.name;545 copy.basename = cs.name;
513 }546 }
514 }547 }
515 return cs.builder.addObjCopy(cs.getOutputSource(), copy);548 return b.addObjCopy(cs.getOutputSource(), copy);
516}549}
517550
518/// Deprecated: use `std.Build.addRunArtifact`551/// Deprecated: use `std.Build.addRunArtifact`
519/// This function will run in the context of the package that created the executable,552/// This function will run in the context of the package that created the executable,
520/// which is undesirable when running an executable provided by a dependency package.553/// which is undesirable when running an executable provided by a dependency package.
521pub fn run(exe: *CompileStep) *RunStep {554pub fn run(cs: *CompileStep) *RunStep {
522 return exe.builder.addRunArtifact(exe);555 return cs.step.owner.addRunArtifact(cs);
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;
537}556}
538557
539pub fn checkObject(self: *CompileStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep {558pub fn checkObject(self: *CompileStep) *CheckObjectStep {
540 return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format);559 return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt);
541}560}
542561
543pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void {562pub 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);
545 source.addStepDependencies(&self.step);565 source.addStepDependencies(&self.step);
546}566}
547567
548pub fn linkFramework(self: *CompileStep, framework_name: []const u8) void {568pub 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");
550}571}
551572
552pub fn linkFrameworkNeeded(self: *CompileStep, framework_name: []const u8) void {573pub 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), .{
554 .needed = true,576 .needed = true,
555 }) catch @panic("OOM");577 }) catch @panic("OOM");
556}578}
557579
558pub fn linkFrameworkWeak(self: *CompileStep, framework_name: []const u8) void {580pub 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), .{
560 .weak = true,583 .weak = true,
561 }) catch @panic("OOM");584 }) catch @panic("OOM");
562}585}
...@@ -595,7 +618,7 @@ pub fn producesPdbFile(self: *CompileStep) bool {...@@ -595,7 +618,7 @@ pub fn producesPdbFile(self: *CompileStep) bool {
595 if (!self.target.isWindows() and !self.target.isUefi()) return false;618 if (!self.target.isWindows() and !self.target.isUefi()) return false;
596 if (self.target.getObjectFormat() == .c) return false;619 if (self.target.getObjectFormat() == .c) return false;
597 if (self.strip == true) return false;620 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";
599}622}
600623
601pub fn linkLibC(self: *CompileStep) void {624pub fn linkLibC(self: *CompileStep) void {
...@@ -609,21 +632,24 @@ pub fn linkLibCpp(self: *CompileStep) void {...@@ -609,21 +632,24 @@ pub fn linkLibCpp(self: *CompileStep) void {
609/// If the value is omitted, it is set to 1.632/// If the value is omitted, it is set to 1.
610/// `name` and `value` need not live longer than the function call.633/// `name` and `value` need not live longer than the function call.
611pub fn defineCMacro(self: *CompileStep, name: []const u8, value: ?[]const u8) void {634pub 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);
613 self.c_macros.append(macro) catch @panic("OOM");637 self.c_macros.append(macro) catch @panic("OOM");
614}638}
615639
616/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1.640/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1.
617pub fn defineCMacroRaw(self: *CompileStep, name_and_value: []const u8) void {641pub 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");
619}644}
620645
621/// This one has no integration with anything, it just puts -lname on the command line.646/// This one has no integration with anything, it just puts -lname on the command line.
622/// Prefer to use `linkSystemLibrary` instead.647/// Prefer to use `linkSystemLibrary` instead.
623pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void {648pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void {
649 const b = self.step.owner;
624 self.link_objects.append(.{650 self.link_objects.append(.{
625 .system_lib = .{651 .system_lib = .{
626 .name = self.builder.dupe(name),652 .name = b.dupe(name),
627 .needed = false,653 .needed = false,
628 .weak = false,654 .weak = false,
629 .use_pkg_config = .no,655 .use_pkg_config = .no,
...@@ -634,9 +660,10 @@ pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void {...@@ -634,9 +660,10 @@ pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void {
634/// This one has no integration with anything, it just puts -needed-lname on the command line.660/// This one has no integration with anything, it just puts -needed-lname on the command line.
635/// Prefer to use `linkSystemLibraryNeeded` instead.661/// Prefer to use `linkSystemLibraryNeeded` instead.
636pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void {662pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void {
663 const b = self.step.owner;
637 self.link_objects.append(.{664 self.link_objects.append(.{
638 .system_lib = .{665 .system_lib = .{
639 .name = self.builder.dupe(name),666 .name = b.dupe(name),
640 .needed = true,667 .needed = true,
641 .weak = false,668 .weak = false,
642 .use_pkg_config = .no,669 .use_pkg_config = .no,
...@@ -647,9 +674,10 @@ pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void {...@@ -647,9 +674,10 @@ pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void {
647/// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the674/// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the
648/// command line. Prefer to use `linkSystemLibraryWeak` instead.675/// command line. Prefer to use `linkSystemLibraryWeak` instead.
649pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void {676pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void {
677 const b = self.step.owner;
650 self.link_objects.append(.{678 self.link_objects.append(.{
651 .system_lib = .{679 .system_lib = .{
652 .name = self.builder.dupe(name),680 .name = b.dupe(name),
653 .needed = false,681 .needed = false,
654 .weak = true,682 .weak = true,
655 .use_pkg_config = .no,683 .use_pkg_config = .no,
...@@ -660,9 +688,10 @@ pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void {...@@ -660,9 +688,10 @@ pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void {
660/// This links against a system library, exclusively using pkg-config to find the library.688/// This links against a system library, exclusively using pkg-config to find the library.
661/// Prefer to use `linkSystemLibrary` instead.689/// Prefer to use `linkSystemLibrary` instead.
662pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void {690pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void {
691 const b = self.step.owner;
663 self.link_objects.append(.{692 self.link_objects.append(.{
664 .system_lib = .{693 .system_lib = .{
665 .name = self.builder.dupe(lib_name),694 .name = b.dupe(lib_name),
666 .needed = false,695 .needed = false,
667 .weak = false,696 .weak = false,
668 .use_pkg_config = .force,697 .use_pkg_config = .force,
...@@ -673,9 +702,10 @@ pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8)...@@ -673,9 +702,10 @@ pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8)
673/// This links against a system library, exclusively using pkg-config to find the library.702/// This links against a system library, exclusively using pkg-config to find the library.
674/// Prefer to use `linkSystemLibraryNeeded` instead.703/// Prefer to use `linkSystemLibraryNeeded` instead.
675pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void {704pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void {
705 const b = self.step.owner;
676 self.link_objects.append(.{706 self.link_objects.append(.{
677 .system_lib = .{707 .system_lib = .{
678 .name = self.builder.dupe(lib_name),708 .name = b.dupe(lib_name),
679 .needed = true,709 .needed = true,
680 .weak = false,710 .weak = false,
681 .use_pkg_config = .force,711 .use_pkg_config = .force,
...@@ -685,14 +715,15 @@ pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []cons...@@ -685,14 +715,15 @@ pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []cons
685715
686/// Run pkg-config for the given library name and parse the output, returning the arguments716/// Run pkg-config for the given library name and parse the output, returning the arguments
687/// that should be passed to zig to link the given library.717/// 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;
689 const pkg_name = match: {720 const pkg_name = match: {
690 // First we have to map the library name to pkg config name. Unfortunately,721 // First we have to map the library name to pkg config name. Unfortunately,
691 // there are several examples where this is not straightforward:722 // there are several examples where this is not straightforward:
692 // -lSDL2 -> pkg-config sdl2723 // -lSDL2 -> pkg-config sdl2
693 // -lgdk-3 -> pkg-config gdk-3.0724 // -lgdk-3 -> pkg-config gdk-3.0
694 // -latk-1.0 -> pkg-config atk725 // -latk-1.0 -> pkg-config atk
695 const pkgs = try getPkgConfigList(self.builder);726 const pkgs = try getPkgConfigList(b);
696727
697 // Exact match means instant winner.728 // Exact match means instant winner.
698 for (pkgs) |pkg| {729 for (pkgs) |pkg| {
...@@ -732,7 +763,7 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u...@@ -732,7 +763,7 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u
732 };763 };
733764
734 var code: u8 = undefined;765 var code: u8 = undefined;
735 const stdout = if (self.builder.execAllowFail(&[_][]const u8{766 const stdout = if (b.execAllowFail(&[_][]const u8{
736 "pkg-config",767 "pkg-config",
737 pkg_name,768 pkg_name,
738 "--cflags",769 "--cflags",
...@@ -745,7 +776,7 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u...@@ -745,7 +776,7 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u
745 else => return err,776 else => return err,
746 };777 };
747778
748 var zig_args = ArrayList([]const u8).init(self.builder.allocator);779 var zig_args = ArrayList([]const u8).init(b.allocator);
749 defer zig_args.deinit();780 defer zig_args.deinit();
750781
751 var it = mem.tokenize(u8, stdout, " \r\n\t");782 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...@@ -770,8 +801,8 @@ pub fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u
770 try zig_args.appendSlice(&[_][]const u8{ "-D", macro });801 try zig_args.appendSlice(&[_][]const u8{ "-D", macro });
771 } else if (mem.startsWith(u8, tok, "-D")) {802 } else if (mem.startsWith(u8, tok, "-D")) {
772 try zig_args.append(tok);803 try zig_args.append(tok);
773 } else if (self.builder.verbose) {804 } else if (b.debug_pkg_config) {
774 log.warn("Ignoring pkg-config flag '{s}'", .{tok});805 return self.step.fail("unknown pkg-config flag '{s}'", .{tok});
775 }806 }
776 }807 }
777808
...@@ -794,6 +825,7 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {...@@ -794,6 +825,7 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {
794 needed: bool = false,825 needed: bool = false,
795 weak: bool = false,826 weak: bool = false,
796}) void {827}) void {
828 const b = self.step.owner;
797 if (isLibCLibrary(name)) {829 if (isLibCLibrary(name)) {
798 self.linkLibC();830 self.linkLibC();
799 return;831 return;
...@@ -805,7 +837,7 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {...@@ -805,7 +837,7 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {
805837
806 self.link_objects.append(.{838 self.link_objects.append(.{
807 .system_lib = .{839 .system_lib = .{
808 .name = self.builder.dupe(name),840 .name = b.dupe(name),
809 .needed = opts.needed,841 .needed = opts.needed,
810 .weak = opts.weak,842 .weak = opts.weak,
811 .use_pkg_config = .yes,843 .use_pkg_config = .yes,
...@@ -813,27 +845,31 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {...@@ -813,27 +845,31 @@ fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct {
813 }) catch @panic("OOM");845 }) catch @panic("OOM");
814}846}
815847
816pub fn setNamePrefix(self: *CompileStep, text: []const u8) void {848pub fn setName(self: *CompileStep, text: []const u8) void {
817 assert(self.kind == .@"test" or self.kind == .test_exe);849 const b = self.step.owner;
818 self.name_prefix = self.builder.dupe(text);850 assert(self.kind == .@"test");
851 self.name = b.dupe(text);
819}852}
820853
821pub fn setFilter(self: *CompileStep, text: ?[]const u8) void {854pub fn setFilter(self: *CompileStep, text: ?[]const u8) void {
822 assert(self.kind == .@"test" or self.kind == .test_exe);855 const b = self.step.owner;
823 self.filter = if (text) |t| self.builder.dupe(t) else null;856 assert(self.kind == .@"test");
857 self.filter = if (text) |t| b.dupe(t) else null;
824}858}
825859
826pub fn setTestRunner(self: *CompileStep, path: ?[]const u8) void {860pub fn setTestRunner(self: *CompileStep, path: ?[]const u8) void {
827 assert(self.kind == .@"test" or self.kind == .test_exe);861 const b = self.step.owner;
828 self.test_runner = if (path) |p| self.builder.dupePath(p) else null;862 assert(self.kind == .@"test");
863 self.test_runner = if (path) |p| b.dupePath(p) else null;
829}864}
830865
831/// Handy when you have many C/C++ source files and want them all to have the same flags.866/// Handy when you have many C/C++ source files and want them all to have the same flags.
832pub fn addCSourceFiles(self: *CompileStep, files: []const []const u8, flags: []const []const u8) void {867pub 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);871 const files_copy = b.dupeStrings(files);
836 const flags_copy = self.builder.dupeStrings(flags);872 const flags_copy = b.dupeStrings(flags);
837873
838 c_source_files.* = .{874 c_source_files.* = .{
839 .files = files_copy,875 .files = files_copy,
...@@ -850,8 +886,9 @@ pub fn addCSourceFile(self: *CompileStep, file: []const u8, flags: []const []con...@@ -850,8 +886,9 @@ pub fn addCSourceFile(self: *CompileStep, file: []const u8, flags: []const []con
850}886}
851887
852pub fn addCSourceFileSource(self: *CompileStep, source: CSourceFile) void {888pub fn addCSourceFileSource(self: *CompileStep, source: CSourceFile) void {
853 const c_source_file = self.builder.allocator.create(CSourceFile) catch @panic("OOM");889 const b = self.step.owner;
854 c_source_file.* = source.dupe(self.builder);890 const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM");
891 c_source_file.* = source.dupe(b);
855 self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM");892 self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM");
856 source.source.addStepDependencies(&self.step);893 source.source.addStepDependencies(&self.step);
857}894}
...@@ -865,52 +902,61 @@ pub fn setVerboseCC(self: *CompileStep, value: bool) void {...@@ -865,52 +902,61 @@ pub fn setVerboseCC(self: *CompileStep, value: bool) void {
865}902}
866903
867pub fn overrideZigLibDir(self: *CompileStep, dir_path: []const u8) void {904pub 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);
869}907}
870908
871pub fn setMainPkgPath(self: *CompileStep, dir_path: []const u8) void {909pub 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);
873}912}
874913
875pub fn setLibCFile(self: *CompileStep, libc_file: ?FileSource) void {914pub 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;
877}917}
878918
879/// Returns the generated executable, library or object file.919/// Returns the generated executable, library or object file.
880/// To run an executable built with zig build, use `run`, or create an install step and invoke it.920/// To run an executable built with zig build, use `run`, or create an install step and invoke it.
881pub fn getOutputSource(self: *CompileStep) FileSource {921pub 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 };
883}927}
884928
885/// Returns the generated import library. This function can only be called for libraries.929/// Returns the generated import library. This function can only be called for libraries.
886pub fn getOutputLibSource(self: *CompileStep) FileSource {930pub fn getOutputLibSource(self: *CompileStep) FileSource {
887 assert(self.kind == .lib);931 assert(self.kind == .lib);
888 return FileSource{ .generated = &self.output_lib_path_source };932 return .{ .generated = &self.output_lib_path_source };
889}933}
890934
891/// Returns the generated header file.935/// Returns the generated header file.
892/// This function can only be called for libraries or object files which have `emit_h` set.936/// This function can only be called for libraries or object files which have `emit_h` set.
893pub fn getOutputHSource(self: *CompileStep) FileSource {937pub 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");
895 assert(self.emit_h);939 assert(self.emit_h);
896 return FileSource{ .generated = &self.output_h_path_source };940 return .{ .generated = &self.output_h_path_source };
897}941}
898942
899/// Returns the generated PDB file. This function can only be called for Windows and UEFI.943/// Returns the generated PDB file. This function can only be called for Windows and UEFI.
900pub fn getOutputPdbSource(self: *CompileStep) FileSource {944pub fn getOutputPdbSource(self: *CompileStep) FileSource {
901 // TODO: Is this right? Isn't PDB for *any* PE/COFF file?945 // TODO: Is this right? Isn't PDB for *any* PE/COFF file?
902 assert(self.target.isWindows() or self.target.isUefi());946 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 };
904}948}
905949
906pub fn addAssemblyFile(self: *CompileStep, path: []const u8) void {950pub fn addAssemblyFile(self: *CompileStep, path: []const u8) void {
951 const b = self.step.owner;
907 self.link_objects.append(.{952 self.link_objects.append(.{
908 .assembly_file = .{ .path = self.builder.dupe(path) },953 .assembly_file = .{ .path = b.dupe(path) },
909 }) catch @panic("OOM");954 }) catch @panic("OOM");
910}955}
911956
912pub fn addAssemblyFileSource(self: *CompileStep, source: FileSource) void {957pub 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);
914 self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM");960 self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM");
915 source_duped.addStepDependencies(&self.step);961 source_duped.addStepDependencies(&self.step);
916}962}
...@@ -920,7 +966,8 @@ pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void {...@@ -920,7 +966,8 @@ pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void {
920}966}
921967
922pub fn addObjectFileSource(self: *CompileStep, source: FileSource) void {968pub 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");
924 source.addStepDependencies(&self.step);971 source.addStepDependencies(&self.step);
925}972}
926973
...@@ -935,11 +982,13 @@ pub const addLibPath = @compileError("deprecated, use addLibraryPath");...@@ -935,11 +982,13 @@ pub const addLibPath = @compileError("deprecated, use addLibraryPath");
935pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath");982pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath");
936983
937pub fn addSystemIncludePath(self: *CompileStep, path: []const u8) void {984pub 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");
939}987}
940988
941pub fn addIncludePath(self: *CompileStep, path: []const u8) void {989pub 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");
943}992}
944993
945pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) void {994pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) void {
...@@ -948,23 +997,42 @@ pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) voi...@@ -948,23 +997,42 @@ pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) voi
948}997}
949998
950pub fn addLibraryPath(self: *CompileStep, path: []const u8) void {999pub 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);
952}1007}
9531008
954pub fn addRPath(self: *CompileStep, path: []const u8) void {1009pub 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);
956}1017}
9571018
958pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void {1019pub 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);
960}1027}
9611028
962/// Adds a module to be used with `@import` and exposing it in the current1029/// Adds a module to be used with `@import` and exposing it in the current
963/// package's module table using `name`.1030/// package's module table using `name`.
964pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void {1031pub 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);
968 defer done.deinit();1036 defer done.deinit();
969 cs.addRecursiveBuildDeps(module, &done) catch @panic("OOM");1037 cs.addRecursiveBuildDeps(module, &done) catch @panic("OOM");
970}1038}
...@@ -972,7 +1040,8 @@ pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void {...@@ -972,7 +1040,8 @@ pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void {
972/// Adds a module to be used with `@import` without exposing it in the current1040/// Adds a module to be used with `@import` without exposing it in the current
973/// package's module table.1041/// package's module table.
974pub fn addAnonymousModule(cs: *CompileStep, name: []const u8, options: std.Build.CreateModuleOptions) void {1042pub 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);
976 return addModule(cs, name, module);1045 return addModule(cs, name, module);
977}1046}
9781047
...@@ -992,12 +1061,13 @@ fn addRecursiveBuildDeps(cs: *CompileStep, module: *Module, done: *std.AutoHashM...@@ -992,12 +1061,13 @@ fn addRecursiveBuildDeps(cs: *CompileStep, module: *Module, done: *std.AutoHashM
992/// If Vcpkg was found on the system, it will be added to include and lib1061/// If Vcpkg was found on the system, it will be added to include and lib
993/// paths for the specified target.1062/// paths for the specified target.
994pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void {1063pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void {
1064 const b = self.step.owner;
995 // Ideally in the Unattempted case we would call the function recursively1065 // Ideally in the Unattempted case we would call the function recursively
996 // after findVcpkgRoot and have only one switch statement, but the compiler1066 // after findVcpkgRoot and have only one switch statement, but the compiler
997 // cannot resolve the error set.1067 // cannot resolve the error set.
998 switch (self.builder.vcpkg_root) {1068 switch (b.vcpkg_root) {
999 .unattempted => {1069 .unattempted => {
1000 self.builder.vcpkg_root = if (try findVcpkgRoot(self.builder.allocator)) |root|1070 b.vcpkg_root = if (try findVcpkgRoot(b.allocator)) |root|
1001 VcpkgRoot{ .found = root }1071 VcpkgRoot{ .found = root }
1002 else1072 else
1003 .not_found;1073 .not_found;
...@@ -1006,31 +1076,32 @@ pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void {...@@ -1006,31 +1076,32 @@ pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void {
1006 .found => {},1076 .found => {},
1007 }1077 }
10081078
1009 switch (self.builder.vcpkg_root) {1079 switch (b.vcpkg_root) {
1010 .unattempted => unreachable,1080 .unattempted => unreachable,
1011 .not_found => return error.VcpkgNotFound,1081 .not_found => return error.VcpkgNotFound,
1012 .found => |root| {1082 .found => |root| {
1013 const allocator = self.builder.allocator;1083 const allocator = b.allocator;
1014 const triplet = try self.target.vcpkgTriplet(allocator, if (linkage == .static) .Static else .Dynamic);1084 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" });
1018 errdefer allocator.free(include_path);1088 errdefer allocator.free(include_path);
1019 try self.include_dirs.append(IncludeDir{ .raw_path = include_path });1089 try self.include_dirs.append(IncludeDir{ .raw_path = include_path });
10201090
1021 const lib_path = self.builder.pathJoin(&.{ root, "installed", triplet, "lib" });1091 const lib_path = b.pathJoin(&.{ root, "installed", triplet, "lib" });
1022 try self.lib_paths.append(lib_path);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" });
1025 },1095 },
1026 }1096 }
1027}1097}
10281098
1029pub fn setExecCmd(self: *CompileStep, args: []const ?[]const u8) void {1099pub fn setExecCmd(self: *CompileStep, args: []const ?[]const u8) void {
1100 const b = self.step.owner;
1030 assert(self.kind == .@"test");1101 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");
1032 for (args, 0..) |arg, i| {1103 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;
1034 }1105 }
1035 self.exec_cmd_args = duped_args;1106 self.exec_cmd_args = duped_args;
1036}1107}
...@@ -1039,22 +1110,27 @@ fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void {...@@ -1039,22 +1110,27 @@ fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void {
1039 self.step.dependOn(&other.step);1110 self.step.dependOn(&other.step);
1040 self.link_objects.append(.{ .other_step = other }) catch @panic("OOM");1111 self.link_objects.append(.{ .other_step = other }) catch @panic("OOM");
1041 self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM");1112 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 }
1042}1117}
10431118
1044fn appendModuleArgs(1119fn appendModuleArgs(
1045 cs: *CompileStep,1120 cs: *CompileStep,
1046 zig_args: *ArrayList([]const u8),1121 zig_args: *ArrayList([]const u8),
1047) error{OutOfMemory}!void {1122) error{OutOfMemory}!void {
1123 const b = cs.step.owner;
1048 // First, traverse the whole dependency graph and give every module a unique name, ideally one1124 // First, traverse the whole dependency graph and give every module a unique name, ideally one
1049 // named after what it's called somewhere in the graph. It will help here to have both a mapping1125 // named after what it's called somewhere in the graph. It will help here to have both a mapping
1050 // from module to name and a set of all the currently-used names.1126 // 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);1127 var mod_names = std.AutoHashMap(*Module, []const u8).init(b.allocator);
1052 var names = std.StringHashMap(void).init(cs.builder.allocator);1128 var names = std.StringHashMap(void).init(b.allocator);
10531129
1054 var to_name = std.ArrayList(struct {1130 var to_name = std.ArrayList(struct {
1055 name: []const u8,1131 name: []const u8,
1056 mod: *Module,1132 mod: *Module,
1057 }).init(cs.builder.allocator);1133 }).init(b.allocator);
1058 {1134 {
1059 var it = cs.modules.iterator();1135 var it = cs.modules.iterator();
1060 while (it.next()) |kv| {1136 while (it.next()) |kv| {
...@@ -1075,7 +1151,7 @@ fn appendModuleArgs(...@@ -1075,7 +1151,7 @@ fn appendModuleArgs(
1075 if (mod_names.contains(dep.mod)) continue;1151 if (mod_names.contains(dep.mod)) continue;
10761152
1077 // We'll use this buffer to store the name we decide on1153 // 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);
1079 // First, try just the exposed dependency name1155 // First, try just the exposed dependency name
1080 std.mem.copy(u8, buf, dep.name);1156 std.mem.copy(u8, buf, dep.name);
1081 var name = buf[0..dep.name.len];1157 var name = buf[0..dep.name.len];
...@@ -1112,15 +1188,15 @@ fn appendModuleArgs(...@@ -1112,15 +1188,15 @@ fn appendModuleArgs(
1112 const mod = kv.key_ptr.*;1188 const mod = kv.key_ptr.*;
1113 const name = kv.value_ptr.*;1189 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);
1116 const src = mod.builder.pathFromRoot(mod.source_file.getPath(mod.builder));1192 const src = mod.builder.pathFromRoot(mod.source_file.getPath(mod.builder));
1117 try zig_args.append("--mod");1193 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 }));
1119 }1195 }
1120 }1196 }
11211197
1122 // Lastly, output the root dependencies1198 // 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);
1124 if (deps_str.len > 0) {1200 if (deps_str.len > 0) {
1125 try zig_args.append("--deps");1201 try zig_args.append("--deps");
1126 try zig_args.append(deps_str);1202 try zig_args.append(deps_str);
...@@ -1150,43 +1226,36 @@ fn constructDepString(...@@ -1150,43 +1226,36 @@ fn constructDepString(
1150 }1226 }
1151}1227}
11521228
1153fn make(step: *Step) !void {1229fn make(step: *Step, prog_node: *std.Progress.Node) !void {
1230 const b = step.owner;
1154 const self = @fieldParentPtr(CompileStep, "step", step);1231 const self = @fieldParentPtr(CompileStep, "step", step);
1155 const builder = self.builder;
11561232
1157 if (self.root_src == null and self.link_objects.items.len == 0) {1233 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});1234 return step.fail("the linker needs one or more objects to link", .{});
1159 return error.NeedAnObject;
1160 }1235 }
11611236
1162 var zig_args = ArrayList([]const u8).init(builder.allocator);1237 var zig_args = ArrayList([]const u8).init(b.allocator);
1163 defer zig_args.deinit();1238 defer zig_args.deinit();
11641239
1165 try zig_args.append(builder.zig_exe);1240 try zig_args.append(b.zig_exe);
11661241
1167 const cmd = switch (self.kind) {1242 const cmd = switch (self.kind) {
1168 .lib => "build-lib",1243 .lib => "build-lib",
1169 .exe => "build-exe",1244 .exe => "build-exe",
1170 .obj => "build-obj",1245 .obj => "build-obj",
1171 .@"test" => "test",1246 .@"test" => "test",
1172 .test_exe => "test",
1173 };1247 };
1174 try zig_args.append(cmd);1248 try zig_args.append(cmd);
11751249
1176 if (builder.color != .auto) {1250 if (b.reference_trace) |some| {
1177 try zig_args.append("--color");1251 try zig_args.append(try std.fmt.allocPrint(b.allocator, "-freference-trace={d}", .{some}));
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}));
1183 }1252 }
11841253
1185 try addFlag(&zig_args, "LLVM", self.use_llvm);1254 try addFlag(&zig_args, "LLVM", self.use_llvm);
1186 try addFlag(&zig_args, "LLD", self.use_lld);1255 try addFlag(&zig_args, "LLD", self.use_lld);
11871256
1188 if (self.target.ofmt) |ofmt| {1257 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)}));
1190 }1259 }
11911260
1192 if (self.entry_symbol_name) |entry| {1261 if (self.entry_symbol_name) |entry| {
...@@ -1196,18 +1265,18 @@ fn make(step: *Step) !void {...@@ -1196,18 +1265,18 @@ fn make(step: *Step) !void {
11961265
1197 if (self.stack_size) |stack_size| {1266 if (self.stack_size) |stack_size| {
1198 try zig_args.append("--stack");1267 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}));
1200 }1269 }
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
1204 // We will add link objects from transitive dependencies, but we want to keep1273 // We will add link objects from transitive dependencies, but we want to keep
1205 // all link objects in the same order provided.1274 // all link objects in the same order provided.
1206 // This array is used to keep self.link_objects immutable.1275 // This array is used to keep self.link_objects immutable.
1207 var transitive_deps: TransitiveDeps = .{1276 var transitive_deps: TransitiveDeps = .{
1208 .link_objects = ArrayList(LinkObject).init(builder.allocator),1277 .link_objects = ArrayList(LinkObject).init(b.allocator),
1209 .seen_system_libs = StringHashMap(void).init(builder.allocator),1278 .seen_system_libs = StringHashMap(void).init(b.allocator),
1210 .seen_steps = std.AutoHashMap(*const Step, void).init(builder.allocator),1279 .seen_steps = std.AutoHashMap(*const Step, void).init(b.allocator),
1211 .is_linking_libcpp = self.is_linking_libcpp,1280 .is_linking_libcpp = self.is_linking_libcpp,
1212 .is_linking_libc = self.is_linking_libc,1281 .is_linking_libc = self.is_linking_libc,
1213 .frameworks = &self.frameworks,1282 .frameworks = &self.frameworks,
...@@ -1220,14 +1289,13 @@ fn make(step: *Step) !void {...@@ -1220,14 +1289,13 @@ fn make(step: *Step) !void {
12201289
1221 for (transitive_deps.link_objects.items) |link_object| {1290 for (transitive_deps.link_objects.items) |link_object| {
1222 switch (link_object) {1291 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
1225 .other_step => |other| switch (other.kind) {1294 .other_step => |other| switch (other.kind) {
1226 .exe => @panic("Cannot link with an executable build artifact"),1295 .exe => @panic("Cannot link with an executable build artifact"),
1227 .test_exe => @panic("Cannot link with an executable build artifact"),
1228 .@"test" => @panic("Cannot link with a test"),1296 .@"test" => @panic("Cannot link with a test"),
1229 .obj => {1297 .obj => {
1230 try zig_args.append(other.getOutputSource().getPath(builder));1298 try zig_args.append(other.getOutputSource().getPath(b));
1231 },1299 },
1232 .lib => l: {1300 .lib => l: {
1233 if (self.isStaticLibrary() and other.isStaticLibrary()) {1301 if (self.isStaticLibrary() and other.isStaticLibrary()) {
...@@ -1235,7 +1303,7 @@ fn make(step: *Step) !void {...@@ -1235,7 +1303,7 @@ fn make(step: *Step) !void {
1235 break :l;1303 break :l;
1236 }1304 }
12371305
1238 const full_path_lib = other.getOutputLibSource().getPath(builder);1306 const full_path_lib = other.getOutputLibSource().getPath(b);
1239 try zig_args.append(full_path_lib);1307 try zig_args.append(full_path_lib);
12401308
1241 if (other.linkage == Linkage.dynamic and !self.target.isWindows()) {1309 if (other.linkage == Linkage.dynamic and !self.target.isWindows()) {
...@@ -1250,14 +1318,11 @@ fn make(step: *Step) !void {...@@ -1250,14 +1318,11 @@ fn make(step: *Step) !void {
1250 .system_lib => |system_lib| {1318 .system_lib => |system_lib| {
1251 const prefix: []const u8 = prefix: {1319 const prefix: []const u8 = prefix: {
1252 if (system_lib.needed) break :prefix "-needed-l";1320 if (system_lib.needed) break :prefix "-needed-l";
1253 if (system_lib.weak) {1321 if (system_lib.weak) break :prefix "-weak-l";
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 }
1257 break :prefix "-l";1322 break :prefix "-l";
1258 };1323 };
1259 switch (system_lib.use_pkg_config) {1324 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 })),
1261 .yes, .force => {1326 .yes, .force => {
1262 if (self.runPkgConfig(system_lib.name)) |args| {1327 if (self.runPkgConfig(system_lib.name)) |args| {
1263 try zig_args.appendSlice(args);1328 try zig_args.appendSlice(args);
...@@ -1271,7 +1336,7 @@ fn make(step: *Step) !void {...@@ -1271,7 +1336,7 @@ fn make(step: *Step) !void {
1271 .yes => {1336 .yes => {
1272 // pkg-config failed, so fall back to linking the library1337 // pkg-config failed, so fall back to linking the library
1273 // by name directly.1338 // by name directly.
1274 try zig_args.append(builder.fmt("{s}{s}", .{1339 try zig_args.append(b.fmt("{s}{s}", .{
1275 prefix,1340 prefix,
1276 system_lib.name,1341 system_lib.name,
1277 }));1342 }));
...@@ -1294,7 +1359,7 @@ fn make(step: *Step) !void {...@@ -1294,7 +1359,7 @@ fn make(step: *Step) !void {
1294 try zig_args.append("--");1359 try zig_args.append("--");
1295 prev_has_extra_flags = false;1360 prev_has_extra_flags = false;
1296 }1361 }
1297 try zig_args.append(asm_file.getPath(builder));1362 try zig_args.append(asm_file.getPath(b));
1298 },1363 },
12991364
1300 .c_source_file => |c_source_file| {1365 .c_source_file => |c_source_file| {
...@@ -1311,7 +1376,7 @@ fn make(step: *Step) !void {...@@ -1311,7 +1376,7 @@ fn make(step: *Step) !void {
1311 }1376 }
1312 try zig_args.append("--");1377 try zig_args.append("--");
1313 }1378 }
1314 try zig_args.append(c_source_file.source.getPath(builder));1379 try zig_args.append(c_source_file.source.getPath(b));
1315 },1380 },
13161381
1317 .c_source_files => |c_source_files| {1382 .c_source_files => |c_source_files| {
...@@ -1329,7 +1394,7 @@ fn make(step: *Step) !void {...@@ -1329,7 +1394,7 @@ fn make(step: *Step) !void {
1329 try zig_args.append("--");1394 try zig_args.append("--");
1330 }1395 }
1331 for (c_source_files.files) |file| {1396 for (c_source_files.files) |file| {
1332 try zig_args.append(builder.pathFromRoot(file));1397 try zig_args.append(b.pathFromRoot(file));
1333 }1398 }
1334 },1399 },
1335 }1400 }
...@@ -1345,7 +1410,7 @@ fn make(step: *Step) !void {...@@ -1345,7 +1410,7 @@ fn make(step: *Step) !void {
13451410
1346 if (self.image_base) |image_base| {1411 if (self.image_base) |image_base| {
1347 try zig_args.append("--image-base");1412 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}));
1349 }1414 }
13501415
1351 if (self.filter) |filter| {1416 if (self.filter) |filter| {
...@@ -1357,39 +1422,34 @@ fn make(step: *Step) !void {...@@ -1357,39 +1422,34 @@ fn make(step: *Step) !void {
1357 try zig_args.append("--test-evented-io");1422 try zig_args.append("--test-evented-io");
1358 }1423 }
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
1365 if (self.test_runner) |test_runner| {1425 if (self.test_runner) |test_runner| {
1366 try zig_args.append("--test-runner");1426 try zig_args.append("--test-runner");
1367 try zig_args.append(builder.pathFromRoot(test_runner));1427 try zig_args.append(b.pathFromRoot(test_runner));
1368 }1428 }
13691429
1370 for (builder.debug_log_scopes) |log_scope| {1430 for (b.debug_log_scopes) |log_scope| {
1371 try zig_args.append("--debug-log");1431 try zig_args.append("--debug-log");
1372 try zig_args.append(log_scope);1432 try zig_args.append(log_scope);
1373 }1433 }
13741434
1375 if (builder.debug_compile_errors) {1435 if (b.debug_compile_errors) {
1376 try zig_args.append("--debug-compile-errors");1436 try zig_args.append("--debug-compile-errors");
1377 }1437 }
13781438
1379 if (builder.verbose_cimport) try zig_args.append("--verbose-cimport");1439 if (b.verbose_cimport) try zig_args.append("--verbose-cimport");
1380 if (builder.verbose_air) try zig_args.append("--verbose-air");1440 if (b.verbose_air) try zig_args.append("--verbose-air");
1381 if (builder.verbose_llvm_ir) try zig_args.append("--verbose-llvm-ir");1441 if (b.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");1442 if (b.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");1443 if (b.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");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);1446 if (self.emit_analysis.getArg(b, "emit-analysis")) |arg| try zig_args.append(arg);
1387 if (self.emit_asm.getArg(builder, "emit-asm")) |arg| try zig_args.append(arg);1447 if (self.emit_asm.getArg(b, "emit-asm")) |arg| try zig_args.append(arg);
1388 if (self.emit_bin.getArg(builder, "emit-bin")) |arg| try zig_args.append(arg);1448 if (self.emit_bin.getArg(b, "emit-bin")) |arg| try zig_args.append(arg);
1389 if (self.emit_docs.getArg(builder, "emit-docs")) |arg| try zig_args.append(arg);1449 if (self.emit_docs.getArg(b, "emit-docs")) |arg| try zig_args.append(arg);
1390 if (self.emit_implib.getArg(builder, "emit-implib")) |arg| try zig_args.append(arg);1450 if (self.emit_implib.getArg(b, "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);1451 if (self.emit_llvm_bc.getArg(b, "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);1452 if (self.emit_llvm_ir.getArg(b, "emit-llvm-ir")) |arg| try zig_args.append(arg);
13931453
1394 if (self.emit_h) try zig_args.append("-femit-h");1454 if (self.emit_h) try zig_args.append("-femit-h");
13951455
...@@ -1430,31 +1490,31 @@ fn make(step: *Step) !void {...@@ -1430,31 +1490,31 @@ fn make(step: *Step) !void {
1430 }1490 }
1431 if (self.link_z_common_page_size) |size| {1491 if (self.link_z_common_page_size) |size| {
1432 try zig_args.append("-z");1492 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}));
1434 }1494 }
1435 if (self.link_z_max_page_size) |size| {1495 if (self.link_z_max_page_size) |size| {
1436 try zig_args.append("-z");1496 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}));
1438 }1498 }
14391499
1440 if (self.libc_file) |libc_file| {1500 if (self.libc_file) |libc_file| {
1441 try zig_args.append("--libc");1501 try zig_args.append("--libc");
1442 try zig_args.append(libc_file.getPath(builder));1502 try zig_args.append(libc_file.getPath(b));
1443 } else if (builder.libc_file) |libc_file| {1503 } else if (b.libc_file) |libc_file| {
1444 try zig_args.append("--libc");1504 try zig_args.append("--libc");
1445 try zig_args.append(libc_file);1505 try zig_args.append(libc_file);
1446 }1506 }
14471507
1448 switch (self.optimize) {1508 switch (self.optimize) {
1449 .Debug => {}, // Skip since it's the default.1509 .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)})),
1451 }1511 }
14521512
1453 try zig_args.append("--cache-dir");1513 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
1456 try zig_args.append("--global-cache-dir");1516 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
1459 try zig_args.append("--name");1519 try zig_args.append("--name");
1460 try zig_args.append(self.name);1520 try zig_args.append(self.name);
...@@ -1466,11 +1526,11 @@ fn make(step: *Step) !void {...@@ -1466,11 +1526,11 @@ fn make(step: *Step) !void {
1466 if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic) {1526 if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic) {
1467 if (self.version) |version| {1527 if (self.version) |version| {
1468 try zig_args.append("--version");1528 try zig_args.append("--version");
1469 try zig_args.append(builder.fmt("{}", .{version}));1529 try zig_args.append(b.fmt("{}", .{version}));
1470 }1530 }
14711531
1472 if (self.target.isDarwin()) {1532 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}", .{
1474 self.target.libPrefix(),1534 self.target.libPrefix(),
1475 self.name,1535 self.name,
1476 self.target.dynamicLibSuffix(),1536 self.target.dynamicLibSuffix(),
...@@ -1484,7 +1544,7 @@ fn make(step: *Step) !void {...@@ -1484,7 +1544,7 @@ fn make(step: *Step) !void {
1484 try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements });1544 try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements });
1485 }1545 }
1486 if (self.pagezero_size) |pagezero_size| {1546 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});
1488 try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size });1548 try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size });
1489 }1549 }
1490 if (self.search_strategy) |strat| switch (strat) {1550 if (self.search_strategy) |strat| switch (strat) {
...@@ -1492,7 +1552,7 @@ fn make(step: *Step) !void {...@@ -1492,7 +1552,7 @@ fn make(step: *Step) !void {
1492 .dylibs_first => try zig_args.append("-search_dylibs_first"),1552 .dylibs_first => try zig_args.append("-search_dylibs_first"),
1493 };1553 };
1494 if (self.headerpad_size) |headerpad_size| {1554 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});
1496 try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size });1556 try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size });
1497 }1557 }
1498 if (self.headerpad_max_install_names) {1558 if (self.headerpad_max_install_names) {
...@@ -1540,16 +1600,16 @@ fn make(step: *Step) !void {...@@ -1540,16 +1600,16 @@ fn make(step: *Step) !void {
1540 try zig_args.append("--export-table");1600 try zig_args.append("--export-table");
1541 }1601 }
1542 if (self.initial_memory) |initial_memory| {1602 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}));
1544 }1604 }
1545 if (self.max_memory) |max_memory| {1605 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}));
1547 }1607 }
1548 if (self.shared_memory) {1608 if (self.shared_memory) {
1549 try zig_args.append("--shared-memory");1609 try zig_args.append("--shared-memory");
1550 }1610 }
1551 if (self.global_base) |global_base| {1611 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}));
1553 }1613 }
15541614
1555 if (self.code_model != .default) {1615 if (self.code_model != .default) {
...@@ -1557,16 +1617,16 @@ fn make(step: *Step) !void {...@@ -1557,16 +1617,16 @@ fn make(step: *Step) !void {
1557 try zig_args.append(@tagName(self.code_model));1617 try zig_args.append(@tagName(self.code_model));
1558 }1618 }
1559 if (self.wasi_exec_model) |model| {1619 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)}));
1561 }1621 }
1562 for (self.export_symbol_names) |symbol_name| {1622 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}));
1564 }1624 }
15651625
1566 if (!self.target.isNative()) {1626 if (!self.target.isNative()) {
1567 try zig_args.appendSlice(&.{1627 try zig_args.appendSlice(&.{
1568 "-target", try self.target.zigTriple(builder.allocator),1628 "-target", try self.target.zigTriple(b.allocator),
1569 "-mcpu", try std.Build.serializeCpu(builder.allocator, self.target.getCpu()),1629 "-mcpu", try std.Build.serializeCpu(b.allocator, self.target.getCpu()),
1570 });1630 });
15711631
1572 if (self.target.dynamic_linker.get()) |dynamic_linker| {1632 if (self.target.dynamic_linker.get()) |dynamic_linker| {
...@@ -1577,12 +1637,12 @@ fn make(step: *Step) !void {...@@ -1577,12 +1637,12 @@ fn make(step: *Step) !void {
15771637
1578 if (self.linker_script) |linker_script| {1638 if (self.linker_script) |linker_script| {
1579 try zig_args.append("--script");1639 try zig_args.append("--script");
1580 try zig_args.append(linker_script.getPath(builder));1640 try zig_args.append(linker_script.getPath(b));
1581 }1641 }
15821642
1583 if (self.version_script) |version_script| {1643 if (self.version_script) |version_script| {
1584 try zig_args.append("--version-script");1644 try zig_args.append("--version-script");
1585 try zig_args.append(builder.pathFromRoot(version_script));1645 try zig_args.append(b.pathFromRoot(version_script));
1586 }1646 }
15871647
1588 if (self.kind == .@"test") {1648 if (self.kind == .@"test") {
...@@ -1595,83 +1655,7 @@ fn make(step: *Step) !void {...@@ -1595,83 +1655,7 @@ fn make(step: *Step) !void {
1595 try zig_args.append("--test-cmd-bin");1655 try zig_args.append("--test-cmd-bin");
1596 }1656 }
1597 }1657 }
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 }
1672 }1658 }
1673 } else if (self.kind == .test_exe) {
1674 try zig_args.append("--test-no-exec");
1675 }1659 }
16761660
1677 try self.appendModuleArgs(&zig_args);1661 try self.appendModuleArgs(&zig_args);
...@@ -1680,18 +1664,18 @@ fn make(step: *Step) !void {...@@ -1680,18 +1664,18 @@ fn make(step: *Step) !void {
1680 switch (include_dir) {1664 switch (include_dir) {
1681 .raw_path => |include_path| {1665 .raw_path => |include_path| {
1682 try zig_args.append("-I");1666 try zig_args.append("-I");
1683 try zig_args.append(builder.pathFromRoot(include_path));1667 try zig_args.append(b.pathFromRoot(include_path));
1684 },1668 },
1685 .raw_path_system => |include_path| {1669 .raw_path_system => |include_path| {
1686 if (builder.sysroot != null) {1670 if (b.sysroot != null) {
1687 try zig_args.append("-iwithsysroot");1671 try zig_args.append("-iwithsysroot");
1688 } else {1672 } else {
1689 try zig_args.append("-isystem");1673 try zig_args.append("-isystem");
1690 }1674 }
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: {
1695 // We need to check for disk designator and strip it out from dir path so1679 // We need to check for disk designator and strip it out from dir path so
1696 // that zig/clang can concat resolved_include_path with sysroot.1680 // that zig/clang can concat resolved_include_path with sysroot.
1697 const disk_designator = fs.path.diskDesignatorWindows(resolved_include_path);1681 const disk_designator = fs.path.diskDesignatorWindows(resolved_include_path);
...@@ -1707,17 +1691,14 @@ fn make(step: *Step) !void {...@@ -1707,17 +1691,14 @@ fn make(step: *Step) !void {
1707 },1691 },
1708 .other_step => |other| {1692 .other_step => |other| {
1709 if (other.emit_h) {1693 if (other.emit_h) {
1710 const h_path = other.getOutputHSource().getPath(builder);1694 const h_path = other.getOutputHSource().getPath(b);
1711 try zig_args.append("-isystem");1695 try zig_args.append("-isystem");
1712 try zig_args.append(fs.path.dirname(h_path).?);1696 try zig_args.append(fs.path.dirname(h_path).?);
1713 }1697 }
1714 if (other.installed_headers.items.len > 0) {1698 if (other.installed_headers.items.len > 0) {
1715 for (other.installed_headers.items) |install_step| {
1716 try install_step.make();
1717 }
1718 try zig_args.append("-I");1699 try zig_args.append("-I");
1719 try zig_args.append(builder.pathJoin(&.{1700 try zig_args.append(b.pathJoin(&.{
1720 other.builder.install_prefix, "include",1701 other.step.owner.install_prefix, "include",
1721 }));1702 }));
1722 }1703 }
1723 },1704 },
...@@ -1729,33 +1710,35 @@ fn make(step: *Step) !void {...@@ -1729,33 +1710,35 @@ fn make(step: *Step) !void {
1729 }1710 }
1730 }1711 }
17311712
1732 for (self.lib_paths.items) |lib_path| {1713 for (self.c_macros.items) |c_macro| {
1733 try zig_args.append("-L");1714 try zig_args.append("-D");
1734 try zig_args.append(lib_path);1715 try zig_args.append(c_macro);
1735 }1716 }
17361717
1737 for (self.rpaths.items) |rpath| {1718 try zig_args.ensureUnusedCapacity(2 * self.lib_paths.items.len);
1738 try zig_args.append("-rpath");1719 for (self.lib_paths.items) |lib_path| {
1739 try zig_args.append(rpath);1720 zig_args.appendAssumeCapacity("-L");
1721 zig_args.appendAssumeCapacity(lib_path.getPath2(b, step));
1740 }1722 }
17411723
1742 for (self.c_macros.items) |c_macro| {1724 try zig_args.ensureUnusedCapacity(2 * self.rpaths.items.len);
1743 try zig_args.append("-D");1725 for (self.rpaths.items) |rpath| {
1744 try zig_args.append(c_macro);1726 zig_args.appendAssumeCapacity("-rpath");
1727 zig_args.appendAssumeCapacity(rpath.getPath2(b, step));
1745 }1728 }
17461729
1747 if (self.target.isDarwin()) {1730 for (self.framework_dirs.items) |directory_source| {
1748 for (self.framework_dirs.items) |dir| {1731 if (b.sysroot != null) {
1749 if (builder.sysroot != null) {1732 try zig_args.append("-iframeworkwithsysroot");
1750 try zig_args.append("-iframeworkwithsysroot");1733 } else {
1751 } else {1734 try zig_args.append("-iframework");
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);
1757 }1735 }
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 {
1759 var it = self.frameworks.iterator();1742 var it = self.frameworks.iterator();
1760 while (it.next()) |entry| {1743 while (it.next()) |entry| {
1761 const name = entry.key_ptr.*;1744 const name = entry.key_ptr.*;
...@@ -1769,29 +1752,45 @@ fn make(step: *Step) !void {...@@ -1769,29 +1752,45 @@ fn make(step: *Step) !void {
1769 }1752 }
1770 try zig_args.append(name);1753 try zig_args.append(name);
1771 }1754 }
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 }
1780 }1755 }
17811756
1782 if (builder.sysroot) |sysroot| {1757 if (b.sysroot) |sysroot| {
1783 try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot });1758 try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot });
1784 }1759 }
17851760
1786 for (builder.search_prefixes.items) |search_prefix| {1761 for (b.search_prefixes.items) |search_prefix| {
1787 try zig_args.append("-L");1762 var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| {
1788 try zig_args.append(builder.pathJoin(&.{1763 return step.fail("unable to open prefix directory '{s}': {s}", .{
1789 search_prefix, "lib",1764 search_prefix, @errorName(err),
1790 }));1765 });
1791 try zig_args.append("-I");1766 };
1792 try zig_args.append(builder.pathJoin(&.{1767 defer prefix_dir.close();
1793 search_prefix, "include",1768
1794 }));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 }
1795 }1794 }
17961795
1797 try addFlag(&zig_args, "valgrind", self.valgrind_support);1796 try addFlag(&zig_args, "valgrind", self.valgrind_support);
...@@ -1800,15 +1799,15 @@ fn make(step: *Step) !void {...@@ -1800,15 +1799,15 @@ fn make(step: *Step) !void {
18001799
1801 if (self.zig_lib_dir) |dir| {1800 if (self.zig_lib_dir) |dir| {
1802 try zig_args.append("--zig-lib-dir");1801 try zig_args.append("--zig-lib-dir");
1803 try zig_args.append(builder.pathFromRoot(dir));1802 try zig_args.append(b.pathFromRoot(dir));
1804 } else if (builder.zig_lib_dir) |dir| {1803 } else if (b.zig_lib_dir) |dir| {
1805 try zig_args.append("--zig-lib-dir");1804 try zig_args.append("--zig-lib-dir");
1806 try zig_args.append(dir);1805 try zig_args.append(dir);
1807 }1806 }
18081807
1809 if (self.main_pkg_path) |dir| {1808 if (self.main_pkg_path) |dir| {
1810 try zig_args.append("--main-pkg-path");1809 try zig_args.append("--main-pkg-path");
1811 try zig_args.append(builder.pathFromRoot(dir));1810 try zig_args.append(b.pathFromRoot(dir));
1812 }1811 }
18131812
1814 try addFlag(&zig_args, "PIC", self.force_pic);1813 try addFlag(&zig_args, "PIC", self.force_pic);
...@@ -1830,6 +1829,7 @@ fn make(step: *Step) !void {...@@ -1830,6 +1829,7 @@ fn make(step: *Step) !void {
1830 }1829 }
18311830
1832 try zig_args.append("--enable-cache");1831 try zig_args.append("--enable-cache");
1832 try zig_args.append("--listen=-");
18331833
1834 // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux1834 // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux
1835 // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and1835 // 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 {...@@ -1840,15 +1840,15 @@ fn make(step: *Step) !void {
1840 args_length += arg.len + 1; // +1 to account for null terminator1840 args_length += arg.len + 1; // +1 to account for null terminator
1841 }1841 }
1842 if (args_length >= 30 * 1024) {1842 if (args_length >= 30 * 1024) {
1843 try builder.cache_root.handle.makePath("args");1843 try b.cache_root.handle.makePath("args");
18441844
1845 const args_to_escape = zig_args.items[2..];1845 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);
1847 arg_blk: for (args_to_escape) |arg| {1847 arg_blk: for (args_to_escape) |arg| {
1848 for (arg, 0..) |c, arg_idx| {1848 for (arg, 0..) |c, arg_idx| {
1849 if (c == '\\' or c == '"') {1849 if (c == '\\' or c == '"') {
1850 // Slow path for arguments that need to be escaped. We'll need to allocate and copy1850 // 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);
1852 const writer = escaped.writer();1852 const writer = escaped.writer();
1853 try writer.writeAll(arg[0..arg_idx]);1853 try writer.writeAll(arg[0..arg_idx]);
1854 for (arg[arg_idx..]) |to_escape| {1854 for (arg[arg_idx..]) |to_escape| {
...@@ -1864,8 +1864,8 @@ fn make(step: *Step) !void {...@@ -1864,8 +1864,8 @@ fn make(step: *Step) !void {
18641864
1865 // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with1865 // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with
1866 // other zig build commands running in parallel.1866 // other zig build commands running in parallel.
1867 const partially_quoted = try std.mem.join(builder.allocator, "\" \"", escaped_args.items);1867 const partially_quoted = try std.mem.join(b.allocator, "\" \"", escaped_args.items);
1868 const args = try std.mem.concat(builder.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" });1868 const args = try std.mem.concat(b.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" });
18691869
1870 var args_hash: [Sha256.digest_length]u8 = undefined;1870 var args_hash: [Sha256.digest_length]u8 = undefined;
1871 Sha256.hash(args, &args_hash, .{});1871 Sha256.hash(args, &args_hash, .{});
...@@ -1877,28 +1877,35 @@ fn make(step: *Step) !void {...@@ -1877,28 +1877,35 @@ fn make(step: *Step) !void {
1877 );1877 );
18781878
1879 const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash;1879 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, &.{
1883 "@",1883 "@",
1884 try builder.cache_root.join(builder.allocator, &.{args_file}),1884 try b.cache_root.join(b.allocator, &.{args_file}),
1885 });1885 });
18861886
1887 zig_args.shrinkRetainingCapacity(2);1887 zig_args.shrinkRetainingCapacity(2);
1888 try zig_args.append(resolved_args_file);1888 try zig_args.append(resolved_args_file);
1889 }1889 }
18901890
1891 const output_dir_nl = try builder.execFromStep(zig_args.items, &self.step);1891 const output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) {
1892 const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");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
1894 if (self.output_dir) |output_dir| {1901 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, .{});
1896 defer src_dir.close();1903 defer src_dir.close();
18971904
1898 // Create the output directory if it doesn't exist.1905 // 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, .{});
1902 defer dest_dir.close();1909 defer dest_dir.close();
19031910
1904 var it = src_dir.iterate();1911 var it = src_dir.iterate();
...@@ -1922,25 +1929,34 @@ fn make(step: *Step) !void {...@@ -1922,25 +1929,34 @@ fn make(step: *Step) !void {
19221929
1923 // Update generated files1930 // Update generated files
1924 if (self.output_dir != null) {1931 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(
1926 &.{ self.output_dir.?, self.out_filename },1935 &.{ self.output_dir.?, self.out_filename },
1927 );1936 );
19281937
1929 if (self.emit_h) {1938 if (self.emit_h) {
1930 self.output_h_path_source.path = builder.pathJoin(1939 self.output_h_path_source.path = b.pathJoin(
1931 &.{ self.output_dir.?, self.out_h_filename },1940 &.{ self.output_dir.?, self.out_h_filename },
1932 );1941 );
1933 }1942 }
19341943
1935 if (self.target.isWindows() or self.target.isUefi()) {1944 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(
1937 &.{ self.output_dir.?, self.out_pdb_filename },1946 &.{ self.output_dir.?, self.out_pdb_filename },
1938 );1947 );
1939 }1948 }
1940 }1949 }
19411950
1942 if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and self.version != null and self.target.wantSharedLibSymLinks()) {1951 if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and
1943 try doAtomicSymLinks(builder.allocator, self.getOutputSource().getPath(builder), self.major_only_filename.?, self.name_only_filename.?);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 );
1944 }1960 }
1945}1961}
19461962
...@@ -1982,30 +1998,27 @@ fn findVcpkgRoot(allocator: Allocator) !?[]const u8 {...@@ -1982,30 +1998,27 @@ fn findVcpkgRoot(allocator: Allocator) !?[]const u8 {
1982}1998}
19831999
1984pub fn doAtomicSymLinks(2000pub fn doAtomicSymLinks(
1985 allocator: Allocator,2001 step: *Step,
1986 output_path: []const u8,2002 output_path: []const u8,
1987 filename_major_only: []const u8,2003 filename_major_only: []const u8,
1988 filename_name_only: []const u8,2004 filename_name_only: []const u8,
1989) !void {2005) !void {
2006 const arena = step.owner.allocator;
1990 const out_dir = fs.path.dirname(output_path) orelse ".";2007 const out_dir = fs.path.dirname(output_path) orelse ".";
1991 const out_basename = fs.path.basename(output_path);2008 const out_basename = fs.path.basename(output_path);
1992 // sym link for libfoo.so.1 to libfoo.so.1.2.32009 // sym link for libfoo.so.1 to libfoo.so.1.2.3
1993 const major_only_path = try fs.path.join(2010 const major_only_path = try fs.path.join(arena, &.{ out_dir, filename_major_only });
1994 allocator,2011 fs.atomicSymLink(arena, out_basename, major_only_path) catch |err| {
1995 &[_][]const u8{ out_dir, filename_major_only },2012 return step.fail("unable to symlink {s} -> {s}: {s}", .{
1996 );2013 major_only_path, out_basename, @errorName(err),
1997 fs.atomicSymLink(allocator, out_basename, major_only_path) catch |err| {2014 });
1998 log.err("Unable to symlink {s} -> {s}", .{ major_only_path, out_basename });
1999 return err;
2000 };2015 };
2001 // sym link for libfoo.so to libfoo.so.12016 // sym link for libfoo.so to libfoo.so.1
2002 const name_only_path = try fs.path.join(2017 const name_only_path = try fs.path.join(arena, &.{ out_dir, filename_name_only });
2003 allocator,2018 fs.atomicSymLink(arena, filename_major_only, name_only_path) catch |err| {
2004 &[_][]const u8{ out_dir, filename_name_only },2019 return step.fail("Unable to symlink {s} -> {s}: {s}", .{
2005 );2020 name_only_path, filename_major_only, @errorName(err),
2006 fs.atomicSymLink(allocator, filename_major_only, name_only_path) catch |err| {2021 });
2007 log.err("Unable to symlink {s} -> {s}", .{ name_only_path, filename_major_only });
2008 return err;
2009 };2022 };
2010}2023}
20112024
...@@ -2117,3 +2130,57 @@ const TransitiveDeps = struct {...@@ -2117,3 +2130,57 @@ const TransitiveDeps = struct {
2117 }2130 }
2118 }2131 }
2119};2132};
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 @@...@@ -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
7pub const Style = union(enum) {1pub const Style = union(enum) {
8 /// The configure format supported by autotools. It uses `#undef foo` to2 /// The configure format supported by autotools. It uses `#undef foo` to
9 /// mark lines that can be substituted with different values.3 /// mark lines that can be substituted with different values.
...@@ -34,7 +28,6 @@ pub const Value = union(enum) {...@@ -34,7 +28,6 @@ pub const Value = union(enum) {
34};28};
3529
36step: Step,30step: Step,
37builder: *std.Build,
38values: std.StringArrayHashMap(Value),31values: std.StringArrayHashMap(Value),
39output_file: std.Build.GeneratedFile,32output_file: std.Build.GeneratedFile,
4033
...@@ -42,43 +35,57 @@ style: Style,...@@ -42,43 +35,57 @@ style: Style,
42max_bytes: usize,35max_bytes: usize,
43include_path: []const u8,36include_path: []const u8,
4437
38pub const base_id: Step.Id = .config_header;
39
45pub const Options = struct {40pub const Options = struct {
46 style: Style = .blank,41 style: Style = .blank,
47 max_bytes: usize = 2 * 1024 * 1024,42 max_bytes: usize = 2 * 1024 * 1024,
48 include_path: ?[]const u8 = null,43 include_path: ?[]const u8 = null,
44 first_ret_addr: ?usize = null,
49};45};
5046
51pub fn create(builder: *std.Build, options: Options) *ConfigHeaderStep {47pub fn create(owner: *std.Build, options: Options) *ConfigHeaderStep {
52 const self = builder.allocator.create(ConfigHeaderStep) catch @panic("OOM");48 const self = owner.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),
6249
63 .max_bytes = options.max_bytes,50 var include_path: []const u8 = "config.h";
64 .include_path = "config.h",
65 .output_file = .{ .step = &self.step },
66 };
6751
68 if (options.style.getFileSource()) |s| switch (s) {52 if (options.style.getFileSource()) |s| switch (s) {
69 .path => |p| {53 .path => |p| {
70 const basename = std.fs.path.basename(p);54 const basename = std.fs.path.basename(p);
71 if (std.mem.endsWith(u8, basename, ".h.in")) {55 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];
73 }57 }
74 },58 },
75 else => {},59 else => {},
76 };60 };
7761
78 if (options.include_path) |include_path| {62 if (options.include_path) |p| {
79 self.include_path = include_path;63 include_path = p;
80 }64 }
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
82 return self;89 return self;
83}90}
8491
...@@ -146,26 +153,20 @@ fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v...@@ -146,26 +153,20 @@ fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v
146 }153 }
147}154}
148155
149fn make(step: *Step) !void {156fn make(step: *Step, prog_node: *std.Progress.Node) !void {
157 _ = prog_node;
158 const b = step.owner;
150 const self = @fieldParentPtr(ConfigHeaderStep, "step", step);159 const self = @fieldParentPtr(ConfigHeaderStep, "step", step);
151 const gpa = self.builder.allocator;160 const gpa = b.allocator;
152161 const arena = b.allocator;
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.
156162
157 // If, for example, a hard-coded path was used as the location to put ConfigHeaderStep163 var man = b.cache.obtain();
158 // files, then two ConfigHeaderStep executing in parallel might clobber each other.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);
165 // Random bytes to make ConfigHeaderStep unique. Refresh this with new166 // Random bytes to make ConfigHeaderStep unique. Refresh this with new
166 // random bytes when ConfigHeaderStep implementation is modified in a167 // random bytes when ConfigHeaderStep implementation is modified in a
167 // non-backwards-compatible way.168 // non-backwards-compatible way.
168 var hash = Hasher.init("PGuDTpidxyMqnkGM");169 man.hash.add(@as(u32, 0xdef08d23));
169170
170 var output = std.ArrayList(u8).init(gpa);171 var output = std.ArrayList(u8).init(gpa);
171 defer output.deinit();172 defer output.deinit();
...@@ -177,15 +178,15 @@ fn make(step: *Step) !void {...@@ -177,15 +178,15 @@ fn make(step: *Step) !void {
177 switch (self.style) {178 switch (self.style) {
178 .autoconf => |file_source| {179 .autoconf => |file_source| {
179 try output.appendSlice(c_generated_line);180 try output.appendSlice(c_generated_line);
180 const src_path = file_source.getPath(self.builder);181 const src_path = file_source.getPath(b);
181 const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);182 const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes);
182 try render_autoconf(contents, &output, self.values, src_path);183 try render_autoconf(step, contents, &output, self.values, src_path);
183 },184 },
184 .cmake => |file_source| {185 .cmake => |file_source| {
185 try output.appendSlice(c_generated_line);186 try output.appendSlice(c_generated_line);
186 const src_path = file_source.getPath(self.builder);187 const src_path = file_source.getPath(b);
187 const contents = try std.fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);188 const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes);
188 try render_cmake(contents, &output, self.values, src_path);189 try render_cmake(step, contents, &output, self.values, src_path);
189 },190 },
190 .blank => {191 .blank => {
191 try output.appendSlice(c_generated_line);192 try output.appendSlice(c_generated_line);
...@@ -197,43 +198,44 @@ fn make(step: *Step) !void {...@@ -197,43 +198,44 @@ fn make(step: *Step) !void {
197 },198 },
198 }199 }
199200
200 hash.update(output.items);201 man.hash.addBytes(output.items);
201202
202 var digest: [16]u8 = undefined;203 if (try step.cacheHit(&man)) {
203 hash.final(&digest);204 const digest = man.final();
204 var hash_basename: [digest.len * 2]u8 = undefined;205 self.output_file.path = try b.cache_root.join(arena, &.{
205 _ = std.fmt.bufPrint(206 "o", &digest, self.include_path,
206 &hash_basename,207 });
207 "{s}",208 return;
208 .{std.fmt.fmtSliceHexLower(&digest)},209 }
209 ) catch unreachable;
210210
211 const output_dir = try self.builder.cache_root.join(gpa, &.{ "o", &hash_basename });211 const digest = man.final();
212212
213 // If output_path has directory parts, deal with them. Example:213 // If output_path has directory parts, deal with them. Example:
214 // output_dir is zig-cache/o/HASH214 // output_dir is zig-cache/o/HASH
215 // output_path is libavutil/avconfig.h215 // output_path is libavutil/avconfig.h
216 // We want to open directory zig-cache/o/HASH/libavutil/216 // We want to open directory zig-cache/o/HASH/libavutil/
217 // but keep output_dir as zig-cache/o/HASH for -I include217 // 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|218 const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, self.include_path });
219 try std.fs.path.join(gpa, &.{ output_dir, d })219 const sub_path_dirname = std.fs.path.dirname(sub_path).?;
220 else
221 output_dir;
222220
223 var dir = std.fs.cwd().makeOpenPath(sub_dir_path, .{}) catch |err| {221 b.cache_root.handle.makePath(sub_path_dirname) catch |err| {
224 std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });222 return step.fail("unable to make path '{}{s}': {s}", .{
225 return err;223 b.cache_root, sub_path_dirname, @errorName(err),
224 });
226 };225 };
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, &.{233 self.output_file.path = try b.cache_root.join(arena, &.{sub_path});
232 output_dir, self.include_path,234 try man.writeManifest();
233 });
234}235}
235236
236fn render_autoconf(237fn render_autoconf(
238 step: *Step,
237 contents: []const u8,239 contents: []const u8,
238 output: *std.ArrayList(u8),240 output: *std.ArrayList(u8),
239 values: std.StringArrayHashMap(Value),241 values: std.StringArrayHashMap(Value),
...@@ -260,7 +262,7 @@ fn render_autoconf(...@@ -260,7 +262,7 @@ fn render_autoconf(
260 }262 }
261 const name = it.rest();263 const name = it.rest();
262 const kv = values_copy.fetchSwapRemove(name) orelse {264 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}'", .{
264 src_path, line_index + 1, name,266 src_path, line_index + 1, name,
265 });267 });
266 any_errors = true;268 any_errors = true;
...@@ -270,15 +272,17 @@ fn render_autoconf(...@@ -270,15 +272,17 @@ fn render_autoconf(
270 }272 }
271273
272 for (values_copy.keys()) |name| {274 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;
274 }277 }
275278
276 if (any_errors) {279 if (any_errors) {
277 return error.HeaderConfigFailed;280 return error.MakeFailed;
278 }281 }
279}282}
280283
281fn render_cmake(284fn render_cmake(
285 step: *Step,
282 contents: []const u8,286 contents: []const u8,
283 output: *std.ArrayList(u8),287 output: *std.ArrayList(u8),
284 values: std.StringArrayHashMap(Value),288 values: std.StringArrayHashMap(Value),
...@@ -304,14 +308,14 @@ fn render_cmake(...@@ -304,14 +308,14 @@ fn render_cmake(
304 continue;308 continue;
305 }309 }
306 const name = it.next() orelse {310 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", .{
308 src_path, line_index + 1,312 src_path, line_index + 1,
309 });313 });
310 any_errors = true;314 any_errors = true;
311 continue;315 continue;
312 };316 };
313 const kv = values_copy.fetchSwapRemove(name) orelse {317 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}'", .{
315 src_path, line_index + 1, name,319 src_path, line_index + 1, name,
316 });320 });
317 any_errors = true;321 any_errors = true;
...@@ -321,7 +325,8 @@ fn render_cmake(...@@ -321,7 +325,8 @@ fn render_cmake(
321 }325 }
322326
323 for (values_copy.keys()) |name| {327 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;
325 }330 }
326331
327 if (any_errors) {332 if (any_errors) {
...@@ -426,3 +431,7 @@ fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !...@@ -426,3 +431,7 @@ fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !
426 },431 },
427 }432 }
428}433}
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 @@...@@ -1,32 +1,73 @@
1const std = @import("../std.zig");1//! This step has two modes:
2const Step = std.Build.Step;2//! * Modify mode: directly modify source files, formatting them in place.
3const FmtStep = @This();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
5pub const base_id = .fmt;10pub const base_id = .fmt;
611
7step: Step,12pub const Options = struct {
8builder: *std.Build,13 paths: []const []const u8 = &.{},
9argv: [][]const u8,14 exclude_paths: []const []const u8 = &.{},
1015 /// If true, fails the build step when any non-conforming files are encountered.
11pub fn create(builder: *std.Build, paths: []const []const u8) *FmtStep {16 check: bool = false,
12 const self = builder.allocator.create(FmtStep) catch @panic("OOM");17};
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 };
1918
20 self.argv[0] = builder.zig_exe;19pub fn create(owner: *std.Build, options: Options) *FmtStep {
21 self.argv[1] = "fmt";20 const self = owner.allocator.create(FmtStep) catch @panic("OOM");
22 for (paths, 0..) |path, i| {21 const name = if (options.check) "zig fmt --check" else "zig fmt";
23 self.argv[2 + i] = builder.pathFromRoot(path);22 self.* = .{
24 }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 };
25 return self;33 return self;
26}34}
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;
29 const self = @fieldParentPtr(FmtStep, "step", step);47 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);
32}69}
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;...@@ -3,83 +3,133 @@ const Step = std.Build.Step;
3const CompileStep = std.Build.CompileStep;3const CompileStep = std.Build.CompileStep;
4const InstallDir = std.Build.InstallDir;4const InstallDir = std.Build.InstallDir;
5const InstallArtifactStep = @This();5const InstallArtifactStep = @This();
6const fs = std.fs;
67
7pub const base_id = .install_artifact;8pub const base_id = .install_artifact;
89
9step: Step,10step: Step,
10builder: *std.Build,11dest_builder: *std.Build,
11artifact: *CompileStep,12artifact: *CompileStep,
12dest_dir: InstallDir,13dest_dir: InstallDir,
13pdb_dir: ?InstallDir,14pdb_dir: ?InstallDir,
14h_dir: ?InstallDir,15h_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 {
17 if (artifact.install_step) |s| return s;21 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");
20 self.* = InstallArtifactStep{24 self.* = InstallArtifactStep{
21 .builder = builder,25 .step = Step.init(.{
22 .step = Step.init(.install_artifact, builder.fmt("install {s}", .{artifact.step.name}), builder.allocator, make),26 .id = base_id,
27 .name = owner.fmt("install {s}", .{artifact.name}),
28 .owner = owner,
29 .makeFn = make,
30 }),
31 .dest_builder = owner,
23 .artifact = artifact,32 .artifact = artifact,
24 .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {33 .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {
25 .obj => @panic("Cannot install a .obj build artifact."),34 .obj => @panic("Cannot install a .obj build artifact."),
26 .@"test" => @panic("Cannot install a .test build artifact, use .test_exe instead."),35 .exe, .@"test" => InstallDir{ .bin = {} },
27 .exe, .test_exe => InstallDir{ .bin = {} },
28 .lib => InstallDir{ .lib = {} },36 .lib => InstallDir{ .lib = {} },
29 },37 },
30 .pdb_dir = if (artifact.producesPdbFile()) blk: {38 .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") {
32 break :blk InstallDir{ .bin = {} };40 break :blk InstallDir{ .bin = {} };
33 } else {41 } else {
34 break :blk InstallDir{ .lib = {} };42 break :blk InstallDir{ .lib = {} };
35 }43 }
36 } else null,44 } else null,
37 .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,45 .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,
46 .dest_sub_path = null,
38 };47 };
39 self.step.dependOn(&artifact.step);48 self.step.dependOn(&artifact.step);
40 artifact.install_step = self;49 artifact.install_step = self;
4150
42 builder.pushInstalledFile(self.dest_dir, artifact.out_filename);51 owner.pushInstalledFile(self.dest_dir, artifact.out_filename);
43 if (self.artifact.isDynamicLibrary()) {52 if (self.artifact.isDynamicLibrary()) {
44 if (artifact.major_only_filename) |name| {53 if (artifact.major_only_filename) |name| {
45 builder.pushInstalledFile(.lib, name);54 owner.pushInstalledFile(.lib, name);
46 }55 }
47 if (artifact.name_only_filename) |name| {56 if (artifact.name_only_filename) |name| {
48 builder.pushInstalledFile(.lib, name);57 owner.pushInstalledFile(.lib, name);
49 }58 }
50 if (self.artifact.target.isWindows()) {59 if (self.artifact.target.isWindows()) {
51 builder.pushInstalledFile(.lib, artifact.out_lib_filename);60 owner.pushInstalledFile(.lib, artifact.out_lib_filename);
52 }61 }
53 }62 }
54 if (self.pdb_dir) |pdb_dir| {63 if (self.pdb_dir) |pdb_dir| {
55 builder.pushInstalledFile(pdb_dir, artifact.out_pdb_filename);64 owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename);
56 }65 }
57 if (self.h_dir) |h_dir| {66 if (self.h_dir) |h_dir| {
58 builder.pushInstalledFile(h_dir, artifact.out_h_filename);67 owner.pushInstalledFile(h_dir, artifact.out_h_filename);
59 }68 }
60 return self;69 return self;
61}70}
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;
64 const self = @fieldParentPtr(InstallArtifactStep, "step", step);75 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);78 const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename;
68 try builder.updateFile(self.artifact.getOutputSource().getPath(builder), full_dest_path);79 const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path);
69 if (self.artifact.isDynamicLibrary() and self.artifact.version != null and self.artifact.target.wantSharedLibSymLinks()) {80 const cwd = fs.cwd();
70 try CompileStep.doAtomicSymLinks(builder.allocator, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?);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.?);
71 }99 }
72 if (self.artifact.isDynamicLibrary() and self.artifact.target.isWindows() and self.artifact.emit_implib != .no_emit) {100 if (self.artifact.isDynamicLibrary() and
73 const full_implib_path = builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename);101 self.artifact.target.isWindows() and
74 try builder.updateFile(self.artifact.getOutputLibSource().getPath(builder), full_implib_path);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;
75 }112 }
76 if (self.pdb_dir) |pdb_dir| {113 if (self.pdb_dir) |pdb_dir| {
77 const full_pdb_path = builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename);114 const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder);
78 try builder.updateFile(self.artifact.getOutputPdbSource().getPath(builder), full_pdb_path);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;
79 }122 }
80 if (self.h_dir) |h_dir| {123 if (self.h_dir) |h_dir| {
81 const full_h_path = builder.getInstallPath(h_dir, self.artifact.out_h_filename);124 const full_src_path = self.artifact.getOutputHSource().getPath(src_builder);
82 try builder.updateFile(self.artifact.getOutputHSource().getPath(builder), full_h_path);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;
83 }132 }
84 self.artifact.installed_path = full_dest_path;133 self.artifact.installed_path = full_dest_path;
134 step.result_cached = all_cached;
85}135}
lib/std/Build/InstallDirStep.zig+43-26
...@@ -4,14 +4,12 @@ const fs = std.fs;...@@ -4,14 +4,12 @@ const fs = std.fs;
4const Step = std.Build.Step;4const Step = std.Build.Step;
5const InstallDir = std.Build.InstallDir;5const InstallDir = std.Build.InstallDir;
6const InstallDirStep = @This();6const InstallDirStep = @This();
7const log = std.log;
87
9step: Step,8step: Step,
10builder: *std.Build,
11options: Options,9options: Options,
12/// This is used by the build system when a file being installed comes from one10/// This is used by the build system when a file being installed comes from one
13/// package but is being installed by another.11/// package but is being installed by another.
14override_source_builder: ?*std.Build = null,12dest_builder: *std.Build,
1513
16pub const base_id = .install_dir;14pub const base_id = .install_dir;
1715
...@@ -40,31 +38,35 @@ pub const Options = struct {...@@ -40,31 +38,35 @@ pub const Options = struct {
40 }38 }
41};39};
4240
43pub fn init(41pub fn init(owner: *std.Build, options: Options) InstallDirStep {
44 builder: *std.Build,42 owner.pushInstalledFile(options.install_dir, options.install_subdir);
45 options: Options,43 return .{
46) InstallDirStep {44 .step = Step.init(.{
47 builder.pushInstalledFile(options.install_dir, options.install_subdir);45 .id = .install_dir,
48 return InstallDirStep{46 .name = owner.fmt("install {s}/", .{options.source_dir}),
49 .builder = builder,47 .owner = owner,
50 .step = Step.init(.install_dir, builder.fmt("install {s}/", .{options.source_dir}), builder.allocator, make),48 .makeFn = make,
51 .options = options.dupe(builder),49 }),
50 .options = options.dupe(owner),
51 .dest_builder = owner,
52 };52 };
53}53}
5454
55fn make(step: *Step) !void {55fn make(step: *Step, prog_node: *std.Progress.Node) !void {
56 _ = prog_node;
56 const self = @fieldParentPtr(InstallDirStep, "step", step);57 const self = @fieldParentPtr(InstallDirStep, "step", step);
57 const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir);58 const dest_builder = self.dest_builder;
58 const src_builder = self.override_source_builder orelse self.builder;59 const arena = dest_builder.allocator;
59 const full_src_dir = src_builder.pathFromRoot(self.options.source_dir);60 const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
60 var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| {61 const src_builder = self.step.owner;
61 log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{62 var src_dir = src_builder.build_root.handle.openIterableDir(self.options.source_dir, .{}) catch |err| {
62 full_src_dir, @errorName(err),63 return step.fail("unable to open source directory '{}{s}': {s}", .{
64 src_builder.build_root, self.options.source_dir, @errorName(err),
63 });65 });
64 return error.StepFailed;
65 };66 };
66 defer src_dir.close();67 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;
68 next_entry: while (try it.next()) |entry| {70 next_entry: while (try it.next()) |entry| {
69 for (self.options.exclude_extensions) |ext| {71 for (self.options.exclude_extensions) |ext| {
70 if (mem.endsWith(u8, entry.path, ext)) {72 if (mem.endsWith(u8, entry.path, ext)) {
...@@ -72,22 +74,37 @@ fn make(step: *Step) !void {...@@ -72,22 +74,37 @@ fn make(step: *Step) !void {
72 }74 }
73 }75 }
7476
75 const full_path = self.builder.pathJoin(&.{ full_src_dir, entry.path });77 // relative to src build root
76 const dest_path = self.builder.pathJoin(&.{ dest_prefix, entry.path });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
78 switch (entry.kind) {82 switch (entry.kind) {
79 .Directory => try fs.cwd().makePath(dest_path),83 .Directory => try cwd.makePath(dest_path),
80 .File => {84 .File => {
81 for (self.options.blank_extensions) |ext| {85 for (self.options.blank_extensions) |ext| {
82 if (mem.endsWith(u8, entry.path, ext)) {86 if (mem.endsWith(u8, entry.path, ext)) {
83 try self.builder.truncateFile(dest_path);87 try dest_builder.truncateFile(dest_path);
84 continue :next_entry;88 continue :next_entry;
85 }89 }
86 }90 }
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;
89 },104 },
90 else => continue,105 else => continue,
91 }106 }
92 }107 }
108
109 step.result_cached = all_cached;
93}110}
lib/std/Build/InstallFileStep.zig+34-17
...@@ -3,38 +3,55 @@ const Step = std.Build.Step;...@@ -3,38 +3,55 @@ const Step = std.Build.Step;
3const FileSource = std.Build.FileSource;3const FileSource = std.Build.FileSource;
4const InstallDir = std.Build.InstallDir;4const InstallDir = std.Build.InstallDir;
5const InstallFileStep = @This();5const InstallFileStep = @This();
6const assert = std.debug.assert;
67
7pub const base_id = .install_file;8pub const base_id = .install_file;
89
9step: Step,10step: Step,
10builder: *std.Build,
11source: FileSource,11source: FileSource,
12dir: InstallDir,12dir: InstallDir,
13dest_rel_path: []const u8,13dest_rel_path: []const u8,
14/// This is used by the build system when a file being installed comes from one14/// This is used by the build system when a file being installed comes from one
15/// package but is being installed by another.15/// package but is being installed by another.
16override_source_builder: ?*std.Build = null,16dest_builder: *std.Build,
1717
18pub fn init(18pub fn create(
19 builder: *std.Build,19 owner: *std.Build,
20 source: FileSource,20 source: FileSource,
21 dir: InstallDir,21 dir: InstallDir,
22 dest_rel_path: []const u8,22 dest_rel_path: []const u8,
23) InstallFileStep {23) *InstallFileStep {
24 builder.pushInstalledFile(dir, dest_rel_path);24 assert(dest_rel_path.len != 0);
25 return InstallFileStep{25 owner.pushInstalledFile(dir, dest_rel_path);
26 .builder = builder,26 const self = owner.allocator.create(InstallFileStep) catch @panic("OOM");
27 .step = Step.init(.install_file, builder.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), builder.allocator, make),27 self.* = .{
28 .source = source.dupe(builder),28 .step = Step.init(.{
29 .dir = dir.dupe(builder),29 .id = base_id,
30 .dest_rel_path = builder.dupePath(dest_rel_path),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,
31 };38 };
39 source.addStepDependencies(&self.step);
40 return self;
32}41}
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;
35 const self = @fieldParentPtr(InstallFileStep, "step", step);46 const self = @fieldParentPtr(InstallFileStep, "step", step);
36 const src_builder = self.override_source_builder orelse self.builder;47 const dest_builder = self.dest_builder;
37 const full_src_path = self.source.getPath(src_builder);48 const full_src_path = self.source.getPath2(src_builder, step);
38 const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path);49 const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path);
39 try self.builder.updateFile(full_src_path, full_dest_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;
40}57}
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 {...@@ -21,7 +21,6 @@ pub const RawFormat = enum {
21};21};
2222
23step: Step,23step: Step,
24builder: *std.Build,
25file_source: std.Build.FileSource,24file_source: std.Build.FileSource,
26basename: []const u8,25basename: []const u8,
27output_file: std.Build.GeneratedFile,26output_file: std.Build.GeneratedFile,
...@@ -38,19 +37,18 @@ pub const Options = struct {...@@ -38,19 +37,18 @@ pub const Options = struct {
38};37};
3938
40pub fn create(39pub fn create(
41 builder: *std.Build,40 owner: *std.Build,
42 file_source: std.Build.FileSource,41 file_source: std.Build.FileSource,
43 options: Options,42 options: Options,
44) *ObjCopyStep {43) *ObjCopyStep {
45 const self = builder.allocator.create(ObjCopyStep) catch @panic("OOM");44 const self = owner.allocator.create(ObjCopyStep) catch @panic("OOM");
46 self.* = ObjCopyStep{45 self.* = ObjCopyStep{
47 .step = Step.init(46 .step = Step.init(.{
48 base_id,47 .id = base_id,
49 builder.fmt("objcopy {s}", .{file_source.getDisplayName()}),48 .name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}),
50 builder.allocator,49 .owner = owner,
51 make,50 .makeFn = make,
52 ),51 }),
53 .builder = builder,
54 .file_source = file_source,52 .file_source = file_source,
55 .basename = options.basename orelse file_source.getDisplayName(),53 .basename = options.basename orelse file_source.getDisplayName(),
56 .output_file = std.Build.GeneratedFile{ .step = &self.step },54 .output_file = std.Build.GeneratedFile{ .step = &self.step },
...@@ -67,9 +65,9 @@ pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource {...@@ -67,9 +65,9 @@ pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource {
67 return .{ .generated = &self.output_file };65 return .{ .generated = &self.output_file };
68}66}
6967
70fn make(step: *Step) !void {68fn make(step: *Step, prog_node: *std.Progress.Node) !void {
69 const b = step.owner;
71 const self = @fieldParentPtr(ObjCopyStep, "step", step);70 const self = @fieldParentPtr(ObjCopyStep, "step", step);
72 const b = self.builder;
7371
74 var man = b.cache.obtain();72 var man = b.cache.obtain();
75 defer man.deinit();73 defer man.deinit();
...@@ -84,7 +82,7 @@ fn make(step: *Step) !void {...@@ -84,7 +82,7 @@ fn make(step: *Step) !void {
84 man.hash.addOptional(self.pad_to);82 man.hash.addOptional(self.pad_to);
85 man.hash.addOptional(self.format);83 man.hash.addOptional(self.format);
8684
87 if (man.hit() catch |err| failWithCacheError(man, err)) {85 if (try step.cacheHit(&man)) {
88 // Cache hit, skip subprocess execution.86 // Cache hit, skip subprocess execution.
89 const digest = man.final();87 const digest = man.final();
90 self.output_file.path = try b.cache_root.join(b.allocator, &.{88 self.output_file.path = try b.cache_root.join(b.allocator, &.{
...@@ -97,8 +95,7 @@ fn make(step: *Step) !void {...@@ -97,8 +95,7 @@ fn make(step: *Step) !void {
97 const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename });95 const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename });
98 const cache_path = "o" ++ fs.path.sep_str ++ digest;96 const cache_path = "o" ++ fs.path.sep_str ++ digest;
99 b.cache_root.handle.makePath(cache_path) catch |err| {97 b.cache_root.handle.makePath(cache_path) catch |err| {
100 std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });98 return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) });
101 return err;
102 };99 };
103100
104 var argv = std.ArrayList([]const u8).init(b.allocator);101 var argv = std.ArrayList([]const u8).init(b.allocator);
...@@ -116,23 +113,10 @@ fn make(step: *Step) !void {...@@ -116,23 +113,10 @@ fn make(step: *Step) !void {
116 };113 };
117114
118 try argv.appendSlice(&.{ full_src_path, full_dest_path });115 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
121 self.output_file.path = full_dest_path;120 self.output_file.path = full_dest_path;
122 try man.writeManifest();121 try man.writeManifest();
123}122}
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;...@@ -12,21 +12,24 @@ pub const base_id = .options;
1212
13step: Step,13step: Step,
14generated_file: GeneratedFile,14generated_file: GeneratedFile,
15builder: *std.Build,
1615
17contents: std.ArrayList(u8),16contents: std.ArrayList(u8),
18artifact_args: std.ArrayList(OptionArtifactArg),17artifact_args: std.ArrayList(OptionArtifactArg),
19file_source_args: std.ArrayList(OptionFileSourceArg),18file_source_args: std.ArrayList(OptionFileSourceArg),
2019
21pub fn create(builder: *std.Build) *OptionsStep {20pub fn create(owner: *std.Build) *OptionsStep {
22 const self = builder.allocator.create(OptionsStep) catch @panic("OOM");21 const self = owner.allocator.create(OptionsStep) catch @panic("OOM");
23 self.* = .{22 self.* = .{
24 .builder = builder,23 .step = Step.init(.{
25 .step = Step.init(.options, "options", builder.allocator, make),24 .id = base_id,
25 .name = "options",
26 .owner = owner,
27 .makeFn = make,
28 }),
26 .generated_file = undefined,29 .generated_file = undefined,
27 .contents = std.ArrayList(u8).init(builder.allocator),30 .contents = std.ArrayList(u8).init(owner.allocator),
28 .artifact_args = std.ArrayList(OptionArtifactArg).init(builder.allocator),31 .artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator),
29 .file_source_args = std.ArrayList(OptionFileSourceArg).init(builder.allocator),32 .file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.allocator),
30 };33 };
31 self.generated_file = .{ .step = &self.step };34 self.generated_file = .{ .step = &self.step };
3235
...@@ -192,7 +195,7 @@ pub fn addOptionFileSource(...@@ -192,7 +195,7 @@ pub fn addOptionFileSource(
192) void {195) void {
193 self.file_source_args.append(.{196 self.file_source_args.append(.{
194 .name = name,197 .name = name,
195 .source = source.dupe(self.builder),198 .source = source.dupe(self.step.owner),
196 }) catch @panic("OOM");199 }) catch @panic("OOM");
197 source.addStepDependencies(&self.step);200 source.addStepDependencies(&self.step);
198}201}
...@@ -200,12 +203,12 @@ pub fn addOptionFileSource(...@@ -200,12 +203,12 @@ pub fn addOptionFileSource(
200/// The value is the path in the cache dir.203/// The value is the path in the cache dir.
201/// Adds a dependency automatically.204/// Adds a dependency automatically.
202pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *CompileStep) void {205pub 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");
204 self.step.dependOn(&artifact.step);207 self.step.dependOn(&artifact.step);
205}208}
206209
207pub fn createModule(self: *OptionsStep) *std.Build.Module {210pub fn createModule(self: *OptionsStep) *std.Build.Module {
208 return self.builder.createModule(.{211 return self.step.owner.createModule(.{
209 .source_file = self.getSource(),212 .source_file = self.getSource(),
210 .dependencies = &.{},213 .dependencies = &.{},
211 });214 });
...@@ -215,14 +218,18 @@ pub fn getSource(self: *OptionsStep) FileSource {...@@ -215,14 +218,18 @@ pub fn getSource(self: *OptionsStep) FileSource {
215 return .{ .generated = &self.generated_file };218 return .{ .generated = &self.generated_file };
216}219}
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;
219 const self = @fieldParentPtr(OptionsStep, "step", step);226 const self = @fieldParentPtr(OptionsStep, "step", step);
220227
221 for (self.artifact_args.items) |item| {228 for (self.artifact_args.items) |item| {
222 self.addOption(229 self.addOption(
223 []const u8,230 []const u8,
224 item.name,231 item.name,
225 self.builder.pathFromRoot(item.artifact.getOutputSource().getPath(self.builder)),232 b.pathFromRoot(item.artifact.getOutputSource().getPath(b)),
226 );233 );
227 }234 }
228235
...@@ -230,20 +237,18 @@ fn make(step: *Step) !void {...@@ -230,20 +237,18 @@ fn make(step: *Step) !void {
230 self.addOption(237 self.addOption(
231 []const u8,238 []const u8,
232 item.name,239 item.name,
233 item.source.getPath(self.builder),240 item.source.getPath(b),
234 );241 );
235 }242 }
236243
237 var options_dir = try self.builder.cache_root.handle.makeOpenPath("options", .{});244 var options_dir = try b.cache_root.handle.makeOpenPath("options", .{});
238 defer options_dir.close();245 defer options_dir.close();
239246
240 const basename = self.hashContentsToFileName();247 const basename = self.hashContentsToFileName();
241248
242 try options_dir.writeFile(&basename, self.contents.items);249 try options_dir.writeFile(&basename, self.contents.items);
243250
244 self.generated_file.path = try self.builder.cache_root.join(self.builder.allocator, &.{251 self.generated_file.path = try b.cache_root.join(b.allocator, &.{ "options", &basename });
245 "options", &basename,
246 });
247}252}
248253
249fn hashContentsToFileName(self: *OptionsStep) [64]u8 {254fn hashContentsToFileName(self: *OptionsStep) [64]u8 {
lib/std/Build/RemoveDirStep.zig+24-11
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const log = std.log;
3const fs = std.fs;2const fs = std.fs;
4const Step = std.Build.Step;3const Step = std.Build.Step;
5const RemoveDirStep = @This();4const RemoveDirStep = @This();
...@@ -7,23 +6,37 @@ const RemoveDirStep = @This();...@@ -7,23 +6,37 @@ const RemoveDirStep = @This();
7pub const base_id = .remove_dir;6pub const base_id = .remove_dir;
87
9step: Step,8step: Step,
10builder: *std.Build,
11dir_path: []const u8,9dir_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 {
14 return RemoveDirStep{12 return RemoveDirStep{
15 .builder = builder,13 .step = Step.init(.{
16 .step = Step.init(.remove_dir, builder.fmt("RemoveDir {s}", .{dir_path}), builder.allocator, make),14 .id = .remove_dir,
17 .dir_path = builder.dupePath(dir_path),15 .name = owner.fmt("RemoveDir {s}", .{dir_path}),
16 .owner = owner,
17 .makeFn = make,
18 }),
19 .dir_path = owner.dupePath(dir_path),
18 };20 };
19}21}
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;
22 const self = @fieldParentPtr(RemoveDirStep, "step", step);29 const self = @fieldParentPtr(RemoveDirStep, "step", step);
2330
24 const full_path = self.builder.pathFromRoot(self.dir_path);31 b.build_root.handle.deleteTree(self.dir_path) catch |err| {
25 fs.cwd().deleteTree(full_path) catch |err| {32 if (b.build_root.path) |base| {
26 log.err("Unable to remove {s}: {s}", .{ full_path, @errorName(err) });33 return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{
27 return err;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 }
28 };41 };
29}42}
lib/std/Build/RunStep.zig+975-271
...@@ -10,76 +10,136 @@ const ArrayList = std.ArrayList;...@@ -10,76 +10,136 @@ const ArrayList = std.ArrayList;
10const EnvMap = process.EnvMap;10const EnvMap = process.EnvMap;
11const Allocator = mem.Allocator;11const Allocator = mem.Allocator;
12const ExecError = std.Build.ExecError;12const ExecError = std.Build.ExecError;
1313const assert = std.debug.assert;
14const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
1514
16const RunStep = @This();15const RunStep = @This();
1716
18pub const base_id: Step.Id = .run;17pub const base_id: Step.Id = .run;
1918
20step: Step,19step: Step,
21builder: *std.Build,
2220
23/// See also addArg and addArgs to modifying this directly21/// See also addArg and addArgs to modifying this directly
24argv: ArrayList(Arg),22argv: ArrayList(Arg),
2523
26/// Set this to modify the current working directory24/// 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.
27cwd: ?[]const u8,27cwd: ?[]const u8,
2828
29/// Override this field to modify the environment, or use setEnvironmentVariable29/// Override this field to modify the environment, or use setEnvironmentVariable
30env_map: ?*EnvMap,30env_map: ?*EnvMap,
3131
32stdout_action: StdIoAction = .inherit,32/// Configures whether the RunStep is considered to have side-effects, and also
33stderr_action: StdIoAction = .inherit,33/// whether the RunStep will inherit stdio streams, forwarding them to the
3434/// parent process, in which case will require a global lock to prevent other
35stdin_behavior: std.ChildProcess.StdIo = .Inherit,35/// steps from interfering with stdio while the subprocess associated with this
3636/// RunStep is running.
37/// Set this to `null` to ignore the exit code for the purpose of determining a successful execution37/// If the RunStep is determined to not have side-effects, then execution will
38expected_term: ?std.ChildProcess.Term = .{ .Exited = 0 },38/// be skipped if all output files are up-to-date and input files are
3939/// unchanged.
40/// Print the command before running it40stdio: StdIo = .infer_from_args,
41print: bool,41/// This field must be `null` if stdio is `inherit`.
42/// Controls whether execution is skipped if the output file is up-to-date.42stdin: ?[]const u8 = null,
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,
4643
47/// Additional file paths relative to build.zig that, when modified, indicate44/// Additional file paths relative to build.zig that, when modified, indicate
48/// that the RunStep should be re-executed.45/// 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.
49extra_file_dependencies: []const []const u8 = &.{},48extra_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.
52 inherit,86 inherit,
53 ignore,87 /// Causes the RunStep to be considered to *not* have side-effects. The
54 expect_exact: []const u8,88 /// process will be re-executed if any of the input dependencies are
55 expect_matches: []const []const u8,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 };
56};106};
57107
58pub const Arg = union(enum) {108pub const Arg = union(enum) {
59 artifact: *CompileStep,109 artifact: *CompileStep,
60 file_source: std.Build.FileSource,110 file_source: std.Build.FileSource,
111 directory_source: std.Build.FileSource,
61 bytes: []u8,112 bytes: []u8,
62 output: Output,113 output: *Output,
114};
63115
64 pub const Output = struct {116pub const Output = struct {
65 generated_file: *std.Build.GeneratedFile,117 generated_file: std.Build.GeneratedFile,
66 basename: []const u8,118 prefix: []const u8,
67 };119 basename: []const u8,
68};120};
69121
70pub fn create(builder: *std.Build, name: []const u8) *RunStep {122pub fn create(owner: *std.Build, name: []const u8) *RunStep {
71 const self = builder.allocator.create(RunStep) catch @panic("OOM");123 const self = owner.allocator.create(RunStep) catch @panic("OOM");
72 self.* = RunStep{124 self.* = .{
73 .builder = builder,125 .step = Step.init(.{
74 .step = Step.init(base_id, name, builder.allocator, make),126 .id = base_id,
75 .argv = ArrayList(Arg).init(builder.allocator),127 .name = name,
128 .owner = owner,
129 .makeFn = make,
130 }),
131 .argv = ArrayList(Arg).init(owner.allocator),
76 .cwd = null,132 .cwd = null,
77 .env_map = null,133 .env_map = null,
78 .print = builder.verbose,
79 };134 };
80 return self;135 return self;
81}136}
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
83pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {143pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {
84 self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM");144 self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM");
85 self.step.dependOn(&artifact.step);145 self.step.dependOn(&artifact.step);
...@@ -89,25 +149,47 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {...@@ -89,25 +149,47 @@ pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void {
89/// run, and returns a FileSource which can be used as inputs to other APIs149/// run, and returns a FileSource which can be used as inputs to other APIs
90/// throughout the build system.150/// throughout the build system.
91pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource {151pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource {
92 const generated_file = rs.builder.allocator.create(std.Build.GeneratedFile) catch @panic("OOM");152 return addPrefixedOutputFileArg(rs, "", basename);
93 generated_file.* = .{ .step = &rs.step };153}
94 rs.argv.append(.{ .output = .{
95 .generated_file = generated_file,
96 .basename = rs.builder.dupe(basename),
97 } }) catch @panic("OOM");
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 };
100}175}
101176
102pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void {177pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void {
103 self.argv.append(Arg{178 self.argv.append(.{
104 .file_source = file_source.dupe(self.builder),179 .file_source = file_source.dupe(self.step.owner),
105 }) catch @panic("OOM");180 }) catch @panic("OOM");
106 file_source.addStepDependencies(&self.step);181 file_source.addStepDependencies(&self.step);
107}182}
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
109pub fn addArg(self: *RunStep, arg: []const u8) void {191pub 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");
111}193}
112194
113pub fn addArgs(self: *RunStep, args: []const []const u8) void {195pub fn addArgs(self: *RunStep, args: []const []const u8) void {
...@@ -117,102 +199,183 @@ pub fn addArgs(self: *RunStep, args: []const []const u8) void {...@@ -117,102 +199,183 @@ pub fn addArgs(self: *RunStep, args: []const []const u8) void {
117}199}
118200
119pub fn clearEnvironment(self: *RunStep) void {201pub fn clearEnvironment(self: *RunStep) void {
120 const new_env_map = self.builder.allocator.create(EnvMap) catch @panic("OOM");202 const b = self.step.owner;
121 new_env_map.* = EnvMap.init(self.builder.allocator);203 const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM");
204 new_env_map.* = EnvMap.init(b.allocator);
122 self.env_map = new_env_map;205 self.env_map = new_env_map;
123}206}
124207
125pub fn addPathDir(self: *RunStep, search_path: []const u8) void {208pub fn addPathDir(self: *RunStep, search_path: []const u8) void {
126 addPathDirInternal(&self.step, self.builder, search_path);209 const b = self.step.owner;
127}210 const env_map = getEnvMapInternal(self);
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);
132211
133 const key = "PATH";212 const key = "PATH";
134 var prev_path = env_map.get(key);213 var prev_path = env_map.get(key);
135214
136 if (prev_path) |pp| {215 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 });
138 env_map.put(key, new_path) catch @panic("OOM");217 env_map.put(key, new_path) catch @panic("OOM");
139 } else {218 } 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");
141 }220 }
142}221}
143222
144pub fn getEnvMap(self: *RunStep) *EnvMap {223pub fn getEnvMap(self: *RunStep) *EnvMap {
145 return getEnvMapInternal(&self.step, self.builder.allocator);224 return getEnvMapInternal(self);
146}225}
147226
148fn getEnvMapInternal(step: *Step, allocator: Allocator) *EnvMap {227fn getEnvMapInternal(self: *RunStep) *EnvMap {
149 const maybe_env_map = switch (step.id) {228 const arena = self.step.owner.allocator;
150 .run => step.cast(RunStep).?.env_map,229 return self.env_map orelse {
151 .emulatable_run => step.cast(std.Build.EmulatableRunStep).?.env_map,230 const env_map = arena.create(EnvMap) catch @panic("OOM");
152 else => unreachable,231 env_map.* = process.getEnvMap(arena) catch @panic("unhandled error");
153 };232 self.env_map = env_map;
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 }
162 return env_map;233 return env_map;
163 };234 };
164}235}
165236
166pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void {237pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void {
238 const b = self.step.owner;
167 const env_map = self.getEnvMap();239 const env_map = self.getEnvMap();
168 env_map.put(240 env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error");
169 self.builder.dupe(key),241}
170 self.builder.dupe(value),242
171 ) catch @panic("unhandled error");243pub fn removeEnvironmentVariable(self: *RunStep, key: []const u8) void {
244 self.getEnvMap().remove(key);
172}245}
173246
247/// Adds a check for exact stderr match. Does not add any other checks.
174pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void {248pub 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);
176}251}
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.
178pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void {255pub 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 }
180}261}
181262
182fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo {263pub fn expectExitCode(self: *RunStep, code: u8) void {
183 return switch (action) {264 const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } };
184 .ignore => .Ignore,265 self.addCheck(new_check);
185 .inherit => .Inherit,266}
186 .expect_exact, .expect_matches => .Pipe,267
268pub fn hasTermCheck(self: RunStep) bool {
269 for (self.stdio.check.items) |check| switch (check) {
270 .expect_term => return true,
271 else => continue,
187 };272 };
273 return false;
188}274}
189275
190fn needOutputCheck(self: RunStep) bool {276pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void {
191 switch (self.condition) {277 switch (self.stdio) {
192 .always => return false,278 .infer_from_args => {
193 .output_outdated => {},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"),
194 }284 }
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;
197 for (self.argv.items) |arg| switch (arg) {331 for (self.argv.items) |arg| switch (arg) {
198 .output => return true,332 .output => return true,
199 else => continue,333 else => continue,
200 };334 };
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 };
202 return false;363 return false;
203}364}
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;
206 const self = @fieldParentPtr(RunStep, "step", step);369 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);
210 var output_placeholders = ArrayList(struct {373 var output_placeholders = ArrayList(struct {
211 index: usize,374 index: usize,
212 output: Arg.Output,375 output: *Output,
213 }).init(self.builder.allocator);376 }).init(arena);
214377
215 var man = self.builder.cache.obtain();378 var man = b.cache.obtain();
216 defer man.deinit();379 defer man.deinit();
217380
218 for (self.argv.items) |arg| {381 for (self.argv.items) |arg| {
...@@ -222,23 +385,29 @@ fn make(step: *Step) !void {...@@ -222,23 +385,29 @@ fn make(step: *Step) !void {
222 man.hash.addBytes(bytes);385 man.hash.addBytes(bytes);
223 },386 },
224 .file_source => |file| {387 .file_source => |file| {
225 const file_path = file.getPath(self.builder);388 const file_path = file.getPath(b);
226 try argv_list.append(file_path);389 try argv_list.append(file_path);
227 _ = try man.addFile(file_path, null);390 _ = try man.addFile(file_path, null);
228 },391 },
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 },
229 .artifact => |artifact| {397 .artifact => |artifact| {
230 if (artifact.target.isWindows()) {398 if (artifact.target.isWindows()) {
231 // On Windows we don't have rpaths so we have to add .dll search paths to PATH399 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
232 self.addPathForDynLibs(artifact);400 self.addPathForDynLibs(artifact);
233 }401 }
234 const file_path = artifact.installed_path orelse402 const file_path = artifact.installed_path orelse
235 artifact.getOutputSource().getPath(self.builder);403 artifact.getOutputSource().getPath(b);
236404
237 try argv_list.append(file_path);405 try argv_list.append(file_path);
238406
239 _ = try man.addFile(file_path, null);407 _ = try man.addFile(file_path, null);
240 },408 },
241 .output => |output| {409 .output => |output| {
410 man.hash.addBytes(output.prefix);
242 man.hash.addBytes(output.basename);411 man.hash.addBytes(output.basename);
243 // Add a placeholder into the argument list because we need the412 // Add a placeholder into the argument list because we need the
244 // manifest hash to be updated with all arguments before the413 // manifest hash to be updated with all arguments before the
...@@ -252,60 +421,77 @@ fn make(step: *Step) !void {...@@ -252,60 +421,77 @@ fn make(step: *Step) !void {
252 }421 }
253 }422 }
254423
255 if (need_output_check) {424 if (self.captured_stdout) |output| {
256 for (self.extra_file_dependencies) |file_path| {425 man.hash.addBytes(output.basename);
257 _ = try man.addFile(self.builder.pathFromRoot(file_path), null);426 }
258 }
259427
260 if (man.hit() catch |err| failWithCacheError(man, err)) {428 if (self.captured_stderr) |output| {
261 // cache hit, skip running command429 man.hash.addBytes(output.basename);
262 const digest = man.final();430 }
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 }
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();
274 for (output_placeholders.items) |placeholder| {446 for (output_placeholders.items) |placeholder| {
275 const output_path = try self.builder.cache_root.join(447 placeholder.output.generated_file.path = try b.cache_root.join(arena, &.{
276 self.builder.allocator,448 "o", &digest, placeholder.output.basename,
277 &.{ "o", &digest, placeholder.output.basename },449 });
278 );450 }
279 const output_dir = fs.path.dirname(output_path).?;451
280 fs.cwd().makePath(output_dir) catch |err| {452 if (self.captured_stdout) |output| {
281 std.debug.print("unable to make path {s}: {s}\n", .{ output_dir, @errorName(err) });453 output.generated_file.path = try b.cache_root.join(arena, &.{
282 return err;454 "o", &digest, output.basename,
283 };455 });
456 }
284457
285 placeholder.output.generated_file.path = output_path;458 if (self.captured_stderr) |output| {
286 argv_list.items[placeholder.index] = output_path;459 output.generated_file.path = try b.cache_root.join(arena, &.{
460 "o", &digest, output.basename,
461 });
287 }462 }
463
464 step.result_cached = true;
465 return;
288 }466 }
289467
290 try runCommand(468 const digest = man.final();
291 argv_list.items,469
292 self.builder,470 for (output_placeholders.items) |placeholder| {
293 self.expected_term,471 const output_components = .{ "o", &digest, placeholder.output.basename };
294 self.stdout_action,472 const output_sub_path = try fs.path.join(arena, &output_components);
295 self.stderr_action,473 const output_sub_dir_path = fs.path.dirname(output_sub_path).?;
296 self.stdin_behavior,474 b.cache_root.handle.makePath(output_sub_dir_path) catch |err| {
297 self.env_map,475 return step.fail("unable to make path '{}{s}': {s}", .{
298 self.cwd,476 b.cache_root, output_sub_dir_path, @errorName(err),
299 self.print,477 });
300 );478 };
301479 const output_path = try b.cache_root.join(arena, &output_components);
302 if (need_output_check) {480 placeholder.output.generated_file.path = output_path;
303 try man.writeManifest();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;
304 }486 }
487
488 try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node);
489
490 try step.writeManifest(&man);
305}491}
306492
307fn formatTerm(493fn formatTerm(
308 term: ?std.ChildProcess.Term,494 term: ?std.process.Child.Term,
309 comptime fmt: []const u8,495 comptime fmt: []const u8,
310 options: std.fmt.FormatOptions,496 options: std.fmt.FormatOptions,
311 writer: anytype,497 writer: anytype,
...@@ -321,11 +507,11 @@ fn formatTerm(...@@ -321,11 +507,11 @@ fn formatTerm(
321 try writer.writeAll("exited with any code");507 try writer.writeAll("exited with any code");
322 }508 }
323}509}
324fn fmtTerm(term: ?std.ChildProcess.Term) std.fmt.Formatter(formatTerm) {510fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) {
325 return .{ .data = term };511 return .{ .data = term };
326}512}
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 {
329 return if (expected) |e| switch (e) {515 return if (expected) |e| switch (e) {
330 .Exited => |expected_code| switch (actual) {516 .Exited => |expected_code| switch (actual) {
331 .Exited => |actual_code| expected_code == actual_code,517 .Exited => |actual_code| expected_code == actual_code,
...@@ -349,183 +535,701 @@ fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term)...@@ -349,183 +535,701 @@ fn termMatches(expected: ?std.ChildProcess.Term, actual: std.ChildProcess.Term)
349 };535 };
350}536}
351537
352pub fn runCommand(538fn runCommand(
539 self: *RunStep,
353 argv: []const []const u8,540 argv: []const []const u8,
354 builder: *std.Build,541 has_side_effects: bool,
355 expected_term: ?std.ChildProcess.Term,542 digest: ?*const [std.Build.Cache.hex_digest_len]u8,
356 stdout_action: StdIoAction,543 prog_node: *std.Progress.Node,
357 stderr_action: StdIoAction,
358 stdin_behavior: std.ChildProcess.StdIo,
359 env_map: ?*EnvMap,
360 maybe_cwd: ?[]const u8,
361 print: bool,
362) !void {544) !void {
363 const cwd = if (maybe_cwd) |cwd| builder.pathFromRoot(cwd) else builder.build_root.path;545 const step = &self.step;
364546 const b = step.owner;
365 if (!std.process.can_spawn) {547 const arena = b.allocator;
366 const cmd = try std.mem.join(builder.allocator, " ", argv);548
367 std.debug.print("the following command cannot be executed ({s} does not support spawning a child process):\n{s}", .{549 try step.handleChildProcUnsupported(self.cwd, argv);
368 @tagName(builtin.os.tag), cmd,550 try Step.handleVerbose2(step.owner, self.cwd, self.env_map, argv);
369 });551
370 builder.allocator.free(cmd);552 const allow_skip = switch (self.stdio) {
371 return ExecError.ExecNotSupported;553 .check, .zig_test => self.skip_foreign_checks,
372 }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);671 if (exe.target.isWindows()) {
375 child.cwd = cwd;672 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
376 child.env_map = env_map orelse builder.env_map;673 self.addPathForDynLibs(exe);
674 }
377675
378 child.stdin_behavior = stdin_behavior;676 try Step.handleVerbose2(step.owner, self.cwd, self.env_map, interp_argv.items);
379 child.stdout_behavior = stdIoActionToBehavior(stdout_action);
380 child.stderr_behavior = stdIoActionToBehavior(stderr_action);
381677
382 if (print)678 break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| {
383 printCmd(cwd, argv);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| {685 return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) });
386 std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) });
387 return err;
388 };686 };
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;692 // Capture stdout and stderr to GeneratedFile objects.
393 defer if (stdout) |s| builder.allocator.free(s);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) {732 const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
396 .expect_exact, .expect_matches => {733
397 stdout = try child.stdout.?.reader().readAllAlloc(builder.allocator, max_stdout_size);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);
398 },836 },
399 .inherit, .ignore => {},
400 }837 }
838}
401839
402 var stderr: ?[]const u8 = null;840const ChildProcResult = struct {
403 defer if (stderr) |s| builder.allocator.free(s);841 term: std.process.Child.Term,
842 elapsed_ns: u64,
843 peak_rss: usize,
404844
405 switch (stderr_action) {845 stdio: StdIoResult,
406 .expect_exact, .expect_matches => {846};
407 stderr = try child.stderr.?.reader().readAllAlloc(builder.allocator, max_stdout_size);847
408 },848fn spawnChildAndCollect(
409 .inherit, .ignore => {},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;
410 }863 }
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| {867 child.stdin_behavior = switch (self.stdio) {
413 std.debug.print("Unable to spawn {s}: {s}\n", .{ argv[0], @errorName(err) });868 .infer_from_args => if (has_side_effects) .Inherit else .Close,
414 return err;869 .inherit => .Inherit,
870 .check => .Close,
871 .zig_test => .Pipe,
415 };872 };
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)) {892 try child.spawn();
418 if (builder.prominent_compile_errors) {893 var timer = try std.time.Timer.start();
419 std.debug.print("Run step {} (expected {})\n", .{ fmtTerm(term), fmtTerm(expected_term) });894
420 } else {895 const result = if (self.stdio == .zig_test)
421 std.debug.print("The following command {} (expected {}):\n", .{ fmtTerm(term), fmtTerm(expected_term) });896 evalZigTest(self, &child, prog_node)
422 printCmd(cwd, argv);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;
423 }959 }
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);
425 }1032 }
4261033
427 switch (stderr_action) {1034 if (stderr.readableLength() > 0) {
428 .inherit, .ignore => {},1035 const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n");
429 .expect_exact => |expected_bytes| {1036 if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg);
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 },
457 }1037 }
4581038
459 switch (stdout_action) {1039 // Send EOF to stdin.
460 .inherit, .ignore => {},1040 child.stdin.?.close();
461 .expect_exact => |expected_bytes| {1041 child.stdin = null;
462 if (!mem.eql(u8, expected_bytes, stdout.?)) {1042
463 std.debug.print(1043 return .{
464 \\1044 .stdout = &.{},
465 \\========= Expected this stdout: =========1045 .stderr = &.{},
466 \\{s}1046 .stdout_null = true,
467 \\========= But found: ====================1047 .stderr_null = true,
468 \\{s}1048 .test_results = .{
469 \\1049 .test_count = test_count,
470 , .{ expected_bytes, stdout.? });1050 .fail_count = fail_count,
471 printCmd(cwd, argv);1051 .skip_count = skip_count,
472 return error.TestFailed;1052 .leak_count = leak_count,
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 }
488 },1053 },
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);
489 }1086 }
490}1087}
4911088
492fn failWithCacheError(man: std.Build.Cache.Manifest, err: anyerror) noreturn {1089fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
493 const i = man.failed_file_index orelse failWithSimpleError(err);1090 const header: std.zig.Client.Message.Header = .{
494 const pp = man.files.items[i].prefixed_path orelse failWithSimpleError(err);1091 .tag = tag,
495 const prefix = man.cache.prefixes()[pp.prefix].path orelse "";1092 .bytes_len = 0,
496 std.debug.print("{s}: {s}/{s}\n", .{ @errorName(err), prefix, pp.sub_path });1093 };
497 std.process.exit(1);1094 try file.writeAll(std.mem.asBytes(&header));
498}1095}
4991096
500fn failWithSimpleError(err: anyerror) noreturn {1097fn sendRunTestMessage(file: std.fs.File, index: u32) !void {
501 std.debug.print("{s}\n", .{@errorName(err)});1098 const header: std.zig.Client.Message.Header = .{
502 std.process.exit(1);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);
503}1104}
5041105
505fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void {1106fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult {
506 if (cwd) |yes_cwd| std.debug.print("cd {s} && ", .{yes_cwd});1107 const arena = self.step.owner.allocator;
507 for (argv) |arg| {1108
508 std.debug.print("{s} ", .{arg});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;
509 }1115 }
510 std.debug.print("\n", .{});
511}
5121116
513fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void {1117 // These are not optionals, as a workaround for
514 addPathForDynLibsInternal(&self.step, self.builder, artifact);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 };
515}1170}
5161171
517/// This should only be used for internal usage, this is called automatically1172fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void {
518/// for the user.1173 const b = self.step.owner;
519pub fn addPathForDynLibsInternal(step: *Step, builder: *std.Build, artifact: *CompileStep) void {
520 for (artifact.link_objects.items) |link_object| {1174 for (artifact.link_objects.items) |link_object| {
521 switch (link_object) {1175 switch (link_object) {
522 .other_step => |other| {1176 .other_step => |other| {
523 if (other.target.isWindows() and other.isDynamicLibrary()) {1177 if (other.target.isWindows() and other.isDynamicLibrary()) {
524 addPathDirInternal(step, builder, fs.path.dirname(other.getOutputSource().getPath(builder)).?);1178 addPathDir(self, fs.path.dirname(other.getOutputSource().getPath(b)).?);
525 addPathForDynLibsInternal(step, builder, other);1179 addPathForDynLibs(self, other);
526 }1180 }
527 },1181 },
528 else => {},1182 else => {},
529 }1183 }
530 }1184 }
531}1185}
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 @@...@@ -1,9 +1,77 @@
1id: Id,1id: Id,
2name: []const u8,2name: []const u8,
3makeFn: *const fn (self: *Step) anyerror!void,3owner: *Build,
4makeFn: MakeFn,
5
4dependencies: std.ArrayList(*Step),6dependencies: std.ArrayList(*Step),
5loop_flag: bool,7/// This field is empty during execution of the user's build script, and
6done_flag: bool,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
8pub const Id = enum {76pub const Id = enum {
9 top_level,77 top_level,
...@@ -17,7 +85,6 @@ pub const Id = enum {...@@ -17,7 +85,6 @@ pub const Id = enum {
17 translate_c,85 translate_c,
18 write_file,86 write_file,
19 run,87 run,
20 emulatable_run,
21 check_file,88 check_file,
22 check_object,89 check_object,
23 config_header,90 config_header,
...@@ -38,7 +105,6 @@ pub const Id = enum {...@@ -38,7 +105,6 @@ pub const Id = enum {
38 .translate_c => Build.TranslateCStep,105 .translate_c => Build.TranslateCStep,
39 .write_file => Build.WriteFileStep,106 .write_file => Build.WriteFileStep,
40 .run => Build.RunStep,107 .run => Build.RunStep,
41 .emulatable_run => Build.EmulatableRunStep,
42 .check_file => Build.CheckFileStep,108 .check_file => Build.CheckFileStep,
43 .check_object => Build.CheckObjectStep,109 .check_object => Build.CheckObjectStep,
44 .config_header => Build.ConfigHeaderStep,110 .config_header => Build.ConfigHeaderStep,
...@@ -49,39 +115,99 @@ pub const Id = enum {...@@ -49,39 +115,99 @@ pub const Id = enum {
49 }115 }
50};116};
51117
52pub fn init(118pub const Options = struct {
53 id: Id,119 id: Id,
54 name: []const u8,120 name: []const u8,
55 allocator: Allocator,121 owner: *Build,
56 makeFn: *const fn (self: *Step) anyerror!void,122 makeFn: MakeFn = makeNoOp,
57) Step {123 first_ret_addr: ?usize = null,
58 return Step{124 max_rss: usize = 0,
59 .id = id,125};
60 .name = allocator.dupe(u8, name) catch @panic("OOM"),126
61 .makeFn = makeFn,127pub fn init(options: Options) Step {
62 .dependencies = std.ArrayList(*Step).init(allocator),128 const arena = options.owner.allocator;
63 .loop_flag = false,129
64 .done_flag = false,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,
65 };135 };
66}136 std.debug.captureStackTrace(first_ret_addr, &stack_trace);
67137
68pub fn initNoOp(id: Id, name: []const u8, allocator: Allocator) Step {138 return .{
69 return init(id, name, allocator, makeNoOp);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 };
70}155}
71156
72pub fn make(self: *Step) !void {157/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
73 if (self.done_flag) return;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);176 if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) {
76 self.done_flag = true;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 }
77}183}
78184
79pub fn dependOn(self: *Step, other: *Step) void {185pub fn dependOn(self: *Step, other: *Step) void {
80 self.dependencies.append(other) catch @panic("OOM");186 self.dependencies.append(other) catch @panic("OOM");
81}187}
82188
83fn makeNoOp(self: *Step) anyerror!void {189pub fn getStackTrace(s: *Step) std.builtin.StackTrace {
84 _ = self;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;
85}211}
86212
87pub fn cast(step: *Step, comptime T: type) ?*T {213pub fn cast(step: *Step, comptime T: type) ?*T {
...@@ -91,7 +217,323 @@ pub fn cast(step: *Step, comptime T: type) ?*T {...@@ -91,7 +217,323 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
91 return null;217 return null;
92}218}
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
94const Step = @This();242const Step = @This();
95const std = @import("../std.zig");243const std = @import("../std.zig");
96const Build = std.Build;244const Build = std.Build;
97const Allocator = std.mem.Allocator;245const 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();...@@ -11,7 +11,6 @@ const TranslateCStep = @This();
11pub const base_id = .translate_c;11pub const base_id = .translate_c;
1212
13step: Step,13step: Step,
14builder: *std.Build,
15source: std.Build.FileSource,14source: std.Build.FileSource,
16include_dirs: std.ArrayList([]const u8),15include_dirs: std.ArrayList([]const u8),
17c_macros: std.ArrayList([]const u8),16c_macros: std.ArrayList([]const u8),
...@@ -26,15 +25,19 @@ pub const Options = struct {...@@ -26,15 +25,19 @@ pub const Options = struct {
26 optimize: std.builtin.OptimizeMode,25 optimize: std.builtin.OptimizeMode,
27};26};
2827
29pub fn create(builder: *std.Build, options: Options) *TranslateCStep {28pub fn create(owner: *std.Build, options: Options) *TranslateCStep {
30 const self = builder.allocator.create(TranslateCStep) catch @panic("OOM");29 const self = owner.allocator.create(TranslateCStep) catch @panic("OOM");
31 const source = options.source_file.dupe(builder);30 const source = options.source_file.dupe(owner);
32 self.* = TranslateCStep{31 self.* = TranslateCStep{
33 .step = Step.init(.translate_c, "translate-c", builder.allocator, make),32 .step = Step.init(.{
34 .builder = builder,33 .id = .translate_c,
34 .name = "translate-c",
35 .owner = owner,
36 .makeFn = make,
37 }),
35 .source = source,38 .source = source,
36 .include_dirs = std.ArrayList([]const u8).init(builder.allocator),39 .include_dirs = std.ArrayList([]const u8).init(owner.allocator),
37 .c_macros = std.ArrayList([]const u8).init(builder.allocator),40 .c_macros = std.ArrayList([]const u8).init(owner.allocator),
38 .out_basename = undefined,41 .out_basename = undefined,
39 .target = options.target,42 .target = options.target,
40 .optimize = options.optimize,43 .optimize = options.optimize,
...@@ -54,7 +57,7 @@ pub const AddExecutableOptions = struct {...@@ -54,7 +57,7 @@ pub const AddExecutableOptions = struct {
5457
55/// Creates a step to build an executable from the translated source.58/// Creates a step to build an executable from the translated source.
56pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep {59pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep {
57 return self.builder.addExecutable(.{60 return self.step.owner.addExecutable(.{
58 .root_source_file = .{ .generated = &self.output_file },61 .root_source_file = .{ .generated = &self.output_file },
59 .name = options.name orelse "translated_c",62 .name = options.name orelse "translated_c",
60 .version = options.version,63 .version = options.version,
...@@ -65,43 +68,49 @@ pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *Comp...@@ -65,43 +68,49 @@ pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *Comp
65}68}
6669
67pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void {70pub 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");
69}72}
7073
71pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep {74pub 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 );
73}80}
7481
75/// If the value is omitted, it is set to 1.82/// If the value is omitted, it is set to 1.
76/// `name` and `value` need not live longer than the function call.83/// `name` and `value` need not live longer than the function call.
77pub fn defineCMacro(self: *TranslateCStep, name: []const u8, value: ?[]const u8) void {84pub 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);
79 self.c_macros.append(macro) catch @panic("OOM");86 self.c_macros.append(macro) catch @panic("OOM");
80}87}
8188
82/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1.89/// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1.
83pub fn defineCMacroRaw(self: *TranslateCStep, name_and_value: []const u8) void {90pub 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");
85}92}
8693
87fn make(step: *Step) !void {94fn make(step: *Step, prog_node: *std.Progress.Node) !void {
95 const b = step.owner;
88 const self = @fieldParentPtr(TranslateCStep, "step", step);96 const self = @fieldParentPtr(TranslateCStep, "step", step);
8997
90 var argv_list = std.ArrayList([]const u8).init(self.builder.allocator);98 var argv_list = std.ArrayList([]const u8).init(b.allocator);
91 try argv_list.append(self.builder.zig_exe);99 try argv_list.append(b.zig_exe);
92 try argv_list.append("translate-c");100 try argv_list.append("translate-c");
93 try argv_list.append("-lc");101 try argv_list.append("-lc");
94102
95 try argv_list.append("--enable-cache");103 try argv_list.append("--enable-cache");
104 try argv_list.append("--listen=-");
96105
97 if (!self.target.isNative()) {106 if (!self.target.isNative()) {
98 try argv_list.append("-target");107 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));
100 }109 }
101110
102 switch (self.optimize) {111 switch (self.optimize) {
103 .Debug => {}, // Skip since it's the default.112 .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)})),
105 }114 }
106115
107 for (self.include_dirs.items) |include_dir| {116 for (self.include_dirs.items) |include_dir| {
...@@ -114,16 +123,15 @@ fn make(step: *Step) !void {...@@ -114,16 +123,15 @@ fn make(step: *Step) !void {
114 try argv_list.append(c_macro);123 try argv_list.append(c_macro);
115 }124 }
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);128 const output_path = try step.evalZigProcess(argv_list.items, prog_node);
120 const output_path = mem.trimRight(u8, output_path_nl, "\r\n");
121129
122 self.out_basename = fs.path.basename(output_path);130 self.out_basename = fs.path.basename(output_path);
123 const output_dir = fs.path.dirname(output_path).?;131 const output_dir = fs.path.dirname(output_path).?;
124132
125 self.output_file.path = try fs.path.join(133 self.output_file.path = try fs.path.join(
126 self.builder.allocator,134 b.allocator,
127 &[_][]const u8{ output_dir, self.out_basename },135 &[_][]const u8{ output_dir, self.out_basename },
128 );136 );
129}137}
lib/std/Build/WriteFileStep.zig+145-67
...@@ -10,11 +10,11 @@...@@ -10,11 +10,11 @@
10//! control.10//! control.
1111
12step: Step,12step: Step,
13builder: *std.Build,
14/// The elements here are pointers because we need stable pointers for the13/// The elements here are pointers because we need stable pointers for the
15/// GeneratedFile field.14/// GeneratedFile field.
16files: std.ArrayListUnmanaged(*File),15files: std.ArrayListUnmanaged(*File),
17output_source_files: std.ArrayListUnmanaged(OutputSourceFile),16output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
17generated_directory: std.Build.GeneratedFile,
1818
19pub const base_id = .write_file;19pub const base_id = .write_file;
2020
...@@ -34,24 +34,34 @@ pub const Contents = union(enum) {...@@ -34,24 +34,34 @@ pub const Contents = union(enum) {
34 copy: std.Build.FileSource,34 copy: std.Build.FileSource,
35};35};
3636
37pub fn init(builder: *std.Build) WriteFileStep {37pub fn create(owner: *std.Build) *WriteFileStep {
38 return .{38 const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM");
39 .builder = builder,39 wf.* = .{
40 .step = Step.init(.write_file, "writefile", builder.allocator, make),40 .step = Step.init(.{
41 .id = .write_file,
42 .name = "WriteFile",
43 .owner = owner,
44 .makeFn = make,
45 }),
41 .files = .{},46 .files = .{},
42 .output_source_files = .{},47 .output_source_files = .{},
48 .generated_directory = .{ .step = &wf.step },
43 };49 };
50 return wf;
44}51}
4552
46pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {53pub 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;
48 const file = gpa.create(File) catch @panic("OOM");56 const file = gpa.create(File) catch @panic("OOM");
49 file.* = .{57 file.* = .{
50 .generated_file = .{ .step = &wf.step },58 .generated_file = .{ .step = &wf.step },
51 .sub_path = wf.builder.dupePath(sub_path),59 .sub_path = b.dupePath(sub_path),
52 .contents = .{ .bytes = wf.builder.dupe(bytes) },60 .contents = .{ .bytes = b.dupe(bytes) },
53 };61 };
54 wf.files.append(gpa, file) catch @panic("OOM");62 wf.files.append(gpa, file) catch @panic("OOM");
63
64 wf.maybeUpdateName();
55}65}
5666
57/// Place the file into the generated directory within the local cache,67/// 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 {...@@ -62,14 +72,18 @@ pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void {
62/// required sub-path exists.72/// required sub-path exists.
63/// This is the option expected to be used most commonly with `addCopyFile`.73/// This is the option expected to be used most commonly with `addCopyFile`.
64pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void {74pub 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;
66 const file = gpa.create(File) catch @panic("OOM");77 const file = gpa.create(File) catch @panic("OOM");
67 file.* = .{78 file.* = .{
68 .generated_file = .{ .step = &wf.step },79 .generated_file = .{ .step = &wf.step },
69 .sub_path = wf.builder.dupePath(sub_path),80 .sub_path = b.dupePath(sub_path),
70 .contents = .{ .copy = source },81 .contents = .{ .copy = source },
71 };82 };
72 wf.files.append(gpa, file) catch @panic("OOM");83 wf.files.append(gpa, file) catch @panic("OOM");
84
85 wf.maybeUpdateName();
86 source.addStepDependencies(&wf.step);
73}87}
7488
75/// A path relative to the package root.89/// A path relative to the package root.
...@@ -79,10 +93,26 @@ pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: [...@@ -79,10 +93,26 @@ pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: [
79/// those changes to version control.93/// those changes to version control.
80/// A file added this way is not available with `getFileSource`.94/// A file added this way is not available with `getFileSource`.
81pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void {95pub 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, .{
83 .contents = .{ .copy = source },98 .contents = .{ .copy = source },
84 .sub_path = sub_path,99 .sub_path = sub_path,
85 }) catch @panic("OOM");100 }) 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");
86}116}
87117
88/// Gets a file source for the given sub_path. If the file does not exist, returns `null`.118/// 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...@@ -95,21 +125,63 @@ pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSo
95 return null;125 return null;
96}126}
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;
99 const wf = @fieldParentPtr(WriteFileStep, "step", step);146 const wf = @fieldParentPtr(WriteFileStep, "step", step);
100147
101 // Writing to source files is kind of an extra capability of this148 // Writing to source files is kind of an extra capability of this
102 // WriteFileStep - arguably it should be a different step. But anyway here149 // WriteFileStep - arguably it should be a different step. But anyway here
103 // it is, it happens unconditionally and does not interact with the other150 // it is, it happens unconditionally and does not interact with the other
104 // files here.151 // files here.
152 var any_miss = false;
105 for (wf.output_source_files.items) |output_source_file| {153 for (wf.output_source_files.items) |output_source_file| {
106 const basename = fs.path.basename(output_source_file.sub_path);
107 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {154 if (fs.path.dirname(output_source_file.sub_path)) |dirname| {
108 var dir = try wf.builder.build_root.handle.makeOpenPath(dirname, .{});155 b.build_root.handle.makePath(dirname) catch |err| {
109 defer dir.close();156 return step.fail("unable to make path '{}{s}': {s}", .{
110 try writeFile(wf, dir, output_source_file.contents, basename);157 b.build_root, dirname, @errorName(err),
111 } else {158 });
112 try writeFile(wf, wf.builder.build_root.handle, output_source_file.contents, basename);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 },
113 }185 }
114 }186 }
115187
...@@ -120,7 +192,7 @@ fn make(step: *Step) !void {...@@ -120,7 +192,7 @@ fn make(step: *Step) !void {
120 // If, for example, a hard-coded path was used as the location to put WriteFileStep192 // If, for example, a hard-coded path was used as the location to put WriteFileStep
121 // files, then two WriteFileSteps executing in parallel might clobber each other.193 // 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();
124 defer man.deinit();196 defer man.deinit();
125197
126 // Random bytes to make WriteFileStep unique. Refresh this with198 // Random bytes to make WriteFileStep unique. Refresh this with
...@@ -135,76 +207,82 @@ fn make(step: *Step) !void {...@@ -135,76 +207,82 @@ fn make(step: *Step) !void {
135 man.hash.addBytes(bytes);207 man.hash.addBytes(bytes);
136 },208 },
137 .copy => |file_source| {209 .copy => |file_source| {
138 _ = try man.addFile(file_source.getPath(wf.builder), null);210 _ = try man.addFile(file_source.getPath(b), null);
139 },211 },
140 }212 }
141 }213 }
142214
143 if (man.hit() catch |err| failWithCacheError(man, err)) {215 if (try step.cacheHit(&man)) {
144 // Cache hit, skip writing file data.
145 const digest = man.final();216 const digest = man.final();
146 for (wf.files.items) |file| {217 for (wf.files.items) |file| {
147 file.generated_file.path = try wf.builder.cache_root.join(218 file.generated_file.path = try b.cache_root.join(b.allocator, &.{
148 wf.builder.allocator,219 "o", &digest, file.sub_path,
149 &.{ "o", &digest, file.sub_path },220 });
150 );
151 }221 }
222 wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
152 return;223 return;
153 }224 }
154225
155 const digest = man.final();226 const digest = man.final();
156 const cache_path = "o" ++ fs.path.sep_str ++ digest;227 const cache_path = "o" ++ fs.path.sep_str ++ digest;
157228
158 var cache_dir = wf.builder.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| {229 wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest });
159 std.debug.print("unable to make path {s}: {s}\n", .{ cache_path, @errorName(err) });230
160 return err;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 });
161 };235 };
162 defer cache_dir.close();236 defer cache_dir.close();
163237
164 for (wf.files.items) |file| {238 for (wf.files.items) |file| {
165 const basename = fs.path.basename(file.sub_path);
166 if (fs.path.dirname(file.sub_path)) |dirname| {239 if (fs.path.dirname(file.sub_path)) |dirname| {
167 var dir = try wf.builder.cache_root.handle.makeOpenPath(dirname, .{});240 cache_dir.makePath(dirname) catch |err| {
168 defer dir.close();241 return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{
169 try writeFile(wf, dir, file.contents, basename);242 b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err),
170 } else {243 });
171 try writeFile(wf, cache_dir, file.contents, basename);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 },
172 }278 }
173279
174 file.generated_file.path = try wf.builder.cache_root.join(280 file.generated_file.path = try b.cache_root.join(b.allocator, &.{
175 wf.builder.allocator,281 cache_path, file.sub_path,
176 &.{ cache_path, file.sub_path },282 });
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 },
192 }283 }
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 {285 try step.writeManifest(&man);
206 std.debug.print("{s}\n", .{@errorName(err)});
207 std.process.exit(1);
208}286}
209287
210const std = @import("../std.zig");288const std = @import("../std.zig");
lib/std/Progress.zig+72-26
...@@ -126,6 +126,21 @@ pub const Node = struct {...@@ -126,6 +126,21 @@ pub const Node = struct {
126 }126 }
127 }127 }
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
129 /// Thread-safe. 0 means unknown.144 /// Thread-safe. 0 means unknown.
130 pub fn setEstimatedTotalItems(self: *Node, count: usize) void {145 pub fn setEstimatedTotalItems(self: *Node, count: usize) void {
131 @atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic);146 @atomicStore(usize, &self.unprotected_estimated_total_items, count, .Monotonic);
...@@ -174,16 +189,20 @@ pub fn maybeRefresh(self: *Progress) void {...@@ -174,16 +189,20 @@ pub fn maybeRefresh(self: *Progress) void {
174 if (self.timer) |*timer| {189 if (self.timer) |*timer| {
175 if (!self.update_mutex.tryLock()) return;190 if (!self.update_mutex.tryLock()) return;
176 defer self.update_mutex.unlock();191 defer self.update_mutex.unlock();
177 const now = timer.read();192 maybeRefreshWithHeldLock(self, timer);
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();
184 }193 }
185}194}
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
187/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe.206/// Updates the terminal and resets `self.next_refresh_timestamp`. Thread-safe.
188pub fn refresh(self: *Progress) void {207pub fn refresh(self: *Progress) void {
189 if (!self.update_mutex.tryLock()) return;208 if (!self.update_mutex.tryLock()) return;
...@@ -192,32 +211,28 @@ pub fn refresh(self: *Progress) void {...@@ -192,32 +211,28 @@ pub fn refresh(self: *Progress) void {
192 return self.refreshWithHeldLock();211 return self.refreshWithHeldLock();
193}212}
194213
195fn refreshWithHeldLock(self: *Progress) void {214fn clearWithHeldLock(p: *Progress, end_ptr: *usize) void {
196 const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;215 const file = p.terminal orelse return;
197 if (is_dumb and self.dont_print_on_dumb) return;216 var end = end_ptr.*;
198217 if (p.columns_written > 0) {
199 const file = self.terminal orelse return;
200
201 var end: usize = 0;
202 if (self.columns_written > 0) {
203 // restore the cursor position by moving the cursor218 // restore the cursor position by moving the cursor
204 // `columns_written` cells to the left, then clear the rest of the219 // `columns_written` cells to the left, then clear the rest of the
205 // line220 // line
206 if (self.supports_ansi_escape_codes) {221 if (p.supports_ansi_escape_codes) {
207 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len;222 end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[{d}D", .{p.columns_written}) catch unreachable).len;
208 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;223 end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
209 } else if (builtin.os.tag == .windows) winapi: {224 } else if (builtin.os.tag == .windows) winapi: {
210 std.debug.assert(self.is_windows_terminal);225 std.debug.assert(p.is_windows_terminal);
211226
212 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;227 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
213 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {228 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
214 // stop trying to write to this file229 // stop trying to write to this file
215 self.terminal = null;230 p.terminal = null;
216 break :winapi;231 break :winapi;
217 }232 }
218233
219 var cursor_pos = windows.COORD{234 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),
221 .Y = info.dwCursorPosition.Y,236 .Y = info.dwCursorPosition.Y,
222 };237 };
223238
...@@ -235,7 +250,7 @@ fn refreshWithHeldLock(self: *Progress) void {...@@ -235,7 +250,7 @@ fn refreshWithHeldLock(self: *Progress) void {
235 &written,250 &written,
236 ) != windows.TRUE) {251 ) != windows.TRUE) {
237 // stop trying to write to this file252 // stop trying to write to this file
238 self.terminal = null;253 p.terminal = null;
239 break :winapi;254 break :winapi;
240 }255 }
241 if (windows.kernel32.FillConsoleOutputCharacterW(256 if (windows.kernel32.FillConsoleOutputCharacterW(
...@@ -246,22 +261,33 @@ fn refreshWithHeldLock(self: *Progress) void {...@@ -246,22 +261,33 @@ fn refreshWithHeldLock(self: *Progress) void {
246 &written,261 &written,
247 ) != windows.TRUE) {262 ) != windows.TRUE) {
248 // stop trying to write to this file263 // stop trying to write to this file
249 self.terminal = null;264 p.terminal = null;
250 break :winapi;265 break :winapi;
251 }266 }
252 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) {267 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) {
253 // stop trying to write to this file268 // stop trying to write to this file
254 self.terminal = null;269 p.terminal = null;
255 break :winapi;270 break :winapi;
256 }271 }
257 } else {272 } else {
258 // we are in a "dumb" terminal like in acme or writing to a file273 // 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';
260 end += 1;275 end += 1;
261 }276 }
262277
263 self.columns_written = 0;278 p.columns_written = 0;
264 }279 }
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
266 if (!self.done) {292 if (!self.done) {
267 var need_ellipse = false;293 var need_ellipse = false;
...@@ -318,6 +344,26 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {...@@ -318,6 +344,26 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
318 self.columns_written = 0;344 self.columns_written = 0;
319}345}
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
321fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {367fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
322 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {368 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
323 const amt = written.len;369 const amt = written.len;
lib/std/Thread.zig+2
...@@ -16,6 +16,8 @@ pub const Mutex = @import("Thread/Mutex.zig");...@@ -16,6 +16,8 @@ pub const Mutex = @import("Thread/Mutex.zig");
16pub const Semaphore = @import("Thread/Semaphore.zig");16pub const Semaphore = @import("Thread/Semaphore.zig");
17pub const Condition = @import("Thread/Condition.zig");17pub const Condition = @import("Thread/Condition.zig");
18pub const RwLock = @import("Thread/RwLock.zig");18pub const RwLock = @import("Thread/RwLock.zig");
19pub const Pool = @import("Thread/Pool.zig");
20pub const WaitGroup = @import("Thread/WaitGroup.zig");
1921
20pub const use_pthreads = target.os.tag != .windows and target.os.tag != .wasi and builtin.link_libc;22pub const use_pthreads = target.os.tag != .windows and target.os.tag != .wasi and builtin.link_libc;
21const is_gnu = target.abi.isGnu();23const 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...@@ -153,7 +153,8 @@ pub extern "c" fn linkat(oldfd: c.fd_t, oldpath: [*:0]const u8, newfd: c.fd_t, n
153pub extern "c" fn unlink(path: [*:0]const u8) c_int;153pub extern "c" fn unlink(path: [*:0]const u8) c_int;
154pub extern "c" fn unlinkat(dirfd: c.fd_t, path: [*:0]const u8, flags: c_uint) c_int;154pub extern "c" fn unlinkat(dirfd: c.fd_t, path: [*:0]const u8, flags: c_uint) c_int;
155pub extern "c" fn getcwd(buf: [*]u8, size: usize) ?[*]u8;155pub 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;
157pub extern "c" fn fork() c_int;158pub extern "c" fn fork() c_int;
158pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) c_int;159pub extern "c" fn access(path: [*:0]const u8, mode: c_uint) c_int;
159pub extern "c" fn faccessat(dirfd: c.fd_t, path: [*:0]const u8, mode: c_uint, flags: c_uint) c_int;160pub 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;...@@ -17,10 +17,12 @@ const Os = std.builtin.Os;
17const TailQueue = std.TailQueue;17const TailQueue = std.TailQueue;
18const maxInt = std.math.maxInt;18const maxInt = std.math.maxInt;
19const assert = std.debug.assert;19const assert = std.debug.assert;
20const is_darwin = builtin.target.isDarwin();
2021
21pub const ChildProcess = struct {22pub const ChildProcess = struct {
22 pub const Id = switch (builtin.os.tag) {23 pub const Id = switch (builtin.os.tag) {
23 .windows => windows.HANDLE,24 .windows => windows.HANDLE,
25 .wasi => void,
24 else => os.pid_t,26 else => os.pid_t,
25 };27 };
2628
...@@ -70,6 +72,43 @@ pub const ChildProcess = struct {...@@ -70,6 +72,43 @@ pub const ChildProcess = struct {
70 /// Darwin-only. Start child process in suspended state as if SIGSTOP was sent.72 /// Darwin-only. Start child process in suspended state as if SIGSTOP was sent.
71 start_suspended: bool = false,73 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
73 pub const Arg0Expand = os.Arg0Expand;112 pub const Arg0Expand = os.Arg0Expand;
74113
75 pub const SpawnError = error{114 pub const SpawnError = error{
...@@ -332,7 +371,16 @@ pub const ChildProcess = struct {...@@ -332,7 +371,16 @@ pub const ChildProcess = struct {
332 }371 }
333372
334 fn waitUnwrapped(self: *ChildProcess) !void {373 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 };
336 const status = res.status;384 const status = res.status;
337 self.cleanupStreams();385 self.cleanupStreams();
338 self.handleWaitResult(status);386 self.handleWaitResult(status);
lib/std/debug.zig+33
...@@ -635,6 +635,7 @@ pub const TTY = struct {...@@ -635,6 +635,7 @@ pub const TTY = struct {
635 pub const Color = enum {635 pub const Color = enum {
636 Red,636 Red,
637 Green,637 Green,
638 Yellow,
638 Cyan,639 Cyan,
639 White,640 White,
640 Dim,641 Dim,
...@@ -659,6 +660,7 @@ pub const TTY = struct {...@@ -659,6 +660,7 @@ pub const TTY = struct {
659 const color_string = switch (color) {660 const color_string = switch (color) {
660 .Red => "\x1b[31;1m",661 .Red => "\x1b[31;1m",
661 .Green => "\x1b[32;1m",662 .Green => "\x1b[32;1m",
663 .Yellow => "\x1b[33;1m",
662 .Cyan => "\x1b[36;1m",664 .Cyan => "\x1b[36;1m",
663 .White => "\x1b[37;1m",665 .White => "\x1b[37;1m",
664 .Bold => "\x1b[1m",666 .Bold => "\x1b[1m",
...@@ -671,6 +673,7 @@ pub const TTY = struct {...@@ -671,6 +673,7 @@ pub const TTY = struct {
671 const attributes = switch (color) {673 const attributes = switch (color) {
672 .Red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,674 .Red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
673 .Green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,675 .Green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
676 .Yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
674 .Cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,677 .Cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
675 .White, .Bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,678 .White, .Bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
676 .Dim => windows.FOREGROUND_INTENSITY,679 .Dim => windows.FOREGROUND_INTENSITY,
...@@ -682,6 +685,36 @@ pub const TTY = struct {...@@ -682,6 +685,36 @@ pub const TTY = struct {
682 },685 },
683 };686 };
684 }687 }
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 }
685 };718 };
686};719};
687720
lib/std/fifo.zig+27
...@@ -164,6 +164,17 @@ pub fn LinearFifo(...@@ -164,6 +164,17 @@ pub fn LinearFifo(
164 return self.readableSliceMut(offset);164 return self.readableSliceMut(offset);
165 }165 }
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
167 /// Discard first `count` items in the fifo178 /// Discard first `count` items in the fifo
168 pub fn discard(self: *Self, count: usize) void {179 pub fn discard(self: *Self, count: usize) void {
169 assert(count <= self.count);180 assert(count <= self.count);
...@@ -383,6 +394,22 @@ pub fn LinearFifo(...@@ -383,6 +394,22 @@ pub fn LinearFifo(
383 self.discard(try dest_writer.write(self.readableSlice(0)));394 self.discard(try dest_writer.write(self.readableSlice(0)));
384 }395 }
385 }396 }
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 }
386 };413 };
387}414}
388415
lib/std/fs/file.zig+33-5
...@@ -1048,12 +1048,27 @@ pub const File = struct {...@@ -1048,12 +1048,27 @@ pub const File = struct {
1048 /// Returns the number of bytes read. If the number read is smaller than the total bytes1048 /// Returns the number of bytes read. If the number read is smaller than the total bytes
1049 /// from all the buffers, it means the file reached the end. Reaching the end of a file1049 /// from all the buffers, it means the file reached the end. Reaching the end of a file
1050 /// is not an error condition.1050 /// is not an error condition.
1051 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in1051 ///
1052 /// order to handle partial reads from the underlying OS layer.1052 /// The `iovecs` parameter is mutable because:
1053 /// See https://github.com/ziglang/zig/issues/76991053 /// * 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
1054 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize {1061 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize {
1055 if (iovecs.len == 0) return 0;1062 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
1057 var i: usize = 0;1072 var i: usize = 0;
1058 var off: usize = 0;1073 var off: usize = 0;
1059 while (true) {1074 while (true) {
...@@ -1181,13 +1196,26 @@ pub const File = struct {...@@ -1181,13 +1196,26 @@ pub const File = struct {
1181 }1196 }
1182 }1197 }
11831198
1184 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in1199 /// The `iovecs` parameter is mutable because:
1185 /// order to handle partial writes from the underlying OS layer.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.
1186 /// See https://github.com/ziglang/zig/issues/76991206 /// See https://github.com/ziglang/zig/issues/7699
1187 /// See equivalent function: `std.net.Stream.writevAll`.1207 /// See equivalent function: `std.net.Stream.writevAll`.
1188 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {1208 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
1189 if (iovecs.len == 0) return;1209 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
1191 var i: usize = 0;1219 var i: usize = 0;
1192 while (true) {1220 while (true) {
1193 var amt = try self.writev(iovecs[i..]);1221 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" {...@@ -1124,17 +1124,31 @@ test "open file with exclusive lock twice, make sure second lock waits" {
1124test "open file with exclusive nonblocking lock twice (absolute paths)" {1124test "open file with exclusive nonblocking lock twice (absolute paths)" {
1125 if (builtin.os.tag == .wasi) return error.SkipZigTest;1125 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 var random_b64: [fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined;
1130 defer allocator.free(cwd);1131 _ = fs.base64_encoder.encode(&random_b64, &random_bytes);
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);
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 });
1138 file1.close();1152 file1.close();
1139 try testing.expectError(error.WouldBlock, file2);1153 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"...@@ -19,6 +19,7 @@ pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig"
19pub const WasmAllocator = @import("heap/WasmAllocator.zig");19pub const WasmAllocator = @import("heap/WasmAllocator.zig");
20pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");20pub const WasmPageAllocator = @import("heap/WasmPageAllocator.zig");
21pub const PageAllocator = @import("heap/PageAllocator.zig");21pub const PageAllocator = @import("heap/PageAllocator.zig");
22pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig");
2223
23const memory_pool = @import("heap/memory_pool.zig");24const memory_pool = @import("heap/memory_pool.zig");
24pub const MemoryPool = memory_pool.MemoryPool;25pub 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" {...@@ -196,13 +196,8 @@ test "Allocator.resize" {
196/// dest.len must be >= source.len.196/// dest.len must be >= source.len.
197/// If the slices overlap, dest.ptr must be <= src.ptr.197/// If the slices overlap, dest.ptr must be <= src.ptr.
198pub fn copy(comptime T: type, dest: []T, source: []const T) void {198pub fn copy(comptime T: type, dest: []T, source: []const T) void {
199 // TODO instead of manually doing this check for the whole array199 for (dest[0..source.len], source) |*d, s|
200 // and turning off runtime safety, the compiler should detect loops like200 d.* = s;
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;
206}201}
207202
208/// Copy all of source into dest at position 0.203/// Copy all of source into dest at position 0.
...@@ -611,8 +606,8 @@ test "lessThan" {...@@ -611,8 +606,8 @@ test "lessThan" {
611pub fn eql(comptime T: type, a: []const T, b: []const T) bool {606pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
612 if (a.len != b.len) return false;607 if (a.len != b.len) return false;
613 if (a.ptr == b.ptr) return true;608 if (a.ptr == b.ptr) return true;
614 for (a, 0..) |item, index| {609 for (a, b) |a_elem, b_elem| {
615 if (b[index] != item) return false;610 if (a_elem != b_elem) return false;
616 }611 }
617 return true;612 return true;
618}613}
lib/std/os.zig+27-1
...@@ -766,6 +766,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -766,6 +766,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
766/// This operation is non-atomic on the following systems:766/// This operation is non-atomic on the following systems:
767/// * Windows767/// * Windows
768/// On these systems, the read races with concurrent writes to the same file descriptor.768/// 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.
769pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {772pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
770 if (builtin.os.tag == .windows) {773 if (builtin.os.tag == .windows) {
771 // TODO improve this to use ReadFileScatter774 // TODO improve this to use ReadFileScatter
...@@ -1167,6 +1170,9 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {...@@ -1167,6 +1170,9 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
1167/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.1170/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
1168///1171///
1169/// If `iov.len` is larger than `IOV_MAX`, a partial write will occur.1172/// 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.
1170pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {1176pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
1171 if (builtin.os.tag == .windows) {1177 if (builtin.os.tag == .windows) {
1172 // TODO improve this to use WriteFileScatter1178 // TODO improve this to use WriteFileScatter
...@@ -4000,8 +4006,28 @@ pub const WaitPidResult = struct {...@@ -4000,8 +4006,28 @@ pub const WaitPidResult = struct {
4000pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {4006pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
4001 const Status = if (builtin.link_libc) c_int else u32;4007 const Status = if (builtin.link_libc) c_int else u32;
4002 var status: Status = undefined;4008 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;
4003 while (true) {4029 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);
4005 switch (errno(rc)) {4031 switch (errno(rc)) {
4006 .SUCCESS => return .{4032 .SUCCESS => return .{
4007 .pid = @intCast(pid_t, rc),4033 .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 {...@@ -944,6 +944,16 @@ pub fn waitpid(pid: pid_t, status: *u32, flags: u32) usize {
944 return syscall4(.wait4, @bitCast(usize, @as(isize, pid)), @ptrToInt(status), flags, 0);944 return syscall4(.wait4, @bitCast(usize, @as(isize, pid)), @ptrToInt(status), flags, 0);
945}945}
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
947pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32) usize {957pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32) usize {
948 return syscall5(.waitid, @enumToInt(id_type), @bitCast(usize, @as(isize, id)), @ptrToInt(infop), flags, 0);958 return syscall5(.waitid, @enumToInt(id_type), @bitCast(usize, @as(isize, id)), @ptrToInt(infop), flags, 0);
949}959}
...@@ -1716,26 +1726,26 @@ pub fn pidfd_send_signal(pidfd: fd_t, sig: i32, info: ?*siginfo_t, flags: u32) u...@@ -1716,26 +1726,26 @@ pub fn pidfd_send_signal(pidfd: fd_t, sig: i32, info: ?*siginfo_t, flags: u32) u
1716 );1726 );
1717}1727}
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 {
1720 return syscall6(1730 return syscall6(
1721 .process_vm_readv,1731 .process_vm_readv,
1722 @bitCast(usize, @as(isize, pid)),1732 @bitCast(usize, @as(isize, pid)),
1723 @ptrToInt(local),1733 @ptrToInt(local.ptr),
1724 local_count,1734 local.len,
1725 @ptrToInt(remote),1735 @ptrToInt(remote.ptr),
1726 remote_count,1736 remote.len,
1727 flags,1737 flags,
1728 );1738 );
1729}1739}
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 {
1732 return syscall6(1742 return syscall6(
1733 .process_vm_writev,1743 .process_vm_writev,
1734 @bitCast(usize, @as(isize, pid)),1744 @bitCast(usize, @as(isize, pid)),
1735 @ptrToInt(local),1745 @ptrToInt(local.ptr),
1736 local_count,1746 local.len,
1737 @ptrToInt(remote),1747 @ptrToInt(remote.ptr),
1738 remote_count,1748 remote.len,
1739 flags,1749 flags,
1740 );1750 );
1741}1751}
...@@ -1820,6 +1830,23 @@ pub fn seccomp(operation: u32, flags: u32, args: ?*const anyopaque) usize {...@@ -1820,6 +1830,23 @@ pub fn seccomp(operation: u32, flags: u32, args: ?*const anyopaque) usize {
1820 return syscall3(.seccomp, operation, flags, @ptrToInt(args));1830 return syscall3(.seccomp, operation, flags, @ptrToInt(args));
1821}1831}
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
1823pub const E = switch (native_arch) {1850pub const E = switch (native_arch) {
1824 .mips, .mipsel => @import("linux/errno/mips.zig").E,1851 .mips, .mipsel => @import("linux/errno/mips.zig").E,
1825 .sparc, .sparcel, .sparc64 => @import("linux/errno/sparc.zig").E,1852 .sparc, .sparcel, .sparc64 => @import("linux/errno/sparc.zig").E,
...@@ -5721,3 +5748,40 @@ pub const AUDIT = struct {...@@ -5721,3 +5748,40 @@ pub const AUDIT = struct {
5721 }5748 }
5722 };5749 };
5723};5750};
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" {...@@ -1728,10 +1728,12 @@ test "writev/fsync/readv" {
1728 };1728 };
1729 defer ring.deinit();1729 defer ring.deinit();
17301730
1731 var tmp = std.testing.tmpDir(.{});
1732 defer tmp.cleanup();
1733
1731 const path = "test_io_uring_writev_fsync_readv";1734 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 });
1733 defer file.close();1736 defer file.close();
1734 defer std.fs.cwd().deleteFile(path) catch {};
1735 const fd = file.handle;1737 const fd = file.handle;
17361738
1737 const buffer_write = [_]u8{42} ** 128;1739 const buffer_write = [_]u8{42} ** 128;
...@@ -1796,10 +1798,11 @@ test "write/read" {...@@ -1796,10 +1798,11 @@ test "write/read" {
1796 };1798 };
1797 defer ring.deinit();1799 defer ring.deinit();
17981800
1801 var tmp = std.testing.tmpDir(.{});
1802 defer tmp.cleanup();
1799 const path = "test_io_uring_write_read";1803 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 });
1801 defer file.close();1805 defer file.close();
1802 defer std.fs.cwd().deleteFile(path) catch {};
1803 const fd = file.handle;1806 const fd = file.handle;
18041807
1805 const buffer_write = [_]u8{97} ** 20;1808 const buffer_write = [_]u8{97} ** 20;
...@@ -1842,10 +1845,12 @@ test "write_fixed/read_fixed" {...@@ -1842,10 +1845,12 @@ test "write_fixed/read_fixed" {
1842 };1845 };
1843 defer ring.deinit();1846 defer ring.deinit();
18441847
1848 var tmp = std.testing.tmpDir(.{});
1849 defer tmp.cleanup();
1850
1845 const path = "test_io_uring_write_read_fixed";1851 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 });
1847 defer file.close();1853 defer file.close();
1848 defer std.fs.cwd().deleteFile(path) catch {};
1849 const fd = file.handle;1854 const fd = file.handle;
18501855
1851 var raw_buffers: [2][11]u8 = undefined;1856 var raw_buffers: [2][11]u8 = undefined;
...@@ -1899,8 +1904,10 @@ test "openat" {...@@ -1899,8 +1904,10 @@ test "openat" {
1899 };1904 };
1900 defer ring.deinit();1905 defer ring.deinit();
19011906
1907 var tmp = std.testing.tmpDir(.{});
1908 defer tmp.cleanup();
1909
1902 const path = "test_io_uring_openat";1910 const path = "test_io_uring_openat";
1903 defer std.fs.cwd().deleteFile(path) catch {};
19041911
1905 // Workaround for LLVM bug: https://github.com/ziglang/zig/issues/120141912 // Workaround for LLVM bug: https://github.com/ziglang/zig/issues/12014
1906 const path_addr = if (builtin.zig_backend == .stage2_llvm) p: {1913 const path_addr = if (builtin.zig_backend == .stage2_llvm) p: {
...@@ -1910,12 +1917,12 @@ test "openat" {...@@ -1910,12 +1917,12 @@ test "openat" {
19101917
1911 const flags: u32 = os.O.CLOEXEC | os.O.RDWR | os.O.CREAT;1918 const flags: u32 = os.O.CLOEXEC | os.O.RDWR | os.O.CREAT;
1912 const mode: os.mode_t = 0o666;1919 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);
1914 try testing.expectEqual(linux.io_uring_sqe{1921 try testing.expectEqual(linux.io_uring_sqe{
1915 .opcode = .OPENAT,1922 .opcode = .OPENAT,
1916 .flags = 0,1923 .flags = 0,
1917 .ioprio = 0,1924 .ioprio = 0,
1918 .fd = linux.AT.FDCWD,1925 .fd = tmp.dir.fd,
1919 .off = 0,1926 .off = 0,
1920 .addr = path_addr,1927 .addr = path_addr,
1921 .len = mode,1928 .len = mode,
...@@ -1931,12 +1938,6 @@ test "openat" {...@@ -1931,12 +1938,6 @@ test "openat" {
1931 const cqe_openat = try ring.copy_cqe();1938 const cqe_openat = try ring.copy_cqe();
1932 try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data);1939 try testing.expectEqual(@as(u64, 0x33333333), cqe_openat.user_data);
1933 if (cqe_openat.err() == .INVAL) return error.SkipZigTest;1940 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 }
1940 if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res});1941 if (cqe_openat.res <= 0) std.debug.print("\ncqe_openat.res={}\n", .{cqe_openat.res});
1941 try testing.expect(cqe_openat.res > 0);1942 try testing.expect(cqe_openat.res > 0);
1942 try testing.expectEqual(@as(u32, 0), cqe_openat.flags);1943 try testing.expectEqual(@as(u32, 0), cqe_openat.flags);
...@@ -1954,10 +1955,12 @@ test "close" {...@@ -1954,10 +1955,12 @@ test "close" {
1954 };1955 };
1955 defer ring.deinit();1956 defer ring.deinit();
19561957
1958 var tmp = std.testing.tmpDir(.{});
1959 defer tmp.cleanup();
1960
1957 const path = "test_io_uring_close";1961 const path = "test_io_uring_close";
1958 const file = try std.fs.cwd().createFile(path, .{});1962 const file = try tmp.dir.createFile(path, .{});
1959 errdefer file.close();1963 errdefer file.close();
1960 defer std.fs.cwd().deleteFile(path) catch {};
19611964
1962 const sqe_close = try ring.close(0x44444444, file.handle);1965 const sqe_close = try ring.close(0x44444444, file.handle);
1963 try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode);1966 try testing.expectEqual(linux.IORING_OP.CLOSE, sqe_close.opcode);
...@@ -1976,6 +1979,11 @@ test "close" {...@@ -1976,6 +1979,11 @@ test "close" {
1976test "accept/connect/send/recv" {1979test "accept/connect/send/recv" {
1977 if (builtin.os.tag != .linux) return error.SkipZigTest;1980 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
1979 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {1987 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
1980 error.SystemOutdated => return error.SkipZigTest,1988 error.SystemOutdated => return error.SkipZigTest,
1981 error.PermissionDenied => return error.SkipZigTest,1989 error.PermissionDenied => return error.SkipZigTest,
...@@ -2017,6 +2025,11 @@ test "accept/connect/send/recv" {...@@ -2017,6 +2025,11 @@ test "accept/connect/send/recv" {
2017test "sendmsg/recvmsg" {2025test "sendmsg/recvmsg" {
2018 if (builtin.os.tag != .linux) return error.SkipZigTest;2026 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
2020 var ring = IO_Uring.init(2, 0) catch |err| switch (err) {2033 var ring = IO_Uring.init(2, 0) catch |err| switch (err) {
2021 error.SystemOutdated => return error.SkipZigTest,2034 error.SystemOutdated => return error.SkipZigTest,
2022 error.PermissionDenied => return error.SkipZigTest,2035 error.PermissionDenied => return error.SkipZigTest,
...@@ -2024,6 +2037,7 @@ test "sendmsg/recvmsg" {...@@ -2024,6 +2037,7 @@ test "sendmsg/recvmsg" {
2024 };2037 };
2025 defer ring.deinit();2038 defer ring.deinit();
20262039
2040 if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907
2027 const address_server = try net.Address.parseIp4("127.0.0.1", 3131);2041 const address_server = try net.Address.parseIp4("127.0.0.1", 3131);
20282042
2029 const server = try os.socket(address_server.any.family, os.SOCK.DGRAM, 0);2043 const server = try os.socket(address_server.any.family, os.SOCK.DGRAM, 0);
...@@ -2223,6 +2237,11 @@ test "timeout_remove" {...@@ -2223,6 +2237,11 @@ test "timeout_remove" {
2223test "accept/connect/recv/link_timeout" {2237test "accept/connect/recv/link_timeout" {
2224 if (builtin.os.tag != .linux) return error.SkipZigTest;2238 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
2226 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {2245 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
2227 error.SystemOutdated => return error.SkipZigTest,2246 error.SystemOutdated => return error.SkipZigTest,
2228 error.PermissionDenied => return error.SkipZigTest,2247 error.PermissionDenied => return error.SkipZigTest,
...@@ -2279,10 +2298,12 @@ test "fallocate" {...@@ -2279,10 +2298,12 @@ test "fallocate" {
2279 };2298 };
2280 defer ring.deinit();2299 defer ring.deinit();
22812300
2301 var tmp = std.testing.tmpDir(.{});
2302 defer tmp.cleanup();
2303
2282 const path = "test_io_uring_fallocate";2304 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 });
2284 defer file.close();2306 defer file.close();
2285 defer std.fs.cwd().deleteFile(path) catch {};
22862307
2287 try testing.expectEqual(@as(u64, 0), (try file.stat()).size);2308 try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
22882309
...@@ -2323,10 +2344,11 @@ test "statx" {...@@ -2323,10 +2344,11 @@ test "statx" {
2323 };2344 };
2324 defer ring.deinit();2345 defer ring.deinit();
23252346
2347 var tmp = std.testing.tmpDir(.{});
2348 defer tmp.cleanup();
2326 const path = "test_io_uring_statx";2349 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 });
2328 defer file.close();2351 defer file.close();
2329 defer std.fs.cwd().deleteFile(path) catch {};
23302352
2331 try testing.expectEqual(@as(u64, 0), (try file.stat()).size);2353 try testing.expectEqual(@as(u64, 0), (try file.stat()).size);
23322354
...@@ -2335,14 +2357,14 @@ test "statx" {...@@ -2335,14 +2357,14 @@ test "statx" {
2335 var buf: linux.Statx = undefined;2357 var buf: linux.Statx = undefined;
2336 const sqe = try ring.statx(2358 const sqe = try ring.statx(
2337 0xaaaaaaaa,2359 0xaaaaaaaa,
2338 linux.AT.FDCWD,2360 tmp.dir.fd,
2339 path,2361 path,
2340 0,2362 0,
2341 linux.STATX_SIZE,2363 linux.STATX_SIZE,
2342 &buf,2364 &buf,
2343 );2365 );
2344 try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode);2366 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);
2346 try testing.expectEqual(@as(u32, 1), try ring.submit());2368 try testing.expectEqual(@as(u32, 1), try ring.submit());
23472369
2348 const cqe = try ring.copy_cqe();2370 const cqe = try ring.copy_cqe();
...@@ -2355,8 +2377,6 @@ test "statx" {...@@ -2355,8 +2377,6 @@ test "statx" {
2355 // The filesystem containing the file referred to by fd does not support this operation;2377 // The filesystem containing the file referred to by fd does not support this operation;
2356 // or the mode is not supported by the filesystem containing the file referred to by fd:2378 // or the mode is not supported by the filesystem containing the file referred to by fd:
2357 .OPNOTSUPP => return error.SkipZigTest,2379 .OPNOTSUPP => return error.SkipZigTest,
2358 // The kernel is too old to support FDCWD for dir_fd
2359 .BADF => return error.SkipZigTest,
2360 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),2380 else => |errno| std.debug.panic("unhandled errno: {}", .{errno}),
2361 }2381 }
2362 try testing.expectEqual(linux.io_uring_cqe{2382 try testing.expectEqual(linux.io_uring_cqe{
...@@ -2372,6 +2392,11 @@ test "statx" {...@@ -2372,6 +2392,11 @@ test "statx" {
2372test "accept/connect/recv/cancel" {2392test "accept/connect/recv/cancel" {
2373 if (builtin.os.tag != .linux) return error.SkipZigTest;2393 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
2375 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {2400 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
2376 error.SystemOutdated => return error.SkipZigTest,2401 error.SystemOutdated => return error.SkipZigTest,
2377 error.PermissionDenied => return error.SkipZigTest,2402 error.PermissionDenied => return error.SkipZigTest,
...@@ -2509,6 +2534,11 @@ test "register_files_update" {...@@ -2509,6 +2534,11 @@ test "register_files_update" {
2509test "shutdown" {2534test "shutdown" {
2510 if (builtin.os.tag != .linux) return error.SkipZigTest;2535 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
2512 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {2542 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
2513 error.SystemOutdated => return error.SkipZigTest,2543 error.SystemOutdated => return error.SkipZigTest,
2514 error.PermissionDenied => return error.SkipZigTest,2544 error.PermissionDenied => return error.SkipZigTest,
...@@ -2516,6 +2546,7 @@ test "shutdown" {...@@ -2516,6 +2546,7 @@ test "shutdown" {
2516 };2546 };
2517 defer ring.deinit();2547 defer ring.deinit();
25182548
2549 if (true) @compileError("don't hard code port numbers in unit tests"); // https://github.com/ziglang/zig/issues/14907
2519 const address = try net.Address.parseIp4("127.0.0.1", 3131);2550 const address = try net.Address.parseIp4("127.0.0.1", 3131);
25202551
2521 // Socket bound, expect shutdown to work2552 // Socket bound, expect shutdown to work
...@@ -2579,28 +2610,28 @@ test "renameat" {...@@ -2579,28 +2610,28 @@ test "renameat" {
2579 const old_path = "test_io_uring_renameat_old";2610 const old_path = "test_io_uring_renameat_old";
2580 const new_path = "test_io_uring_renameat_new";2611 const new_path = "test_io_uring_renameat_new";
25812612
2613 var tmp = std.testing.tmpDir(.{});
2614 defer tmp.cleanup();
2615
2582 // Write old file with data2616 // Write old file with data
25832617
2584 const old_file = try std.fs.cwd().createFile(old_path, .{ .truncate = true, .mode = 0o666 });2618 const old_file = try tmp.dir.createFile(old_path, .{ .truncate = true, .mode = 0o666 });
2585 defer {2619 defer old_file.close();
2586 old_file.close();
2587 std.fs.cwd().deleteFile(new_path) catch {};
2588 }
2589 try old_file.writeAll("hello");2620 try old_file.writeAll("hello");
25902621
2591 // Submit renameat2622 // Submit renameat
25922623
2593 var sqe = try ring.renameat(2624 var sqe = try ring.renameat(
2594 0x12121212,2625 0x12121212,
2595 linux.AT.FDCWD,2626 tmp.dir.fd,
2596 old_path,2627 old_path,
2597 linux.AT.FDCWD,2628 tmp.dir.fd,
2598 new_path,2629 new_path,
2599 0,2630 0,
2600 );2631 );
2601 try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode);2632 try testing.expectEqual(linux.IORING_OP.RENAMEAT, sqe.opcode);
2602 try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);2633 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
2603 try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len));2634 try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len));
2604 try testing.expectEqual(@as(u32, 1), try ring.submit());2635 try testing.expectEqual(@as(u32, 1), try ring.submit());
26052636
2606 const cqe = try ring.copy_cqe();2637 const cqe = try ring.copy_cqe();
...@@ -2618,7 +2649,7 @@ test "renameat" {...@@ -2618,7 +2649,7 @@ test "renameat" {
26182649
2619 // Validate that the old file doesn't exist anymore2650 // Validate that the old file doesn't exist anymore
2620 {2651 {
2621 _ = std.fs.cwd().openFile(old_path, .{}) catch |err| switch (err) {2652 _ = tmp.dir.openFile(old_path, .{}) catch |err| switch (err) {
2622 error.FileNotFound => {},2653 error.FileNotFound => {},
2623 else => std.debug.panic("unexpected error: {}", .{err}),2654 else => std.debug.panic("unexpected error: {}", .{err}),
2624 };2655 };
...@@ -2626,7 +2657,7 @@ test "renameat" {...@@ -2626,7 +2657,7 @@ test "renameat" {
26262657
2627 // Validate that the new file exists with the proper content2658 // Validate that the new file exists with the proper content
2628 {2659 {
2629 const new_file = try std.fs.cwd().openFile(new_path, .{});2660 const new_file = try tmp.dir.openFile(new_path, .{});
2630 defer new_file.close();2661 defer new_file.close();
26312662
2632 var new_file_data: [16]u8 = undefined;2663 var new_file_data: [16]u8 = undefined;
...@@ -2647,22 +2678,24 @@ test "unlinkat" {...@@ -2647,22 +2678,24 @@ test "unlinkat" {
26472678
2648 const path = "test_io_uring_unlinkat";2679 const path = "test_io_uring_unlinkat";
26492680
2681 var tmp = std.testing.tmpDir(.{});
2682 defer tmp.cleanup();
2683
2650 // Write old file with data2684 // 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 });
2653 defer file.close();2687 defer file.close();
2654 defer std.fs.cwd().deleteFile(path) catch {};
26552688
2656 // Submit unlinkat2689 // Submit unlinkat
26572690
2658 var sqe = try ring.unlinkat(2691 var sqe = try ring.unlinkat(
2659 0x12121212,2692 0x12121212,
2660 linux.AT.FDCWD,2693 tmp.dir.fd,
2661 path,2694 path,
2662 0,2695 0,
2663 );2696 );
2664 try testing.expectEqual(linux.IORING_OP.UNLINKAT, sqe.opcode);2697 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);
2666 try testing.expectEqual(@as(u32, 1), try ring.submit());2699 try testing.expectEqual(@as(u32, 1), try ring.submit());
26672700
2668 const cqe = try ring.copy_cqe();2701 const cqe = try ring.copy_cqe();
...@@ -2679,7 +2712,7 @@ test "unlinkat" {...@@ -2679,7 +2712,7 @@ test "unlinkat" {
2679 }, cqe);2712 }, cqe);
26802713
2681 // Validate that the file doesn't exist anymore2714 // 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) {
2683 error.FileNotFound => {},2716 error.FileNotFound => {},
2684 else => std.debug.panic("unexpected error: {}", .{err}),2717 else => std.debug.panic("unexpected error: {}", .{err}),
2685 };2718 };
...@@ -2695,20 +2728,21 @@ test "mkdirat" {...@@ -2695,20 +2728,21 @@ test "mkdirat" {
2695 };2728 };
2696 defer ring.deinit();2729 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
2702 // Submit mkdirat2736 // Submit mkdirat
27032737
2704 var sqe = try ring.mkdirat(2738 var sqe = try ring.mkdirat(
2705 0x12121212,2739 0x12121212,
2706 linux.AT.FDCWD,2740 tmp.dir.fd,
2707 path,2741 path,
2708 0o0755,2742 0o0755,
2709 );2743 );
2710 try testing.expectEqual(linux.IORING_OP.MKDIRAT, sqe.opcode);2744 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);
2712 try testing.expectEqual(@as(u32, 1), try ring.submit());2746 try testing.expectEqual(@as(u32, 1), try ring.submit());
27132747
2714 const cqe = try ring.copy_cqe();2748 const cqe = try ring.copy_cqe();
...@@ -2725,7 +2759,7 @@ test "mkdirat" {...@@ -2725,7 +2759,7 @@ test "mkdirat" {
2725 }, cqe);2759 }, cqe);
27262760
2727 // Validate that the directory exist2761 // Validate that the directory exist
2728 _ = try std.fs.cwd().openDir(path, .{});2762 _ = try tmp.dir.openDir(path, .{});
2729}2763}
27302764
2731test "symlinkat" {2765test "symlinkat" {
...@@ -2738,26 +2772,25 @@ test "symlinkat" {...@@ -2738,26 +2772,25 @@ test "symlinkat" {
2738 };2772 };
2739 defer ring.deinit();2773 defer ring.deinit();
27402774
2775 var tmp = std.testing.tmpDir(.{});
2776 defer tmp.cleanup();
2777
2741 const path = "test_io_uring_symlinkat";2778 const path = "test_io_uring_symlinkat";
2742 const link_path = "test_io_uring_symlinkat_link";2779 const link_path = "test_io_uring_symlinkat_link";
27432780
2744 const file = try std.fs.cwd().createFile(path, .{ .truncate = true, .mode = 0o666 });2781 const file = try tmp.dir.createFile(path, .{ .truncate = true, .mode = 0o666 });
2745 defer {2782 defer file.close();
2746 file.close();
2747 std.fs.cwd().deleteFile(path) catch {};
2748 std.fs.cwd().deleteFile(link_path) catch {};
2749 }
27502783
2751 // Submit symlinkat2784 // Submit symlinkat
27522785
2753 var sqe = try ring.symlinkat(2786 var sqe = try ring.symlinkat(
2754 0x12121212,2787 0x12121212,
2755 path,2788 path,
2756 linux.AT.FDCWD,2789 tmp.dir.fd,
2757 link_path,2790 link_path,
2758 );2791 );
2759 try testing.expectEqual(linux.IORING_OP.SYMLINKAT, sqe.opcode);2792 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);
2761 try testing.expectEqual(@as(u32, 1), try ring.submit());2794 try testing.expectEqual(@as(u32, 1), try ring.submit());
27622795
2763 const cqe = try ring.copy_cqe();2796 const cqe = try ring.copy_cqe();
...@@ -2774,7 +2807,7 @@ test "symlinkat" {...@@ -2774,7 +2807,7 @@ test "symlinkat" {
2774 }, cqe);2807 }, cqe);
27752808
2776 // Validate that the symlink exist2809 // Validate that the symlink exist
2777 _ = try std.fs.cwd().openFile(link_path, .{});2810 _ = try tmp.dir.openFile(link_path, .{});
2778}2811}
27792812
2780test "linkat" {2813test "linkat" {
...@@ -2787,32 +2820,31 @@ test "linkat" {...@@ -2787,32 +2820,31 @@ test "linkat" {
2787 };2820 };
2788 defer ring.deinit();2821 defer ring.deinit();
27892822
2823 var tmp = std.testing.tmpDir(.{});
2824 defer tmp.cleanup();
2825
2790 const first_path = "test_io_uring_linkat_first";2826 const first_path = "test_io_uring_linkat_first";
2791 const second_path = "test_io_uring_linkat_second";2827 const second_path = "test_io_uring_linkat_second";
27922828
2793 // Write file with data2829 // Write file with data
27942830
2795 const first_file = try std.fs.cwd().createFile(first_path, .{ .truncate = true, .mode = 0o666 });2831 const first_file = try tmp.dir.createFile(first_path, .{ .truncate = true, .mode = 0o666 });
2796 defer {2832 defer first_file.close();
2797 first_file.close();
2798 std.fs.cwd().deleteFile(first_path) catch {};
2799 std.fs.cwd().deleteFile(second_path) catch {};
2800 }
2801 try first_file.writeAll("hello");2833 try first_file.writeAll("hello");
28022834
2803 // Submit linkat2835 // Submit linkat
28042836
2805 var sqe = try ring.linkat(2837 var sqe = try ring.linkat(
2806 0x12121212,2838 0x12121212,
2807 linux.AT.FDCWD,2839 tmp.dir.fd,
2808 first_path,2840 first_path,
2809 linux.AT.FDCWD,2841 tmp.dir.fd,
2810 second_path,2842 second_path,
2811 0,2843 0,
2812 );2844 );
2813 try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode);2845 try testing.expectEqual(linux.IORING_OP.LINKAT, sqe.opcode);
2814 try testing.expectEqual(@as(i32, linux.AT.FDCWD), sqe.fd);2846 try testing.expectEqual(@as(i32, tmp.dir.fd), sqe.fd);
2815 try testing.expectEqual(@as(i32, linux.AT.FDCWD), @bitCast(i32, sqe.len));2847 try testing.expectEqual(@as(i32, tmp.dir.fd), @bitCast(i32, sqe.len));
2816 try testing.expectEqual(@as(u32, 1), try ring.submit());2848 try testing.expectEqual(@as(u32, 1), try ring.submit());
28172849
2818 const cqe = try ring.copy_cqe();2850 const cqe = try ring.copy_cqe();
...@@ -2829,7 +2861,7 @@ test "linkat" {...@@ -2829,7 +2861,7 @@ test "linkat" {
2829 }, cqe);2861 }, cqe);
28302862
2831 // Validate the second file2863 // 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, .{});
2833 defer second_file.close();2865 defer second_file.close();
28342866
2835 var second_file_data: [16]u8 = undefined;2867 var second_file_data: [16]u8 = undefined;
...@@ -3060,6 +3092,11 @@ test "remove_buffers" {...@@ -3060,6 +3092,11 @@ test "remove_buffers" {
3060test "provide_buffers: accept/connect/send/recv" {3092test "provide_buffers: accept/connect/send/recv" {
3061 if (builtin.os.tag != .linux) return error.SkipZigTest;3093 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
3063 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {3100 var ring = IO_Uring.init(16, 0) catch |err| switch (err) {
3064 error.SystemOutdated => return error.SkipZigTest,3101 error.SystemOutdated => return error.SkipZigTest,
3065 error.PermissionDenied => return error.SkipZigTest,3102 error.PermissionDenied => return error.SkipZigTest,
...@@ -3236,6 +3273,7 @@ const SocketTestHarness = struct {...@@ -3236,6 +3273,7 @@ const SocketTestHarness = struct {
3236fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {3273fn createSocketTestHarness(ring: *IO_Uring) !SocketTestHarness {
3237 // Create a TCP server socket3274 // 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
3239 const address = try net.Address.parseIp4("127.0.0.1", 3131);3277 const address = try net.Address.parseIp4("127.0.0.1", 3131);
3240 const kernel_backlog = 1;3278 const kernel_backlog = 1;
3241 const listener_socket = try os.socket(address.any.family, os.SOCK.STREAM | os.SOCK.CLOEXEC, 0);3279 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;...@@ -8,10 +8,12 @@ const expectEqual = std.testing.expectEqual;
8const fs = std.fs;8const fs = std.fs;
99
10test "fallocate" {10test "fallocate" {
11 var tmp = std.testing.tmpDir(.{});
12 defer tmp.cleanup();
13
11 const path = "test_fallocate";14 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 });
13 defer file.close();16 defer file.close();
14 defer fs.cwd().deleteFile(path) catch {};
1517
16 try expect((try file.stat()).size == 0);18 try expect((try file.stat()).size == 0);
1719
...@@ -67,12 +69,12 @@ test "timer" {...@@ -67,12 +69,12 @@ test "timer" {
67}69}
6870
69test "statx" {71test "statx" {
72 var tmp = std.testing.tmpDir(.{});
73 defer tmp.cleanup();
74
70 const tmp_file_name = "just_a_temporary_file.txt";75 const tmp_file_name = "just_a_temporary_file.txt";
71 var file = try fs.cwd().createFile(tmp_file_name, .{});76 var file = try tmp.dir.createFile(tmp_file_name, .{});
72 defer {77 defer file.close();
73 file.close();
74 fs.cwd().deleteFile(tmp_file_name) catch {};
75 }
7678
77 var statx_buf: linux.Statx = undefined;79 var statx_buf: linux.Statx = undefined;
78 switch (linux.getErrno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) {80 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" {...@@ -105,21 +107,16 @@ test "user and group ids" {
105}107}
106108
107test "fadvise" {109test "fadvise" {
110 var tmp = std.testing.tmpDir(.{});
111 defer tmp.cleanup();
112
108 const tmp_file_name = "temp_posix_fadvise.txt";113 const tmp_file_name = "temp_posix_fadvise.txt";
109 var file = try fs.cwd().createFile(tmp_file_name, .{});114 var file = try tmp.dir.createFile(tmp_file_name, .{});
110 defer {115 defer file.close();
111 file.close();
112 fs.cwd().deleteFile(tmp_file_name) catch {};
113 }
114116
115 var buf: [2048]u8 = undefined;117 var buf: [2048]u8 = undefined;
116 try file.writeAll(&buf);118 try file.writeAll(&buf);
117119
118 const ret = linux.fadvise(120 const ret = linux.fadvise(file.handle, 0, 0, linux.POSIX_FADV.SEQUENTIAL);
119 file.handle,
120 0,
121 0,
122 linux.POSIX_FADV.SEQUENTIAL,
123 );
124 try expectEqual(@as(usize, 0), ret);121 try expectEqual(@as(usize, 0), ret);
125}122}
lib/std/os/windows.zig+47-35
...@@ -105,41 +105,53 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN...@@ -105,41 +105,53 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
105 // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.105 // If we're not following symlinks, we need to ensure we don't pass in any synchronization flags such as FILE_SYNCHRONOUS_IO_NONALERT.
106 const flags: ULONG = if (options.follow_symlinks) file_or_dir_flag | blocking_flag else file_or_dir_flag | FILE_OPEN_REPARSE_POINT;106 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(108 while (true) {
109 &result,109 const rc = ntdll.NtCreateFile(
110 options.access_mask,110 &result,
111 &attr,111 options.access_mask,
112 &io,112 &attr,
113 null,113 &io,
114 FILE_ATTRIBUTE_NORMAL,114 null,
115 options.share_access,115 FILE_ATTRIBUTE_NORMAL,
116 options.creation,116 options.share_access,
117 flags,117 options.creation,
118 null,118 flags,
119 0,119 null,
120 );120 0,
121 switch (rc) {121 );
122 .SUCCESS => {122 switch (rc) {
123 if (std.io.is_async and options.io_mode == .evented) {123 .SUCCESS => {
124 _ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined;124 if (std.io.is_async and options.io_mode == .evented) {
125 }125 _ = CreateIoCompletionPort(result, std.event.Loop.instance.?.os_data.io_port, undefined, undefined) catch undefined;
126 return result;126 }
127 },127 return result;
128 .OBJECT_NAME_INVALID => unreachable,128 },
129 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,129 .OBJECT_NAME_INVALID => unreachable,
130 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,130 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
131 .NO_MEDIA_IN_DEVICE => return error.NoDevice,131 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
132 .INVALID_PARAMETER => unreachable,132 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
133 .SHARING_VIOLATION => return error.AccessDenied,133 .INVALID_PARAMETER => unreachable,
134 .ACCESS_DENIED => return error.AccessDenied,134 .SHARING_VIOLATION => return error.AccessDenied,
135 .PIPE_BUSY => return error.PipeBusy,135 .ACCESS_DENIED => return error.AccessDenied,
136 .OBJECT_PATH_SYNTAX_BAD => unreachable,136 .PIPE_BUSY => return error.PipeBusy,
137 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,137 .OBJECT_PATH_SYNTAX_BAD => unreachable,
138 .FILE_IS_A_DIRECTORY => return error.IsDir,138 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
139 .NOT_A_DIRECTORY => return error.NotDir,139 .FILE_IS_A_DIRECTORY => return error.IsDir,
140 .USER_MAPPED_FILE => return error.AccessDenied,140 .NOT_A_DIRECTORY => return error.NotDir,
141 .INVALID_HANDLE => unreachable,141 .USER_MAPPED_FILE => return error.AccessDenied,
142 else => return unexpectedStatus(rc),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 }
143 }155 }
144}156}
145157
lib/std/os/windows/kernel32.zig+3
...@@ -67,6 +67,7 @@ const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;...@@ -67,6 +67,7 @@ const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION;
67const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;67const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS;
68const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;68const EXCEPTION_ROUTINE = windows.EXCEPTION_ROUTINE;
69const MODULEENTRY32 = windows.MODULEENTRY32;69const MODULEENTRY32 = windows.MODULEENTRY32;
70const ULONGLONG = windows.ULONGLONG;
7071
71pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*anyopaque;72pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*anyopaque;
72pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;73pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong;
...@@ -457,3 +458,5 @@ pub extern "kernel32" fn RegOpenKeyExW(...@@ -457,3 +458,5 @@ pub extern "kernel32" fn RegOpenKeyExW(
457 samDesired: REGSAM,458 samDesired: REGSAM,
458 phkResult: *HKEY,459 phkResult: *HKEY,
459) callconv(WINAPI) LSTATUS;460) 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...@@ -828,24 +828,6 @@ pub fn argsWithAllocator(allocator: Allocator) ArgIterator.InitError!ArgIterator
828 return ArgIterator.initWithAllocator(allocator);828 return ArgIterator.initWithAllocator(allocator);
829}829}
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
849/// Caller must call argsFree on result.831/// Caller must call argsFree on result.
850pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {832pub fn argsAlloc(allocator: Allocator) ![][:0]u8 {
851 // TODO refactor to only make 1 allocation.833 // TODO refactor to only make 1 allocation.
...@@ -1169,3 +1151,51 @@ pub fn execve(...@@ -1169,3 +1151,51 @@ pub fn execve(
11691151
1170 return os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp);1152 return os.execvpeZ_expandArg0(.no_expand, argv_buf.ptr[0].?, argv_buf.ptr, envp);
1171}1153}
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");...@@ -3,6 +3,9 @@ const tokenizer = @import("zig/tokenizer.zig");
3const fmt = @import("zig/fmt.zig");3const fmt = @import("zig/fmt.zig");
4const assert = std.debug.assert;4const 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");
6pub const Token = tokenizer.Token;9pub const Token = tokenizer.Token;
7pub const Tokenizer = tokenizer.Tokenizer;10pub const Tokenizer = tokenizer.Tokenizer;
8pub const fmtId = fmt.fmtId;11pub 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(...@@ -1090,6 +1090,11 @@ pub fn getExternalExecutor(
1090 switch (candidate.target.os.tag) {1090 switch (candidate.target.os.tag) {
1091 .windows => {1091 .windows => {
1092 if (options.allow_wine) {1092 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 }
1093 switch (candidate.target.cpu.arch.ptrBitWidth()) {1098 switch (candidate.target.cpu.arch.ptrBitWidth()) {
1094 32 => return Executor{ .wine = "wine" },1099 32 => return Executor{ .wine = "wine" },
1095 64 => return Executor{ .wine = "wine64" },1100 64 => return Executor{ .wine = "wine64" },
lib/test_runner.zig+123-45
...@@ -8,14 +8,126 @@ pub const std_options = struct {...@@ -8,14 +8,126 @@ pub const std_options = struct {
8};8};
99
10var log_err_count: usize = 0;10var log_err_count: usize = 0;
11var cmdline_buffer: [4096]u8 = undefined;
12var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
1113
12pub fn main() void {14pub fn main() void {
13 if (builtin.zig_backend != .stage1 and15 if (builtin.zig_backend == .stage2_wasm or
14 builtin.zig_backend != .stage2_llvm and16 builtin.zig_backend == .stage2_x86_64 or
15 builtin.zig_backend != .stage2_c)17 builtin.zig_backend == .stage2_aarch64)
16 {18 {
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 }
18 }127 }
128}
129
130fn mainTerminal() void {
19 const test_fn_list = builtin.test_functions;131 const test_fn_list = builtin.test_functions;
20 var ok_count: usize = 0;132 var ok_count: usize = 0;
21 var skip_count: usize = 0;133 var skip_count: usize = 0;
...@@ -118,51 +230,17 @@ pub fn log(...@@ -118,51 +230,17 @@ pub fn log(
118 }230 }
119}231}
120232
121pub fn main2() anyerror!void {233/// Simpler main(), exercising fewer language features, so that
122 var skipped: usize = 0;234/// work-in-progress backends can handle it.
123 var failed: usize = 0;235pub fn mainSimple() anyerror!void {
124 // Simpler main(), exercising fewer language features, so that stage2 can handle it.236 //const stderr = std.io.getStdErr();
125 for (builtin.test_functions) |test_fn| {237 for (builtin.test_functions) |test_fn| {
126 test_fn.func() catch |err| {238 test_fn.func() catch |err| {
127 if (err != error.SkipZigTest) {239 if (err != error.SkipZigTest) {
128 failed += 1;240 //stderr.writeAll(test_fn.name) catch {};
129 } else {241 //stderr.writeAll("\n") catch {};
130 skipped += 1;242 return err;
131 }243 }
132 };244 };
133 }245 }
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);
168}246}
src/AstGen.zig+88-40
...@@ -148,18 +148,24 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {...@@ -148,18 +148,24 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir {
148 };148 };
149 defer gz_instructions.deinit(gpa);149 defer gz_instructions.deinit(gpa);
150150
151 if (AstGen.structDeclInner(151 // The AST -> ZIR lowering process assumes an AST that does not have any
152 &gen_scope,152 // parse errors.
153 &gen_scope.base,153 if (tree.errors.len == 0) {
154 0,154 if (AstGen.structDeclInner(
155 tree.containerDeclRoot(),155 &gen_scope,
156 .Auto,156 &gen_scope.base,
157 0,157 0,
158 )) |struct_decl_ref| {158 tree.containerDeclRoot(),
159 assert(refToIndex(struct_decl_ref).? == 0);159 .Auto,
160 } else |err| switch (err) {160 0,
161 error.OutOfMemory => return error.OutOfMemory,161 )) |struct_decl_ref| {
162 error.AnalysisFail => {}, // Handled via compile_errors below.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);
163 }169 }
164170
165 const err_index = @enumToInt(Zir.ExtraIndex.compile_errors);171 const err_index = @enumToInt(Zir.ExtraIndex.compile_errors);
...@@ -10380,7 +10386,7 @@ fn appendErrorTok(...@@ -10380,7 +10386,7 @@ fn appendErrorTok(
10380 comptime format: []const u8,10386 comptime format: []const u8,
10381 args: anytype,10387 args: anytype,
10382) !void {10388) !void {
10383 try astgen.appendErrorTokNotes(token, format, args, &[0]u32{});10389 try astgen.appendErrorTokNotesOff(token, 0, format, args, &[0]u32{});
10384}10390}
1038510391
10386fn failTokNotes(10392fn failTokNotes(
...@@ -10390,7 +10396,7 @@ fn failTokNotes(...@@ -10390,7 +10396,7 @@ fn failTokNotes(
10390 args: anytype,10396 args: anytype,
10391 notes: []const u32,10397 notes: []const u32,
10392) InnerError {10398) InnerError {
10393 try appendErrorTokNotes(astgen, token, format, args, notes);10399 try appendErrorTokNotesOff(astgen, token, 0, format, args, notes);
10394 return error.AnalysisFail;10400 return error.AnalysisFail;
10395}10401}
1039610402
...@@ -10401,27 +10407,11 @@ fn appendErrorTokNotes(...@@ -10401,27 +10407,11 @@ fn appendErrorTokNotes(
10401 args: anytype,10407 args: anytype,
10402 notes: []const u32,10408 notes: []const u32,
10403) !void {10409) !void {
10404 @setCold(true);10410 return appendErrorTokNotesOff(astgen, token, 0, format, args, notes);
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 });
10422}10411}
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.
10425fn failOff(10415fn failOff(
10426 astgen: *AstGen,10416 astgen: *AstGen,
10427 token: Ast.TokenIndex,10417 token: Ast.TokenIndex,
...@@ -10429,27 +10419,36 @@ fn failOff(...@@ -10429,27 +10419,36 @@ fn failOff(
10429 comptime format: []const u8,10419 comptime format: []const u8,
10430 args: anytype,10420 args: anytype,
10431) InnerError {10421) InnerError {
10432 try appendErrorOff(astgen, token, byte_offset, format, args);10422 try appendErrorTokNotesOff(astgen, token, byte_offset, format, args, &.{});
10433 return error.AnalysisFail;10423 return error.AnalysisFail;
10434}10424}
1043510425
10436fn appendErrorOff(10426fn appendErrorTokNotesOff(
10437 astgen: *AstGen,10427 astgen: *AstGen,
10438 token: Ast.TokenIndex,10428 token: Ast.TokenIndex,
10439 byte_offset: u32,10429 byte_offset: u32,
10440 comptime format: []const u8,10430 comptime format: []const u8,
10441 args: anytype,10431 args: anytype,
10442) Allocator.Error!void {10432 notes: []const u32,
10433) !void {
10443 @setCold(true);10434 @setCold(true);
10435 const gpa = astgen.gpa;
10444 const string_bytes = &astgen.string_bytes;10436 const string_bytes = &astgen.string_bytes;
10445 const msg = @intCast(u32, string_bytes.items.len);10437 const msg = @intCast(u32, string_bytes.items.len);
10446 try string_bytes.writer(astgen.gpa).print(format ++ "\x00", args);10438 try string_bytes.writer(gpa).print(format ++ "\x00", args);
10447 try astgen.compile_errors.append(astgen.gpa, .{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, .{
10448 .msg = msg,10447 .msg = msg,
10449 .node = 0,10448 .node = 0,
10450 .token = token,10449 .token = token,
10451 .byte_offset = byte_offset,10450 .byte_offset = byte_offset,
10452 .notes = 0,10451 .notes = notes_index,
10453 });10452 });
10454}10453}
1045510454
...@@ -10458,6 +10457,16 @@ fn errNoteTok(...@@ -10458,6 +10457,16 @@ fn errNoteTok(
10458 token: Ast.TokenIndex,10457 token: Ast.TokenIndex,
10459 comptime format: []const u8,10458 comptime format: []const u8,
10460 args: anytype,10459 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,
10461) Allocator.Error!u32 {10470) Allocator.Error!u32 {
10462 @setCold(true);10471 @setCold(true);
10463 const string_bytes = &astgen.string_bytes;10472 const string_bytes = &astgen.string_bytes;
...@@ -10467,7 +10476,7 @@ fn errNoteTok(...@@ -10467,7 +10476,7 @@ fn errNoteTok(
10467 .msg = msg,10476 .msg = msg,
10468 .node = 0,10477 .node = 0,
10469 .token = token,10478 .token = token,
10470 .byte_offset = 0,10479 .byte_offset = byte_offset,
10471 .notes = 0,10480 .notes = 0,
10472 });10481 });
10473}10482}
...@@ -12634,3 +12643,42 @@ fn emitDbgStmt(gz: *GenZir, line: u32, column: u32) !void {...@@ -12634,3 +12643,42 @@ fn emitDbgStmt(gz: *GenZir, line: u32, column: u32) !void {
12634 },12643 },
12635 } });12644 } });
12636}12645}
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;...@@ -7,6 +7,9 @@ const Allocator = std.mem.Allocator;
7const assert = std.debug.assert;7const assert = std.debug.assert;
8const log = std.log.scoped(.compilation);8const log = std.log.scoped(.compilation);
9const Target = std.Target;9const Target = std.Target;
10const ThreadPool = std.Thread.Pool;
11const WaitGroup = std.Thread.WaitGroup;
12const ErrorBundle = std.zig.ErrorBundle;
1013
11const Value = @import("value.zig").Value;14const Value = @import("value.zig").Value;
12const Type = @import("type.zig").Type;15const Type = @import("type.zig").Type;
...@@ -30,8 +33,6 @@ const Cache = std.Build.Cache;...@@ -30,8 +33,6 @@ const Cache = std.Build.Cache;
30const translate_c = @import("translate_c.zig");33const translate_c = @import("translate_c.zig");
31const clang = @import("clang.zig");34const clang = @import("clang.zig");
32const c_codegen = @import("codegen/c.zig");35const c_codegen = @import("codegen/c.zig");
33const ThreadPool = @import("ThreadPool.zig");
34const WaitGroup = @import("WaitGroup.zig");
35const libtsan = @import("libtsan.zig");36const libtsan = @import("libtsan.zig");
36const Zir = @import("Zir.zig");37const Zir = @import("Zir.zig");
37const Autodoc = @import("Autodoc.zig");38const Autodoc = @import("Autodoc.zig");
...@@ -99,6 +100,7 @@ job_queued_compiler_rt_lib: bool = false,...@@ -99,6 +100,7 @@ job_queued_compiler_rt_lib: bool = false,
99job_queued_compiler_rt_obj: bool = false,100job_queued_compiler_rt_obj: bool = false,
100alloc_failure_occurred: bool = false,101alloc_failure_occurred: bool = false,
101formatted_panics: bool = false,102formatted_panics: bool = false,
103last_update_was_cache_hit: bool = false,
102104
103c_source_files: []const CSourceFile,105c_source_files: []const CSourceFile,
104clang_argv: []const []const u8,106clang_argv: []const []const u8,
...@@ -334,12 +336,41 @@ pub const MiscTask = enum {...@@ -334,12 +336,41 @@ pub const MiscTask = enum {
334 libssp,336 libssp,
335 zig_libc,337 zig_libc,
336 analyze_pkg,338 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",
337};368};
338369
339pub const MiscError = struct {370pub const MiscError = struct {
340 /// Allocated with gpa.371 /// Allocated with gpa.
341 msg: []u8,372 msg: []u8,
342 children: ?AllErrors = null,373 children: ?ErrorBundle = null,
343374
344 pub fn deinit(misc_err: *MiscError, gpa: Allocator) void {375 pub fn deinit(misc_err: *MiscError, gpa: Allocator) void {
345 gpa.free(misc_err.msg);376 gpa.free(misc_err.msg);
...@@ -365,448 +396,6 @@ pub const LldError = struct {...@@ -365,448 +396,6 @@ pub const LldError = struct {
365 }396 }
366};397};
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
810pub const Directory = Cache.Directory;399pub const Directory = Cache.Directory;
811400
812pub const EmitLoc = struct {401pub const EmitLoc = struct {
...@@ -2259,12 +1848,20 @@ fn cleanupTmpArtifactDirectory(...@@ -2259,12 +1848,20 @@ fn cleanupTmpArtifactDirectory(
2259 }1848 }
2260}1849}
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
2262/// Detect changes to source files, perform semantic analysis, and update the output files.1858/// 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 {
2264 const tracy_trace = trace(@src());1860 const tracy_trace = trace(@src());
2265 defer tracy_trace.end();1861 defer tracy_trace.end();
22661862
2267 comp.clearMiscFailures();1863 comp.clearMiscFailures();
1864 comp.last_update_was_cache_hit = false;
22681865
2269 var man: Cache.Manifest = undefined;1866 var man: Cache.Manifest = undefined;
2270 defer if (comp.whole_cache_manifest != null) man.deinit();1867 defer if (comp.whole_cache_manifest != null) man.deinit();
...@@ -2292,6 +1889,7 @@ pub fn update(comp: *Compilation) !void {...@@ -2292,6 +1889,7 @@ pub fn update(comp: *Compilation) !void {
2292 return err;1889 return err;
2293 };1890 };
2294 if (is_hit) {1891 if (is_hit) {
1892 comp.last_update_was_cache_hit = true;
2295 log.debug("CacheMode.whole cache hit for {s}", .{comp.bin_file.options.root_name});1893 log.debug("CacheMode.whole cache hit for {s}", .{comp.bin_file.options.root_name});
2296 const digest = man.final();1894 const digest = man.final();
22971895
...@@ -2407,21 +2005,6 @@ pub fn update(comp: *Compilation) !void {...@@ -2407,21 +2005,6 @@ pub fn update(comp: *Compilation) !void {
2407 }2005 }
2408 }2006 }
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
2425 try comp.performAllTheWork(main_progress_node);2008 try comp.performAllTheWork(main_progress_node);
24262009
2427 if (comp.bin_file.options.module) |module| {2010 if (comp.bin_file.options.module) |module| {
...@@ -2891,7 +2474,7 @@ pub fn makeBinFileWritable(self: *Compilation) !void {...@@ -2891,7 +2474,7 @@ pub fn makeBinFileWritable(self: *Compilation) !void {
2891}2474}
28922475
2893/// This function is temporally single-threaded.2476/// This function is temporally single-threaded.
2894pub fn totalErrorCount(self: *Compilation) usize {2477pub fn totalErrorCount(self: *Compilation) u32 {
2895 var total: usize = self.failed_c_objects.count() + self.misc_failures.count() +2478 var total: usize = self.failed_c_objects.count() + self.misc_failures.count() +
2896 @boolToInt(self.alloc_failure_occurred) + self.lld_errors.items.len;2479 @boolToInt(self.alloc_failure_occurred) + self.lld_errors.items.len;
28972480
...@@ -2951,17 +2534,16 @@ pub fn totalErrorCount(self: *Compilation) usize {...@@ -2951,17 +2534,16 @@ pub fn totalErrorCount(self: *Compilation) usize {
2951 }2534 }
2952 }2535 }
29532536
2954 return total;2537 return @intCast(u32, total);
2955}2538}
29562539
2957/// This function is temporally single-threaded.2540/// This function is temporally single-threaded.
2958pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {2541pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
2959 var arena = std.heap.ArenaAllocator.init(self.gpa);2542 const gpa = self.gpa;
2960 errdefer arena.deinit();
2961 const arena_allocator = arena.allocator();
29622543
2963 var errors = std.ArrayList(AllErrors.Message).init(self.gpa);2544 var bundle: ErrorBundle.Wip = undefined;
2964 defer errors.deinit();2545 try bundle.init(gpa);
2546 defer bundle.deinit();
29652547
2966 {2548 {
2967 var it = self.failed_c_objects.iterator();2549 var it = self.failed_c_objects.iterator();
...@@ -2970,53 +2552,58 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {...@@ -2970,53 +2552,58 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
2970 const err_msg = entry.value_ptr.*;2552 const err_msg = entry.value_ptr.*;
2971 // TODO these fields will need to be adjusted when we have proper2553 // TODO these fields will need to be adjusted when we have proper
2972 // C error reporting bubbling up.2554 // C error reporting bubbling up.
2973 try errors.append(.{2555 try bundle.addRootErrorMessage(.{
2974 .src = .{2556 .msg = try bundle.printString("unable to build C object: {s}", .{err_msg.msg}),
2975 .src_path = try arena_allocator.dupe(u8, c_object.src.src_path),2557 .src_loc = try bundle.addSourceLocation(.{
2976 .msg = try std.fmt.allocPrint(arena_allocator, "unable to build C object: {s}", .{2558 .src_path = try bundle.addString(c_object.src.src_path),
2977 err_msg.msg,2559 .span_start = 0,
2978 }),2560 .span_main = 0,
2979 .span = .{ .start = 0, .end = 1, .main = 0 },2561 .span_end = 1,
2980 .line = err_msg.line,2562 .line = err_msg.line,
2981 .column = err_msg.column,2563 .column = err_msg.column,
2982 .source_line = null, // TODO2564 .source_line = 0, // TODO
2983 },2565 }),
2984 });2566 });
2985 }2567 }
2986 }2568 }
2569
2987 for (self.lld_errors.items) |lld_error| {2570 for (self.lld_errors.items) |lld_error| {
2988 const notes = try arena_allocator.alloc(AllErrors.Message, lld_error.context_lines.len);2571 const notes_len = @intCast(u32, 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 }
29942572
2995 try errors.append(.{2573 try bundle.addRootErrorMessage(.{
2996 .plain = .{2574 .msg = try bundle.addString(lld_error.msg),
2997 .msg = try arena_allocator.dupe(u8, lld_error.msg),2575 .notes_len = notes_len,
2998 .notes = notes,
2999 },
3000 });2576 });
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 }
3001 }2583 }
3002 for (self.misc_failures.values()) |*value| {2584 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);
3004 }2590 }
3005 if (self.alloc_failure_occurred) {2591 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 });
3007 }2595 }
3008 if (self.bin_file.options.module) |module| {2596 if (self.bin_file.options.module) |module| {
3009 {2597 {
3010 var it = module.failed_files.iterator();2598 var it = module.failed_files.iterator();
3011 while (it.next()) |entry| {2599 while (it.next()) |entry| {
3012 if (entry.value_ptr.*) |msg| {2600 if (entry.value_ptr.*) |msg| {
3013 try AllErrors.add(module, &arena, &errors, msg.*);2601 try addModuleErrorMsg(&bundle, msg.*);
3014 } else {2602 } else {
3015 // Must be ZIR errors. In order for ZIR errors to exist, the parsing2603 // Must be ZIR errors. Note that this may include AST errors.
3016 // must have completed successfully.2604 // addZirErrorMessages asserts that the tree is loaded.
3017 const tree = try entry.key_ptr.*.getTree(module.gpa);2605 _ = try entry.key_ptr.*.getTree(gpa);
3018 assert(tree.errors.len == 0);2606 try addZirErrorMessages(&bundle, entry.key_ptr.*);
3019 try AllErrors.addZir(arena_allocator, &errors, entry.key_ptr.*);
3020 }2607 }
3021 }2608 }
3022 }2609 }
...@@ -3024,7 +2611,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {...@@ -3024,7 +2611,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
3024 var it = module.failed_embed_files.iterator();2611 var it = module.failed_embed_files.iterator();
3025 while (it.next()) |entry| {2612 while (it.next()) |entry| {
3026 const msg = entry.value_ptr.*;2613 const msg = entry.value_ptr.*;
3027 try AllErrors.add(module, &arena, &errors, msg.*);2614 try addModuleErrorMsg(&bundle, msg.*);
3028 }2615 }
3029 }2616 }
3030 {2617 {
...@@ -3034,23 +2621,20 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {...@@ -3034,23 +2621,20 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
3034 // Skip errors for Decls within files that had a parse failure.2621 // Skip errors for Decls within files that had a parse failure.
3035 // We'll try again once parsing succeeds.2622 // We'll try again once parsing succeeds.
3036 if (decl.getFileScope().okToReportErrors()) {2623 if (decl.getFileScope().okToReportErrors()) {
3037 try AllErrors.add(module, &arena, &errors, entry.value_ptr.*.*);2624 try addModuleErrorMsg(&bundle, entry.value_ptr.*.*);
3038 if (module.cimport_errors.get(entry.key_ptr.*)) |cimport_errors| for (cimport_errors) |c_error| {2625 if (module.cimport_errors.get(entry.key_ptr.*)) |cimport_errors| for (cimport_errors) |c_error| {
3039 if (c_error.path) |some|2626 try bundle.addRootErrorMessage(.{
3040 try errors.append(.{2627 .msg = try bundle.addString(std.mem.span(c_error.msg)),
3041 .src = .{2628 .src_loc = if (c_error.path) |some| try bundle.addSourceLocation(.{
3042 .src_path = try arena_allocator.dupe(u8, std.mem.span(some)),2629 .src_path = try bundle.addString(std.mem.span(some)),
3043 .span = .{ .start = c_error.offset, .end = c_error.offset + 1, .main = c_error.offset },2630 .span_start = c_error.offset,
3044 .msg = try arena_allocator.dupe(u8, std.mem.span(c_error.msg)),2631 .span_main = c_error.offset,
3045 .line = c_error.line,2632 .span_end = c_error.offset + 1,
3046 .column = c_error.column,2633 .line = c_error.line,
3047 .source_line = if (c_error.source_line) |line| try arena_allocator.dupe(u8, std.mem.span(line)) else null,2634 .column = c_error.column,
3048 },2635 .source_line = if (c_error.source_line) |line| try bundle.addString(std.mem.span(line)) else 0,
3049 })2636 }) else .none,
3050 else2637 });
3051 try errors.append(.{
3052 .plain = .{ .msg = try arena_allocator.dupe(u8, std.mem.span(c_error.msg)) },
3053 });
3054 };2638 };
3055 }2639 }
3056 }2640 }
...@@ -3062,45 +2646,39 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {...@@ -3062,45 +2646,39 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
3062 // Skip errors for Decls within files that had a parse failure.2646 // Skip errors for Decls within files that had a parse failure.
3063 // We'll try again once parsing succeeds.2647 // We'll try again once parsing succeeds.
3064 if (decl.getFileScope().okToReportErrors()) {2648 if (decl.getFileScope().okToReportErrors()) {
3065 try AllErrors.add(module, &arena, &errors, entry.value_ptr.*.*);2649 try addModuleErrorMsg(&bundle, entry.value_ptr.*.*);
3066 }2650 }
3067 }2651 }
3068 }2652 }
3069 for (module.failed_exports.values()) |value| {2653 for (module.failed_exports.values()) |value| {
3070 try AllErrors.add(module, &arena, &errors, value.*);2654 try addModuleErrorMsg(&bundle, value.*);
3071 }2655 }
3072 }2656 }
30732657
3074 if (errors.items.len == 0) {2658 if (bundle.root_list.items.len == 0) {
3075 if (self.link_error_flags.no_entry_point_found) {2659 if (self.link_error_flags.no_entry_point_found) {
3076 try errors.append(.{2660 try bundle.addRootErrorMessage(.{
3077 .plain = .{2661 .msg = try bundle.addString("no entry point found"),
3078 .msg = try std.fmt.allocPrint(arena_allocator, "no entry point found", .{}),
3079 },
3080 });2662 });
3081 }2663 }
3082 }2664 }
30832665
3084 if (self.link_error_flags.missing_libc) {2666 if (self.link_error_flags.missing_libc) {
3085 const notes = try arena_allocator.create([2]AllErrors.Message);2667 try bundle.addRootErrorMessage(.{
3086 notes.* = .{2668 .msg = try bundle.addString("libc not available"),
3087 .{ .plain = .{2669 .notes_len = 2,
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 },
3099 });2670 });
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 }));
3100 }2678 }
31012679
3102 if (self.bin_file.options.module) |module| {2680 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) {
3104 const keys = module.compile_log_decls.keys();2682 const keys = module.compile_log_decls.keys();
3105 const values = module.compile_log_decls.values();2683 const values = module.compile_log_decls.values();
3106 // First one will be the error; subsequent ones will be notes.2684 // First one will be the error; subsequent ones will be notes.
...@@ -3109,9 +2687,9 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {...@@ -3109,9 +2687,9 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
3109 const err_msg = Module.ErrorMsg{2687 const err_msg = Module.ErrorMsg{
3110 .src_loc = src_loc,2688 .src_loc = src_loc,
3111 .msg = "found compile log statement",2689 .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),
3113 };2691 };
3114 defer self.gpa.free(err_msg.notes);2692 defer gpa.free(err_msg.notes);
31152693
3116 for (keys[1..], 0..) |key, i| {2694 for (keys[1..], 0..) |key, i| {
3117 const note_decl = module.declPtr(key);2695 const note_decl = module.declPtr(key);
...@@ -3121,21 +2699,260 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {...@@ -3121,21 +2699,260 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
3121 };2699 };
3122 }2700 }
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);
3125 }2726 }
2727
2728 return @truncate(u32, hasher.final());
3126 }2729 }
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{2758pub fn addModuleErrorMsg(eb: *ErrorBundle.Wip, module_err_msg: Module.ErrorMsg) !void {
3131 .list = try arena_allocator.dupe(AllErrors.Message, errors.items),2759 const gpa = eb.gpa;
3132 .arena = arena.state,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;
3133 };2769 };
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 }
3134}2871}
31352872
3136pub fn getCompileLogOutput(self: *Compilation) []const u8 {2873pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Module.File) !void {
3137 const module = self.bin_file.options.module orelse return &[0]u8{};2874 assert(file.zir_loaded);
3138 return module.compile_log_text.items;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 }
3139}2956}
31402957
3141pub fn performAllTheWork(2958pub fn performAllTheWork(
...@@ -3231,11 +3048,11 @@ pub fn performAllTheWork(...@@ -3231,11 +3048,11 @@ pub fn performAllTheWork(
3231 // backend, preventing anonymous Decls from being prematurely destroyed.3048 // backend, preventing anonymous Decls from being prematurely destroyed.
3232 while (true) {3049 while (true) {
3233 if (comp.work_queue.readItem()) |work_item| {3050 if (comp.work_queue.readItem()) |work_item| {
3234 try processOneJob(comp, work_item);3051 try processOneJob(comp, work_item, main_progress_node);
3235 continue;3052 continue;
3236 }3053 }
3237 if (comp.anon_work_queue.readItem()) |work_item| {3054 if (comp.anon_work_queue.readItem()) |work_item| {
3238 try processOneJob(comp, work_item);3055 try processOneJob(comp, work_item, main_progress_node);
3239 continue;3056 continue;
3240 }3057 }
3241 break;3058 break;
...@@ -3243,16 +3060,16 @@ pub fn performAllTheWork(...@@ -3243,16 +3060,16 @@ pub fn performAllTheWork(
32433060
3244 if (comp.job_queued_compiler_rt_lib) {3061 if (comp.job_queued_compiler_rt_lib) {
3245 comp.job_queued_compiler_rt_lib = false;3062 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);
3247 }3064 }
32483065
3249 if (comp.job_queued_compiler_rt_obj) {3066 if (comp.job_queued_compiler_rt_obj) {
3250 comp.job_queued_compiler_rt_obj = false;3067 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);
3252 }3069 }
3253}3070}
32543071
3255fn processOneJob(comp: *Compilation, job: Job) !void {3072fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !void {
3256 switch (job) {3073 switch (job) {
3257 .codegen_decl => |decl_index| {3074 .codegen_decl => |decl_index| {
3258 const module = comp.bin_file.options.module.?;3075 const module = comp.bin_file.options.module.?;
...@@ -3404,7 +3221,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3404,7 +3221,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3404 const named_frame = tracy.namedFrame("glibc_crt_file");3221 const named_frame = tracy.namedFrame("glibc_crt_file");
3405 defer named_frame.end();3222 defer named_frame.end();
34063223
3407 glibc.buildCRTFile(comp, crt_file) catch |err| {3224 glibc.buildCRTFile(comp, crt_file, prog_node) catch |err| {
3408 // TODO Surface more error details.3225 // TODO Surface more error details.
3409 comp.lockAndSetMiscFailure(.glibc_crt_file, "unable to build glibc CRT file: {s}", .{3226 comp.lockAndSetMiscFailure(.glibc_crt_file, "unable to build glibc CRT file: {s}", .{
3410 @errorName(err),3227 @errorName(err),
...@@ -3415,7 +3232,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3415,7 +3232,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3415 const named_frame = tracy.namedFrame("glibc_shared_objects");3232 const named_frame = tracy.namedFrame("glibc_shared_objects");
3416 defer named_frame.end();3233 defer named_frame.end();
34173234
3418 glibc.buildSharedObjects(comp) catch |err| {3235 glibc.buildSharedObjects(comp, prog_node) catch |err| {
3419 // TODO Surface more error details.3236 // TODO Surface more error details.
3420 comp.lockAndSetMiscFailure(3237 comp.lockAndSetMiscFailure(
3421 .glibc_shared_objects,3238 .glibc_shared_objects,
...@@ -3428,7 +3245,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3428,7 +3245,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3428 const named_frame = tracy.namedFrame("musl_crt_file");3245 const named_frame = tracy.namedFrame("musl_crt_file");
3429 defer named_frame.end();3246 defer named_frame.end();
34303247
3431 musl.buildCRTFile(comp, crt_file) catch |err| {3248 musl.buildCRTFile(comp, crt_file, prog_node) catch |err| {
3432 // TODO Surface more error details.3249 // TODO Surface more error details.
3433 comp.lockAndSetMiscFailure(3250 comp.lockAndSetMiscFailure(
3434 .musl_crt_file,3251 .musl_crt_file,
...@@ -3441,7 +3258,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3441,7 +3258,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3441 const named_frame = tracy.namedFrame("mingw_crt_file");3258 const named_frame = tracy.namedFrame("mingw_crt_file");
3442 defer named_frame.end();3259 defer named_frame.end();
34433260
3444 mingw.buildCRTFile(comp, crt_file) catch |err| {3261 mingw.buildCRTFile(comp, crt_file, prog_node) catch |err| {
3445 // TODO Surface more error details.3262 // TODO Surface more error details.
3446 comp.lockAndSetMiscFailure(3263 comp.lockAndSetMiscFailure(
3447 .mingw_crt_file,3264 .mingw_crt_file,
...@@ -3468,7 +3285,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3468,7 +3285,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3468 const named_frame = tracy.namedFrame("libunwind");3285 const named_frame = tracy.namedFrame("libunwind");
3469 defer named_frame.end();3286 defer named_frame.end();
34703287
3471 libunwind.buildStaticLib(comp) catch |err| {3288 libunwind.buildStaticLib(comp, prog_node) catch |err| {
3472 // TODO Surface more error details.3289 // TODO Surface more error details.
3473 comp.lockAndSetMiscFailure(3290 comp.lockAndSetMiscFailure(
3474 .libunwind,3291 .libunwind,
...@@ -3481,7 +3298,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3481,7 +3298,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3481 const named_frame = tracy.namedFrame("libcxx");3298 const named_frame = tracy.namedFrame("libcxx");
3482 defer named_frame.end();3299 defer named_frame.end();
34833300
3484 libcxx.buildLibCXX(comp) catch |err| {3301 libcxx.buildLibCXX(comp, prog_node) catch |err| {
3485 // TODO Surface more error details.3302 // TODO Surface more error details.
3486 comp.lockAndSetMiscFailure(3303 comp.lockAndSetMiscFailure(
3487 .libcxx,3304 .libcxx,
...@@ -3494,7 +3311,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3494,7 +3311,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3494 const named_frame = tracy.namedFrame("libcxxabi");3311 const named_frame = tracy.namedFrame("libcxxabi");
3495 defer named_frame.end();3312 defer named_frame.end();
34963313
3497 libcxx.buildLibCXXABI(comp) catch |err| {3314 libcxx.buildLibCXXABI(comp, prog_node) catch |err| {
3498 // TODO Surface more error details.3315 // TODO Surface more error details.
3499 comp.lockAndSetMiscFailure(3316 comp.lockAndSetMiscFailure(
3500 .libcxxabi,3317 .libcxxabi,
...@@ -3507,7 +3324,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3507,7 +3324,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3507 const named_frame = tracy.namedFrame("libtsan");3324 const named_frame = tracy.namedFrame("libtsan");
3508 defer named_frame.end();3325 defer named_frame.end();
35093326
3510 libtsan.buildTsan(comp) catch |err| {3327 libtsan.buildTsan(comp, prog_node) catch |err| {
3511 // TODO Surface more error details.3328 // TODO Surface more error details.
3512 comp.lockAndSetMiscFailure(3329 comp.lockAndSetMiscFailure(
3513 .libtsan,3330 .libtsan,
...@@ -3520,7 +3337,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3520,7 +3337,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3520 const named_frame = tracy.namedFrame("wasi_libc_crt_file");3337 const named_frame = tracy.namedFrame("wasi_libc_crt_file");
3521 defer named_frame.end();3338 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| {
3524 // TODO Surface more error details.3341 // TODO Surface more error details.
3525 comp.lockAndSetMiscFailure(3342 comp.lockAndSetMiscFailure(
3526 .wasi_libc_crt_file,3343 .wasi_libc_crt_file,
...@@ -3538,6 +3355,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3538,6 +3355,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3538 .Lib,3355 .Lib,
3539 &comp.libssp_static_lib,3356 &comp.libssp_static_lib,
3540 .libssp,3357 .libssp,
3358 prog_node,
3541 ) catch |err| switch (err) {3359 ) catch |err| switch (err) {
3542 error.OutOfMemory => return error.OutOfMemory,3360 error.OutOfMemory => return error.OutOfMemory,
3543 error.SubCompilationFailed => return, // error reported already3361 error.SubCompilationFailed => return, // error reported already
...@@ -3557,6 +3375,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {...@@ -3557,6 +3375,7 @@ fn processOneJob(comp: *Compilation, job: Job) !void {
3557 .Lib,3375 .Lib,
3558 &comp.libc_static_lib,3376 &comp.libc_static_lib,
3559 .zig_libc,3377 .zig_libc,
3378 prog_node,
3560 ) catch |err| switch (err) {3379 ) catch |err| switch (err) {
3561 error.OutOfMemory => return error.OutOfMemory,3380 error.OutOfMemory => return error.OutOfMemory,
3562 error.SubCompilationFailed => return, // error reported already3381 error.SubCompilationFailed => return, // error reported already
...@@ -3897,8 +3716,15 @@ fn buildCompilerRtOneShot(...@@ -3897,8 +3716,15 @@ fn buildCompilerRtOneShot(
3897 comp: *Compilation,3716 comp: *Compilation,
3898 output_mode: std.builtin.OutputMode,3717 output_mode: std.builtin.OutputMode,
3899 out: *?CRTFile,3718 out: *?CRTFile,
3719 prog_node: *std.Progress.Node,
3900) void {3720) 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) {
3902 error.SubCompilationFailed => return, // error reported already3728 error.SubCompilationFailed => return, // error reported already
3903 else => comp.lockAndSetMiscFailure(3729 else => comp.lockAndSetMiscFailure(
3904 .compiler_rt,3730 .compiler_rt,
...@@ -5230,7 +5056,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca...@@ -5230,7 +5056,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
5230 \\const std = @import("std");5056 \\const std = @import("std");
5231 \\/// Zig version. When writing code that supports multiple versions of Zig, prefer5057 \\/// Zig version. When writing code that supports multiple versions of Zig, prefer
5232 \\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks.5058 \\/// 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}";
5234 \\pub const zig_backend = std.builtin.CompilerBackend.{};5061 \\pub const zig_backend = std.builtin.CompilerBackend.{};
5235 \\5062 \\
5236 \\pub const output_mode = std.builtin.OutputMode.{};5063 \\pub const output_mode = std.builtin.OutputMode.{};
...@@ -5417,34 +5244,36 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca...@@ -5417,34 +5244,36 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
5417 return buffer.toOwnedSliceSentinel(0);5244 return buffer.toOwnedSliceSentinel(0);
5418}5245}
54195246
5420pub fn updateSubCompilation(sub_compilation: *Compilation) !void {5247pub fn updateSubCompilation(
5421 try sub_compilation.update();5248 parent_comp: *Compilation,
54225249 sub_comp: *Compilation,
5423 // Look for compilation errors in this sub_compilation5250 misc_task: MiscTask,
5424 // TODO instead of logging these errors, handle them in the callsites5251 prog_node: *std.Progress.Node,
5425 // of updateSubCompilation and attach them as sub-errors, properly5252) !void {
5426 // surfacing the errors. You can see an example of this already5253 {
5427 // done inside buildOutputFromZig.5254 var sub_node = prog_node.start(@tagName(misc_task), 0);
5428 var errors = try sub_compilation.getAllErrorsAlloc();5255 sub_node.activate();
5429 defer errors.deinit(sub_compilation.gpa);5256 defer sub_node.end();
54305257
5431 if (errors.list.len != 0) {5258 try sub_comp.update(prog_node);
5432 for (errors.list) |full_err_msg| {5259 }
5433 switch (full_err_msg) {5260
5434 .src => |src| {5261 // Look for compilation errors in this sub compilation
5435 log.err("{s}:{d}:{d}: {s}", .{5262 const gpa = parent_comp.gpa;
5436 src.src_path,5263 var keep_errors = false;
5437 src.line + 1,5264 var errors = try sub_comp.getAllErrorsAlloc();
5438 src.column + 1,5265 defer if (!keep_errors) errors.deinit(gpa);
5439 src.msg,5266
5440 });5267 if (errors.errorMessageCount() > 0) {
5441 },5268 try parent_comp.misc_failures.ensureUnusedCapacity(gpa, 1);
5442 .plain => |plain| {5269 parent_comp.misc_failures.putAssumeCapacityNoClobber(misc_task, .{
5443 log.err("{s}", .{plain.msg});5270 .msg = try std.fmt.allocPrint(gpa, "sub-compilation of {s} failed", .{
5444 },5271 @tagName(misc_task),
5445 }5272 }),
5446 }5273 .children = errors,
5447 return error.BuildingLibCObjectFailed;5274 });
5275 keep_errors = true;
5276 return error.SubCompilationFailed;
5448 }5277 }
5449}5278}
54505279
...@@ -5454,6 +5283,7 @@ fn buildOutputFromZig(...@@ -5454,6 +5283,7 @@ fn buildOutputFromZig(
5454 output_mode: std.builtin.OutputMode,5283 output_mode: std.builtin.OutputMode,
5455 out: *?CRTFile,5284 out: *?CRTFile,
5456 misc_task_tag: MiscTask,5285 misc_task_tag: MiscTask,
5286 prog_node: *std.Progress.Node,
5457) !void {5287) !void {
5458 const tracy_trace = trace(@src());5288 const tracy_trace = trace(@src());
5459 defer tracy_trace.end();5289 defer tracy_trace.end();
...@@ -5520,23 +5350,7 @@ fn buildOutputFromZig(...@@ -5520,23 +5350,7 @@ fn buildOutputFromZig(
5520 });5350 });
5521 defer sub_compilation.destroy();5351 defer sub_compilation.destroy();
55225352
5523 try sub_compilation.update();5353 try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node);
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 }
55405354
5541 assert(out.* == null);5355 assert(out.* == null);
5542 out.* = Compilation.CRTFile{5356 out.* = Compilation.CRTFile{
...@@ -5551,6 +5365,8 @@ pub fn build_crt_file(...@@ -5551,6 +5365,8 @@ pub fn build_crt_file(
5551 comp: *Compilation,5365 comp: *Compilation,
5552 root_name: []const u8,5366 root_name: []const u8,
5553 output_mode: std.builtin.OutputMode,5367 output_mode: std.builtin.OutputMode,
5368 misc_task_tag: MiscTask,
5369 prog_node: *std.Progress.Node,
5554 c_source_files: []const Compilation.CSourceFile,5370 c_source_files: []const Compilation.CSourceFile,
5555) !void {5371) !void {
5556 const tracy_trace = trace(@src());5372 const tracy_trace = trace(@src());
...@@ -5611,7 +5427,7 @@ pub fn build_crt_file(...@@ -5611,7 +5427,7 @@ pub fn build_crt_file(
5611 });5427 });
5612 defer sub_compilation.destroy();5428 defer sub_compilation.destroy();
56135429
5614 try sub_compilation.updateSubCompilation();5430 try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node);
56155431
5616 try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1);5432 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 {...@@ -3756,67 +3756,9 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
3756 file.source_loaded = true;3756 file.source_loaded = true;
37573757
3758 file.tree = try Ast.parse(gpa, source, .zig);3758 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 }
3818 file.tree_loaded = true;3759 file.tree_loaded = true;
38193760
3761 // Any potential AST errors are converted to ZIR errors here.
3820 file.zir = try AstGen.generate(gpa, file.tree);3762 file.zir = try AstGen.generate(gpa, file.tree);
3821 file.zir_loaded = true;3763 file.zir_loaded = true;
3822 file.status = .success_zir;3764 file.status = .success_zir;
...@@ -3925,6 +3867,9 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {...@@ -3925,6 +3867,9 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {
3925 const gpa = mod.gpa;3867 const gpa = mod.gpa;
3926 const new_zir = file.zir;3868 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
3928 // Maps from old ZIR to new ZIR, struct_decl, enum_decl, etc. Any instruction which3873 // Maps from old ZIR to new ZIR, struct_decl, enum_decl, etc. Any instruction which
3929 // creates a namespace, gets mapped from old to new here.3874 // creates a namespace, gets mapped from old to new here.
3930 var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};3875 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 {...@@ -3942,7 +3887,6 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {
3942 var decl_stack: ArrayListUnmanaged(Decl.Index) = .{};3887 var decl_stack: ArrayListUnmanaged(Decl.Index) = .{};
3943 defer decl_stack.deinit(gpa);3888 defer decl_stack.deinit(gpa);
39443889
3945 const root_decl = file.root_decl.unwrap().?;
3946 try decl_stack.append(gpa, root_decl);3890 try decl_stack.append(gpa, root_decl);
39473891
3948 file.deleted_decls.clearRetainingCapacity();3892 file.deleted_decls.clearRetainingCapacity();
src/Package.zig+53-57
...@@ -8,11 +8,11 @@ const Allocator = mem.Allocator;...@@ -8,11 +8,11 @@ const Allocator = mem.Allocator;
8const assert = std.debug.assert;8const assert = std.debug.assert;
9const log = std.log.scoped(.package);9const log = std.log.scoped(.package);
10const main = @import("main.zig");10const main = @import("main.zig");
11const ThreadPool = std.Thread.Pool;
12const WaitGroup = std.Thread.WaitGroup;
1113
12const Compilation = @import("Compilation.zig");14const Compilation = @import("Compilation.zig");
13const Module = @import("Module.zig");15const Module = @import("Module.zig");
14const ThreadPool = @import("ThreadPool.zig");
15const WaitGroup = @import("WaitGroup.zig");
16const Cache = std.Build.Cache;16const Cache = std.Build.Cache;
17const build_options = @import("build_options");17const build_options = @import("build_options");
18const Manifest = @import("Manifest.zig");18const Manifest = @import("Manifest.zig");
...@@ -225,7 +225,7 @@ pub fn fetchAndAddDependencies(...@@ -225,7 +225,7 @@ pub fn fetchAndAddDependencies(
225 dependencies_source: *std.ArrayList(u8),225 dependencies_source: *std.ArrayList(u8),
226 build_roots_source: *std.ArrayList(u8),226 build_roots_source: *std.ArrayList(u8),
227 name_prefix: []const u8,227 name_prefix: []const u8,
228 color: main.Color,228 error_bundle: *std.zig.ErrorBundle.Wip,
229 all_modules: *AllModules,229 all_modules: *AllModules,
230) !void {230) !void {
231 const max_bytes = 10 * 1024 * 1024;231 const max_bytes = 10 * 1024 * 1024;
...@@ -250,7 +250,7 @@ pub fn fetchAndAddDependencies(...@@ -250,7 +250,7 @@ pub fn fetchAndAddDependencies(
250250
251 if (ast.errors.len > 0) {251 if (ast.errors.len > 0) {
252 const file_path = try directory.join(arena, &.{Manifest.basename});252 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);
254 return error.PackageFetchFailed;254 return error.PackageFetchFailed;
255 }255 }
256256
...@@ -258,14 +258,9 @@ pub fn fetchAndAddDependencies(...@@ -258,14 +258,9 @@ pub fn fetchAndAddDependencies(
258 defer manifest.deinit(gpa);258 defer manifest.deinit(gpa);
259259
260 if (manifest.errors.len > 0) {260 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 };
266 const file_path = try directory.join(arena, &.{Manifest.basename});261 const file_path = try directory.join(arena, &.{Manifest.basename});
267 for (manifest.errors) |msg| {262 for (manifest.errors) |msg| {
268 Report.renderErrorMessage(ast, file_path, ttyconf, msg, &.{});263 try Report.addErrorMessage(ast, file_path, error_bundle, 0, msg);
269 }264 }
270 return error.PackageFetchFailed;265 return error.PackageFetchFailed;
271 }266 }
...@@ -273,8 +268,7 @@ pub fn fetchAndAddDependencies(...@@ -273,8 +268,7 @@ pub fn fetchAndAddDependencies(
273 const report: Report = .{268 const report: Report = .{
274 .ast = &ast,269 .ast = &ast,
275 .directory = directory,270 .directory = directory,
276 .color = color,271 .error_bundle = error_bundle,
277 .arena = arena,
278 };272 };
279273
280 var any_error = false;274 var any_error = false;
...@@ -307,7 +301,7 @@ pub fn fetchAndAddDependencies(...@@ -307,7 +301,7 @@ pub fn fetchAndAddDependencies(
307 dependencies_source,301 dependencies_source,
308 build_roots_source,302 build_roots_source,
309 sub_prefix,303 sub_prefix,
310 color,304 error_bundle,
311 all_modules,305 all_modules,
312 );306 );
313307
...@@ -350,8 +344,7 @@ pub fn createFilePkg(...@@ -350,8 +344,7 @@ pub fn createFilePkg(
350const Report = struct {344const Report = struct {
351 ast: *const std.zig.Ast,345 ast: *const std.zig.Ast,
352 directory: Compilation.Directory,346 directory: Compilation.Directory,
353 color: main.Color,347 error_bundle: *std.zig.ErrorBundle.Wip,
354 arena: Allocator,
355348
356 fn fail(349 fn fail(
357 report: Report,350 report: Report,
...@@ -359,52 +352,46 @@ const Report = struct {...@@ -359,52 +352,46 @@ const Report = struct {
359 comptime fmt_string: []const u8,352 comptime fmt_string: []const u8,
360 fmt_args: anytype,353 fmt_args: anytype,
361 ) error{ PackageFetchFailed, OutOfMemory } {354 ) error{ PackageFetchFailed, OutOfMemory } {
362 return failWithNotes(report, &.{}, tok, fmt_string, fmt_args);355 const gpa = report.error_bundle.gpa;
363 }
364356
365 fn failWithNotes(357 const file_path = try report.directory.join(gpa, &.{Manifest.basename});
366 report: Report,358 defer gpa.free(file_path);
367 notes: []const Compilation.AllErrors.Message,359
368 tok: std.zig.Ast.TokenIndex,360 const msg = try std.fmt.allocPrint(gpa, fmt_string, fmt_args);
369 comptime fmt_string: []const u8,361 defer gpa.free(msg);
370 fmt_args: anytype,362
371 ) error{ PackageFetchFailed, OutOfMemory } {363 try addErrorMessage(report.ast.*, file_path, report.error_bundle, 0, .{
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, .{
379 .tok = tok,364 .tok = tok,
380 .off = 0,365 .off = 0,
381 .msg = try std.fmt.allocPrint(report.arena, fmt_string, fmt_args),366 .msg = msg,
382 }, notes);367 });
368
383 return error.PackageFetchFailed;369 return error.PackageFetchFailed;
384 }370 }
385371
386 fn renderErrorMessage(372 fn addErrorMessage(
387 ast: std.zig.Ast,373 ast: std.zig.Ast,
388 file_path: []const u8,374 file_path: []const u8,
389 ttyconf: std.debug.TTY.Config,375 eb: *std.zig.ErrorBundle.Wip,
376 notes_len: u32,
390 msg: Manifest.ErrorMessage,377 msg: Manifest.ErrorMessage,
391 notes: []const Compilation.AllErrors.Message,378 ) error{OutOfMemory}!void {
392 ) void {
393 const token_starts = ast.tokens.items(.start);379 const token_starts = ast.tokens.items(.start);
394 const start_loc = ast.tokenLocation(0, msg.tok);380 const start_loc = ast.tokenLocation(0, msg.tok);
395 Compilation.AllErrors.Message.renderToStdErr(.{ .src = .{381
396 .msg = msg.msg,382 try eb.addRootErrorMessage(.{
397 .src_path = file_path,383 .msg = try eb.addString(msg.msg),
398 .line = @intCast(u32, start_loc.line),384 .src_loc = try eb.addSourceLocation(.{
399 .column = @intCast(u32, start_loc.column),385 .src_path = try eb.addString(file_path),
400 .span = .{386 .span_start = token_starts[msg.tok],
401 .start = token_starts[msg.tok],387 .span_end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),
402 .end = @intCast(u32, token_starts[msg.tok] + ast.tokenSlice(msg.tok).len),388 .span_main = token_starts[msg.tok] + msg.off,
403 .main = token_starts[msg.tok] + msg.off,389 .line = @intCast(u32, start_loc.line),
404 },390 .column = @intCast(u32, start_loc.column),
405 .source_line = ast.source[start_loc.line_start..start_loc.line_end],391 .source_line = try eb.addString(ast.source[start_loc.line_start..start_loc.line_end]),
406 .notes = notes,392 }),
407 } }, ttyconf);393 .notes_len = notes_len,
394 });
408 }395 }
409};396};
410397
...@@ -504,9 +491,7 @@ fn fetchAndUnpack(...@@ -504,9 +491,7 @@ fn fetchAndUnpack(
504 // by default, so the same logic applies for buffering the reader as for gzip.491 // by default, so the same logic applies for buffering the reader as for gzip.
505 try unpackTarball(gpa, &req, tmp_directory.handle, std.compress.xz);492 try unpackTarball(gpa, &req, tmp_directory.handle, std.compress.xz);
506 } else {493 } else {
507 return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{494 return report.fail(dep.url_tok, "unknown file extension for path '{s}'", .{uri.path});
508 uri.path,
509 });
510 }495 }
511496
512 // TODO: delete files not included in the package prior to computing the package hash.497 // TODO: delete files not included in the package prior to computing the package hash.
...@@ -533,10 +518,21 @@ fn fetchAndUnpack(...@@ -533,10 +518,21 @@ fn fetchAndUnpack(
533 });518 });
534 }519 }
535 } else {520 } else {
536 const notes: [1]Compilation.AllErrors.Message = .{.{ .plain = .{521 const file_path = try report.directory.join(gpa, &.{Manifest.basename});
537 .msg = try std.fmt.allocPrint(report.arena, "expected .hash = \"{s}\",", .{&actual_hex}),522 defer gpa.free(file_path);
538 } }};523
539 return report.failWithNotes(&notes, dep.url_tok, "url field is missing corresponding hash field", .{});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;
540 }536 }
541537
542 const build_root = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path});538 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(...@@ -2211,29 +2211,27 @@ pub fn fail(
22112211
2212fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {2212fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
2213 @setCold(true);2213 @setCold(true);
2214 const gpa = sema.gpa;
22142215
2215 if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) {2216 if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) {
2216 if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation;2217 if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation;
2217 var arena = std.heap.ArenaAllocator.init(sema.gpa);2218 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
2218 errdefer arena.deinit();2219 wip_errors.init(gpa) catch unreachable;
2219 var errors = std.ArrayList(Compilation.AllErrors.Message).init(sema.gpa);2220 Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable;
2220 defer errors.deinit();
2221
2222 Compilation.AllErrors.add(sema.mod, &arena, &errors, err_msg.*) catch unreachable;
2223
2224 std.debug.print("compile error during Sema:\n", .{});2221 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 });
2226 crash_report.compilerPanic("unexpected compile error occurred", null, null);2224 crash_report.compilerPanic("unexpected compile error occurred", null, null);
2227 }2225 }
22282226
2229 const mod = sema.mod;2227 const mod = sema.mod;
2230 ref: {2228 ref: {
2231 errdefer err_msg.destroy(mod.gpa);2229 errdefer err_msg.destroy(gpa);
2232 if (err_msg.src_loc.lazy == .unneeded) {2230 if (err_msg.src_loc.lazy == .unneeded) {
2233 return error.NeededSourceLocation;2231 return error.NeededSourceLocation;
2234 }2232 }
2235 try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1);2233 try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
2236 try mod.failed_files.ensureUnusedCapacity(mod.gpa, 1);2234 try mod.failed_files.ensureUnusedCapacity(gpa, 1);
22372235
2238 const max_references = blk: {2236 const max_references = blk: {
2239 if (sema.mod.comp.reference_trace) |num| break :blk num;2237 if (sema.mod.comp.reference_trace) |num| break :blk num;
...@@ -2243,11 +2241,11 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {...@@ -2243,11 +2241,11 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
2243 };2241 };
22442242
2245 var referenced_by = if (sema.func) |some| some.owner_decl else sema.owner_decl_index;2243 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);
2247 defer reference_stack.deinit();2245 defer reference_stack.deinit();
22482246
2249 // Avoid infinite loops.2247 // 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);
2251 defer seen.deinit();2249 defer seen.deinit();
22522250
2253 var cur_reference_trace: u32 = 0;2251 var cur_reference_trace: u32 = 0;
...@@ -2288,7 +2286,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {...@@ -2288,7 +2286,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
2288 if (gop.found_existing) {2286 if (gop.found_existing) {
2289 // If there are multiple errors for the same Decl, prefer the first one added.2287 // If there are multiple errors for the same Decl, prefer the first one added.
2290 sema.err = null;2288 sema.err = null;
2291 err_msg.destroy(mod.gpa);2289 err_msg.destroy(gpa);
2292 } else {2290 } else {
2293 sema.err = err_msg;2291 sema.err = err_msg;
2294 gop.value_ptr.* = err_msg;2292 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 {...@@ -3594,6 +3594,12 @@ pub const Inst = struct {
3594 /// 0 or a payload index of a `Block`, each is a payload3594 /// 0 or a payload index of a `Block`, each is a payload
3595 /// index of another `Item`.3595 /// index of another `Item`.
3596 notes: u32,3596 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 }
3597 };3603 };
3598 };3604 };
35993605
src/glibc.zig+11-8
...@@ -161,7 +161,7 @@ pub const CRTFile = enum {...@@ -161,7 +161,7 @@ pub const CRTFile = enum {
161 libc_nonshared_a,161 libc_nonshared_a,
162};162};
163163
164pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {164pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
165 if (!build_options.have_llvm) {165 if (!build_options.have_llvm) {
166 return error.ZigCompilerNotBuiltWithLLVMExtensions;166 return error.ZigCompilerNotBuiltWithLLVMExtensions;
167 }167 }
...@@ -196,7 +196,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -196,7 +196,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
196 "-DASSEMBLER",196 "-DASSEMBLER",
197 "-Wa,--noexecstack",197 "-Wa,--noexecstack",
198 });198 });
199 return comp.build_crt_file("crti", .Obj, &[1]Compilation.CSourceFile{199 return comp.build_crt_file("crti", .Obj, .@"glibc crti.o", prog_node, &.{
200 .{200 .{
201 .src_path = try start_asm_path(comp, arena, "crti.S"),201 .src_path = try start_asm_path(comp, arena, "crti.S"),
202 .cache_exempt_flags = args.items,202 .cache_exempt_flags = args.items,
...@@ -215,7 +215,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -215,7 +215,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
215 "-DASSEMBLER",215 "-DASSEMBLER",
216 "-Wa,--noexecstack",216 "-Wa,--noexecstack",
217 });217 });
218 return comp.build_crt_file("crtn", .Obj, &[1]Compilation.CSourceFile{218 return comp.build_crt_file("crtn", .Obj, .@"glibc crtn.o", prog_node, &.{
219 .{219 .{
220 .src_path = try start_asm_path(comp, arena, "crtn.S"),220 .src_path = try start_asm_path(comp, arena, "crtn.S"),
221 .cache_exempt_flags = args.items,221 .cache_exempt_flags = args.items,
...@@ -265,7 +265,9 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -265,7 +265,9 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
265 .cache_exempt_flags = args.items,265 .cache_exempt_flags = args.items,
266 };266 };
267 };267 };
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 });
269 },271 },
270 .libc_nonshared_a => {272 .libc_nonshared_a => {
271 const s = path.sep_str;273 const s = path.sep_str;
...@@ -366,7 +368,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -366,7 +368,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
366 files_index += 1;368 files_index += 1;
367 }369 }
368 const files = files_buf[0..files_index];370 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);
370 },372 },
371 }373 }
372}374}
...@@ -639,7 +641,7 @@ pub const BuiltSharedObjects = struct {...@@ -639,7 +641,7 @@ pub const BuiltSharedObjects = struct {
639641
640const all_map_basename = "all.map";642const all_map_basename = "all.map";
641643
642pub fn buildSharedObjects(comp: *Compilation) !void {644pub fn buildSharedObjects(comp: *Compilation, prog_node: *std.Progress.Node) !void {
643 const tracy = trace(@src());645 const tracy = trace(@src());
644 defer tracy.end();646 defer tracy.end();
645647
...@@ -1023,7 +1025,7 @@ pub fn buildSharedObjects(comp: *Compilation) !void {...@@ -1023,7 +1025,7 @@ pub fn buildSharedObjects(comp: *Compilation) !void {
1023 const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;1025 const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;
1024 try o_directory.handle.writeFile(asm_file_basename, stubs_asm.items);1026 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);
1027 }1029 }
10281030
1029 man.writeManifest() catch |err| {1031 man.writeManifest() catch |err| {
...@@ -1046,6 +1048,7 @@ fn buildSharedLib(...@@ -1046,6 +1048,7 @@ fn buildSharedLib(
1046 bin_directory: Compilation.Directory,1048 bin_directory: Compilation.Directory,
1047 asm_file_basename: []const u8,1049 asm_file_basename: []const u8,
1048 lib: Lib,1050 lib: Lib,
1051 prog_node: *std.Progress.Node,
1049) !void {1052) !void {
1050 const tracy = trace(@src());1053 const tracy = trace(@src());
1051 defer tracy.end();1054 defer tracy.end();
...@@ -1105,7 +1108,7 @@ fn buildSharedLib(...@@ -1105,7 +1108,7 @@ fn buildSharedLib(
1105 });1108 });
1106 defer sub_compilation.destroy();1109 defer sub_compilation.destroy();
11071110
1108 try sub_compilation.updateSubCompilation();1111 try comp.updateSubCompilation(sub_compilation, .@"glibc shared object", prog_node);
1109}1112}
11101113
1111// Return true if glibc has crti/crtn sources for that architecture.1114// 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{...@@ -96,7 +96,7 @@ const libcxx_files = [_][]const u8{
96 "src/verbose_abort.cpp",96 "src/verbose_abort.cpp",
97};97};
9898
99pub fn buildLibCXX(comp: *Compilation) !void {99pub fn buildLibCXX(comp: *Compilation, prog_node: *std.Progress.Node) !void {
100 if (!build_options.have_llvm) {100 if (!build_options.have_llvm) {
101 return error.ZigCompilerNotBuiltWithLLVMExtensions;101 return error.ZigCompilerNotBuiltWithLLVMExtensions;
102 }102 }
...@@ -258,7 +258,7 @@ pub fn buildLibCXX(comp: *Compilation) !void {...@@ -258,7 +258,7 @@ pub fn buildLibCXX(comp: *Compilation) !void {
258 });258 });
259 defer sub_compilation.destroy();259 defer sub_compilation.destroy();
260260
261 try sub_compilation.updateSubCompilation();261 try comp.updateSubCompilation(sub_compilation, .libcxx, prog_node);
262262
263 assert(comp.libcxx_static_lib == null);263 assert(comp.libcxx_static_lib == null);
264 comp.libcxx_static_lib = Compilation.CRTFile{264 comp.libcxx_static_lib = Compilation.CRTFile{
...@@ -269,7 +269,7 @@ pub fn buildLibCXX(comp: *Compilation) !void {...@@ -269,7 +269,7 @@ pub fn buildLibCXX(comp: *Compilation) !void {
269 };269 };
270}270}
271271
272pub fn buildLibCXXABI(comp: *Compilation) !void {272pub fn buildLibCXXABI(comp: *Compilation, prog_node: *std.Progress.Node) !void {
273 if (!build_options.have_llvm) {273 if (!build_options.have_llvm) {
274 return error.ZigCompilerNotBuiltWithLLVMExtensions;274 return error.ZigCompilerNotBuiltWithLLVMExtensions;
275 }275 }
...@@ -418,7 +418,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void {...@@ -418,7 +418,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void {
418 });418 });
419 defer sub_compilation.destroy();419 defer sub_compilation.destroy();
420420
421 try sub_compilation.updateSubCompilation();421 try comp.updateSubCompilation(sub_compilation, .libcxxabi, prog_node);
422422
423 assert(comp.libcxxabi_static_lib == null);423 assert(comp.libcxxabi_static_lib == null);
424 comp.libcxxabi_static_lib = Compilation.CRTFile{424 comp.libcxxabi_static_lib = Compilation.CRTFile{
src/libtsan.zig+2-2
...@@ -5,7 +5,7 @@ const Compilation = @import("Compilation.zig");...@@ -5,7 +5,7 @@ const Compilation = @import("Compilation.zig");
5const build_options = @import("build_options");5const build_options = @import("build_options");
6const trace = @import("tracy.zig").trace;6const trace = @import("tracy.zig").trace;
77
8pub fn buildTsan(comp: *Compilation) !void {8pub fn buildTsan(comp: *Compilation, prog_node: *std.Progress.Node) !void {
9 if (!build_options.have_llvm) {9 if (!build_options.have_llvm) {
10 return error.ZigCompilerNotBuiltWithLLVMExtensions;10 return error.ZigCompilerNotBuiltWithLLVMExtensions;
11 }11 }
...@@ -235,7 +235,7 @@ pub fn buildTsan(comp: *Compilation) !void {...@@ -235,7 +235,7 @@ pub fn buildTsan(comp: *Compilation) !void {
235 });235 });
236 defer sub_compilation.destroy();236 defer sub_compilation.destroy();
237237
238 try sub_compilation.updateSubCompilation();238 try comp.updateSubCompilation(sub_compilation, .libtsan, prog_node);
239239
240 assert(comp.tsan_static_lib == null);240 assert(comp.tsan_static_lib == null);
241 comp.tsan_static_lib = Compilation.CRTFile{241 comp.tsan_static_lib = Compilation.CRTFile{
src/libunwind.zig+2-2
...@@ -7,7 +7,7 @@ const Compilation = @import("Compilation.zig");...@@ -7,7 +7,7 @@ const Compilation = @import("Compilation.zig");
7const build_options = @import("build_options");7const build_options = @import("build_options");
8const trace = @import("tracy.zig").trace;8const trace = @import("tracy.zig").trace;
99
10pub fn buildStaticLib(comp: *Compilation) !void {10pub fn buildStaticLib(comp: *Compilation, prog_node: *std.Progress.Node) !void {
11 if (!build_options.have_llvm) {11 if (!build_options.have_llvm) {
12 return error.ZigCompilerNotBuiltWithLLVMExtensions;12 return error.ZigCompilerNotBuiltWithLLVMExtensions;
13 }13 }
...@@ -130,7 +130,7 @@ pub fn buildStaticLib(comp: *Compilation) !void {...@@ -130,7 +130,7 @@ pub fn buildStaticLib(comp: *Compilation) !void {
130 });130 });
131 defer sub_compilation.destroy();131 defer sub_compilation.destroy();
132132
133 try sub_compilation.updateSubCompilation();133 try comp.updateSubCompilation(sub_compilation, .libunwind, prog_node);
134134
135 assert(comp.libunwind_static_lib == null);135 assert(comp.libunwind_static_lib == null);
136136
src/link.zig+40-3
...@@ -264,6 +264,8 @@ pub const File = struct {...@@ -264,6 +264,8 @@ pub const File = struct {
264 /// of this linking operation.264 /// of this linking operation.
265 lock: ?Cache.Lock = null,265 lock: ?Cache.Lock = null,
266266
267 child_pid: ?std.ChildProcess.Id = null,
268
267 /// Attempts incremental linking, if the file already exists. If269 /// Attempts incremental linking, if the file already exists. If
268 /// incremental linking fails, falls back to truncating the file and270 /// incremental linking fails, falls back to truncating the file and
269 /// rewriting it. A malicious file is detected as incremental link failure271 /// rewriting it. A malicious file is detected as incremental link failure
...@@ -376,6 +378,26 @@ pub const File = struct {...@@ -376,6 +378,26 @@ pub const File = struct {
376 if (build_options.only_c) unreachable;378 if (build_options.only_c) unreachable;
377 if (base.file != null) return;379 if (base.file != null) return;
378 const emit = base.options.emit orelse return;380 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 }
379 base.file = try emit.directory.handle.createFile(emit.sub_path, .{401 base.file = try emit.directory.handle.createFile(emit.sub_path, .{
380 .truncate = false,402 .truncate = false,
381 .read = true,403 .read = true,
...@@ -424,6 +446,18 @@ pub const File = struct {...@@ -424,6 +446,18 @@ pub const File = struct {
424 }446 }
425 f.close();447 f.close();
426 base.file = null;448 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 }
427 },461 },
428 .c, .spirv, .nvptx => {},462 .c, .spirv, .nvptx => {},
429 }463 }
...@@ -462,6 +496,7 @@ pub const File = struct {...@@ -462,6 +496,7 @@ pub const File = struct {
462 NetNameDeleted,496 NetNameDeleted,
463 DeviceBusy,497 DeviceBusy,
464 InvalidArgument,498 InvalidArgument,
499 HotSwapUnavailableOnHostOperatingSystem,
465 };500 };
466501
467 /// Called from within the CodeGen to lower a local variable instantion as an unnamed502 /// Called from within the CodeGen to lower a local variable instantion as an unnamed
...@@ -1053,9 +1088,11 @@ pub const File = struct {...@@ -1053,9 +1088,11 @@ pub const File = struct {
1053 log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)});1088 log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)});
1054 };1089 };
10551090
1056 man.writeManifest() catch |err| {1091 if (man.have_exclusive_lock) {
1057 log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)});1092 man.writeManifest() catch |err| {
1058 };1093 log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)});
1094 };
1095 }
10591096
1060 base.lock = man.toOwnedLock();1097 base.lock = man.toOwnedLock();
1061 }1098 }
src/link/Elf.zig+50-5
...@@ -467,7 +467,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -467,7 +467,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
467 .p_paddr = entry_addr,467 .p_paddr = entry_addr,
468 .p_memsz = file_size,468 .p_memsz = file_size,
469 .p_align = p_align,469 .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,
471 });471 });
472 self.entry_addr = null;472 self.entry_addr = null;
473 self.phdr_table_dirty = true;473 self.phdr_table_dirty = true;
...@@ -493,7 +493,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -493,7 +493,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
493 .p_paddr = got_addr,493 .p_paddr = got_addr,
494 .p_memsz = file_size,494 .p_memsz = file_size,
495 .p_align = p_align,495 .p_align = p_align,
496 .p_flags = elf.PF_R,496 .p_flags = elf.PF_R | elf.PF_W,
497 });497 });
498 self.phdr_table_dirty = true;498 self.phdr_table_dirty = true;
499 }499 }
...@@ -516,7 +516,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -516,7 +516,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
516 .p_paddr = rodata_addr,516 .p_paddr = rodata_addr,
517 .p_memsz = file_size,517 .p_memsz = file_size,
518 .p_align = p_align,518 .p_align = p_align,
519 .p_flags = elf.PF_R,519 .p_flags = elf.PF_R | elf.PF_W,
520 });520 });
521 self.phdr_table_dirty = true;521 self.phdr_table_dirty = true;
522 }522 }
...@@ -2166,7 +2166,7 @@ fn allocateAtom(self: *Elf, atom_index: Atom.Index, new_block_size: u64, alignme...@@ -2166,7 +2166,7 @@ fn allocateAtom(self: *Elf, atom_index: Atom.Index, new_block_size: u64, alignme
2166 // First we look for an appropriately sized free list node.2166 // First we look for an appropriately sized free list node.
2167 // The list is unordered. We'll just take the first thing that works.2167 // The list is unordered. We'll just take the first thing that works.
2168 const vaddr = blk: {2168 const vaddr = blk: {
2169 var i: usize = 0;2169 var i: usize = if (self.base.child_pid == null) 0 else free_list.items.len;
2170 while (i < free_list.items.len) {2170 while (i < free_list.items.len) {
2171 const big_atom_index = free_list.items[i];2171 const big_atom_index = free_list.items[i];
2172 const big_atom = self.getAtom(big_atom_index);2172 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...@@ -2397,7 +2397,7 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s
2397 const atom = self.getAtom(atom_index);2397 const atom = self.getAtom(atom_index);
23982398
2399 const shdr_index = decl_metadata.shdr;2399 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) {
2401 const local_sym = atom.getSymbolPtr(self);2401 const local_sym = atom.getSymbolPtr(self);
2402 local_sym.st_name = try self.shstrtab.insert(gpa, decl_name);2402 local_sym.st_name = try self.shstrtab.insert(gpa, decl_name);
2403 local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits;2403 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...@@ -2451,6 +2451,28 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s
2451 const phdr_index = self.sections.items(.phdr_index)[shdr_index];2451 const phdr_index = self.sections.items(.phdr_index)[shdr_index];
2452 const section_offset = local_sym.st_value - self.program_headers.items[phdr_index].p_vaddr;2452 const section_offset = local_sym.st_value - self.program_headers.items[phdr_index].p_vaddr;
2453 const file_offset = self.sections.items(.shdr)[shdr_index].sh_offset + section_offset;2453 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
2454 try self.base.file.?.pwriteAll(code, file_offset);2476 try self.base.file.?.pwriteAll(code, file_offset);
24552477
2456 return local_sym;2478 return local_sym;
...@@ -2820,6 +2842,8 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {...@@ -2820,6 +2842,8 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
2820 const endian = self.base.options.target.cpu.arch.endian();2842 const endian = self.base.options.target.cpu.arch.endian();
2821 const shdr = &self.sections.items(.shdr)[self.got_section_index.?];2843 const shdr = &self.sections.items(.shdr)[self.got_section_index.?];
2822 const off = shdr.sh_offset + @as(u64, entry_size) * index;2844 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;
2823 switch (entry_size) {2847 switch (entry_size) {
2824 2 => {2848 2 => {
2825 var buf: [2]u8 = undefined;2849 var buf: [2]u8 = undefined;
...@@ -2835,6 +2859,27 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {...@@ -2835,6 +2859,27 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
2835 var buf: [8]u8 = undefined;2859 var buf: [8]u8 = undefined;
2836 mem.writeInt(u64, &buf, self.offset_table.items[index], endian);2860 mem.writeInt(u64, &buf, self.offset_table.items[index], endian);
2837 try self.base.file.?.pwriteAll(&buf, off);2861 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 }
2838 },2883 },
2839 else => unreachable,2884 else => unreachable,
2840 }2885 }
src/link/MachO/CodeSignature.zig+2-2
...@@ -7,12 +7,12 @@ const log = std.log.scoped(.link);...@@ -7,12 +7,12 @@ const log = std.log.scoped(.link);
7const macho = std.macho;7const macho = std.macho;
8const mem = std.mem;8const mem = std.mem;
9const testing = std.testing;9const testing = std.testing;
10const ThreadPool = std.Thread.Pool;
11const WaitGroup = std.Thread.WaitGroup;
1012
11const Allocator = mem.Allocator;13const Allocator = mem.Allocator;
12const Compilation = @import("../../Compilation.zig");14const Compilation = @import("../../Compilation.zig");
13const Sha256 = std.crypto.hash.sha2.Sha256;15const Sha256 = std.crypto.hash.sha2.Sha256;
14const ThreadPool = @import("../../ThreadPool.zig");
15const WaitGroup = @import("../../WaitGroup.zig");
1616
17const hash_size = Sha256.digest_length;17const hash_size = Sha256.digest_length;
1818
src/main.zig+495-337
...@@ -9,6 +9,8 @@ const Allocator = mem.Allocator;...@@ -9,6 +9,8 @@ const Allocator = mem.Allocator;
9const ArrayList = std.ArrayList;9const ArrayList = std.ArrayList;
10const Ast = std.zig.Ast;10const Ast = std.zig.Ast;
11const warn = std.log.warn;11const warn = std.log.warn;
12const ThreadPool = std.Thread.Pool;
13const cleanExit = std.process.cleanExit;
1214
13const tracy = @import("tracy.zig");15const tracy = @import("tracy.zig");
14const Compilation = @import("Compilation.zig");16const Compilation = @import("Compilation.zig");
...@@ -22,8 +24,10 @@ const translate_c = @import("translate_c.zig");...@@ -22,8 +24,10 @@ const translate_c = @import("translate_c.zig");
22const clang = @import("clang.zig");24const clang = @import("clang.zig");
23const Cache = std.Build.Cache;25const Cache = std.Build.Cache;
24const target_util = @import("target.zig");26const target_util = @import("target.zig");
25const ThreadPool = @import("ThreadPool.zig");
26const crash_report = @import("crash_report.zig");27const crash_report = @import("crash_report.zig");
28const Module = @import("Module.zig");
29const AstGen = @import("AstGen.zig");
30const Server = std.zig.Server;
2731
28pub const std_options = struct {32pub const std_options = struct {
29 pub const wasiCwd = wasi_cwd;33 pub const wasiCwd = wasi_cwd;
...@@ -361,7 +365,6 @@ const usage_build_generic =...@@ -361,7 +365,6 @@ const usage_build_generic =
361 \\365 \\
362 \\General Options:366 \\General Options:
363 \\ -h, --help Print this help and exit367 \\ -h, --help Print this help and exit
364 \\ --watch Enable compiler REPL
365 \\ --color [auto|off|on] Enable or disable colored error messages368 \\ --color [auto|off|on] Enable or disable colored error messages
366 \\ -femit-bin[=path] (default) Output machine code369 \\ -femit-bin[=path] (default) Output machine code
367 \\ -fno-emit-bin Do not output machine code370 \\ -fno-emit-bin Do not output machine code
...@@ -666,6 +669,16 @@ const ArgMode = union(enum) {...@@ -666,6 +669,16 @@ const ArgMode = union(enum) {
666 run,669 run,
667};670};
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
669fn buildOutputType(682fn buildOutputType(
670 gpa: Allocator,683 gpa: Allocator,
671 arena: Allocator,684 arena: Allocator,
...@@ -686,7 +699,7 @@ fn buildOutputType(...@@ -686,7 +699,7 @@ fn buildOutputType(
686 var formatted_panics: ?bool = null;699 var formatted_panics: ?bool = null;
687 var function_sections = false;700 var function_sections = false;
688 var no_builtin = false;701 var no_builtin = false;
689 var watch = false;702 var listen: Listen = .none;
690 var debug_compile_errors = false;703 var debug_compile_errors = false;
691 var verbose_link = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_LINK");704 var verbose_link = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_LINK");
692 var verbose_cc = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_CC");705 var verbose_cc = (builtin.os.tag != .wasi or builtin.link_libc) and std.process.hasEnvVarConstant("ZIG_VERBOSE_CC");
...@@ -1144,6 +1157,23 @@ fn buildOutputType(...@@ -1144,6 +1157,23 @@ fn buildOutputType(
1144 } else {1157 } else {
1145 try log_scopes.append(gpa, args_iter.nextOrFatal());1158 try log_scopes.append(gpa, args_iter.nextOrFatal());
1146 }1159 }
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;
1147 } else if (mem.eql(u8, arg, "--debug-link-snapshot")) {1177 } else if (mem.eql(u8, arg, "--debug-link-snapshot")) {
1148 if (!build_options.enable_link_snapshots) {1178 if (!build_options.enable_link_snapshots) {
1149 std.log.warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{});1179 std.log.warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{});
...@@ -1172,8 +1202,6 @@ fn buildOutputType(...@@ -1172,8 +1202,6 @@ fn buildOutputType(
1172 test_evented_io = true;1202 test_evented_io = true;
1173 } else if (mem.eql(u8, arg, "--test-no-exec")) {1203 } else if (mem.eql(u8, arg, "--test-no-exec")) {
1174 test_no_exec = true;1204 test_no_exec = true;
1175 } else if (mem.eql(u8, arg, "--watch")) {
1176 watch = true;
1177 } else if (mem.eql(u8, arg, "-ftime-report")) {1205 } else if (mem.eql(u8, arg, "-ftime-report")) {
1178 time_report = true;1206 time_report = true;
1179 } else if (mem.eql(u8, arg, "-fstack-report")) {1207 } else if (mem.eql(u8, arg, "-fstack-report")) {
...@@ -2999,7 +3027,7 @@ fn buildOutputType(...@@ -2999,7 +3027,7 @@ fn buildOutputType(
2999 defer zig_lib_directory.handle.close();3027 defer zig_lib_directory.handle.close();
30003028
3001 var thread_pool: ThreadPool = undefined;3029 var thread_pool: ThreadPool = undefined;
3002 try thread_pool.init(gpa);3030 try thread_pool.init(.{ .allocator = gpa });
3003 defer thread_pool.deinit();3031 defer thread_pool.deinit();
30043032
3005 var libc_installation: ?LibCInstallation = null;3033 var libc_installation: ?LibCInstallation = null;
...@@ -3259,8 +3287,52 @@ fn buildOutputType(...@@ -3259,8 +3287,52 @@ fn buildOutputType(
3259 if (show_builtin) {3287 if (show_builtin) {
3260 return std.io.getStdOut().writeAll(try comp.generateBuiltinZigSource(arena));3288 return std.io.getStdOut().writeAll(try comp.generateBuiltinZigSource(arena));
3261 }3289 }
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
3262 if (arg_mode == .translate_c) {3334 if (arg_mode == .translate_c) {
3263 return cmdTranslateC(comp, arena, have_enable_cache);3335 return cmdTranslateC(comp, arena, null);
3264 }3336 }
32653337
3266 const hook: AfterUpdateHook = blk: {3338 const hook: AfterUpdateHook = blk: {
...@@ -3276,7 +3348,7 @@ fn buildOutputType(...@@ -3276,7 +3348,7 @@ fn buildOutputType(
3276 };3348 };
32773349
3278 updateModule(gpa, comp, hook) catch |err| switch (err) {3350 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),
3280 else => |e| return e,3352 else => |e| return e,
3281 };3353 };
3282 if (build_options.only_c) return cleanExit();3354 if (build_options.only_c) return cleanExit();
...@@ -3332,7 +3404,6 @@ fn buildOutputType(...@@ -3332,7 +3404,6 @@ fn buildOutputType(
3332 self_exe_path.?,3404 self_exe_path.?,
3333 arg_mode,3405 arg_mode,
3334 target_info,3406 target_info,
3335 watch,
3336 &comp_destroyed,3407 &comp_destroyed,
3337 all_args,3408 all_args,
3338 runtime_args_start,3409 runtime_args_start,
...@@ -3340,109 +3411,215 @@ fn buildOutputType(...@@ -3340,109 +3411,215 @@ fn buildOutputType(
3340 );3411 );
3341 }3412 }
33423413
3343 const stdin = std.io.getStdIn().reader();3414 // Skip resource deallocation in release builds; let the OS do it.
3344 const stderr = std.io.getStdErr().writer();3415 return cleanExit();
3345 var repl_buf: [1024]u8 = undefined;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 {3430 var server = try Server.init(.{
3348 update,3431 .gpa = gpa,
3349 help,3432 .in = in,
3350 run,3433 .out = out,
3351 update_and_run,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,
3352 };3453 };
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) {3460 switch (hdr.tag) {
3357 try stderr.print("(zig) ", .{});3461 .exit => {
3358 try comp.makeBinFileExecutable();3462 return cleanExit();
3359 if (stdin.readUntilDelimiterOrEof(&repl_buf, '\n') catch |err| {3463 },
3360 try stderr.print("\nUnable to parse command: {s}\n", .{@errorName(err)});3464 .update => {
3361 continue;3465 assert(main_progress_node.recently_updated_child == null);
3362 }) |line| {3466 tracy.frameMark();
3363 const actual_line = mem.trimRight(u8, line, "\r\n ");3467
3364 const cmd: ReplCmd = blk: {3468 if (arg_mode == .translate_c) {
3365 if (mem.eql(u8, actual_line, "update")) {3469 var arena_instance = std.heap.ArenaAllocator.init(gpa);
3366 break :blk .update;3470 defer arena_instance.deinit();
3367 } else if (mem.eql(u8, actual_line, "exit")) {3471 const arena = arena_instance.allocator();
3368 break;3472 var output: TranslateCOutput = undefined;
3369 } else if (mem.eql(u8, actual_line, "help")) {3473 try cmdTranslateC(comp, arena, &output);
3370 break :blk .help;3474 try server.serveEmitBinPath(output.path, .{
3371 } else if (mem.eql(u8, actual_line, "run")) {3475 .flags = .{ .cache_hit = output.cache_hit },
3372 break :blk .run;3476 });
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});
3379 continue;3477 continue;
3380 }3478 }
3381 };3479
3382 last_cmd = cmd;3480 if (comp.bin_file.options.output_mode == .Exe) {
3383 switch (cmd) {3481 try comp.makeBinFileWritable();
3384 .update => {3482 }
3385 tracy.frameMark();3483
3386 if (output_mode == .Exe) {3484 {
3387 try comp.makeBinFileWritable();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();
3388 }3493 }
3389 updateModule(gpa, comp, hook) catch |err| switch (err) {3494
3390 error.SemanticAnalyzeFail => continue,3495 try comp.update(main_progress_node);
3391 else => |e| return e,3496 }
3392 };3497
3393 },3498 try comp.makeBinFileExecutable();
3394 .help => {3499 try serveUpdateResults(&server, comp);
3395 try stderr.writeAll(repl_help);3500 },
3396 },3501 .run => {
3397 .run => {3502 if (child_pid != null) {
3398 tracy.frameMark();3503 @panic("TODO block until the child exits");
3399 try runOrTest(3504 }
3400 comp,3505 @panic("TODO call runOrTest");
3401 gpa,3506 //try runOrTest(
3402 arena,3507 // comp,
3403 test_exec_args.items,3508 // gpa,
3404 self_exe_path.?,3509 // arena,
3405 arg_mode,3510 // test_exec_args,
3406 target_info,3511 // self_exe_path.?,
3407 watch,3512 // arg_mode,
3408 &comp_destroyed,3513 // target_info,
3409 all_args,3514 // true,
3410 runtime_args_start,3515 // &comp_destroyed,
3411 link_libc,3516 // all_args,
3412 );3517 // runtime_args_start,
3413 },3518 // link_libc,
3414 .update_and_run => {3519 //);
3415 tracy.frameMark();3520 },
3416 if (output_mode == .Exe) {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) {
3417 try comp.makeBinFileWritable();3529 try comp.makeBinFileWritable();
3418 }3530 }
3419 updateModule(gpa, comp, hook) catch |err| switch (err) {3531 try comp.update(main_progress_node);
3420 error.SemanticAnalyzeFail => continue,
3421 else => |e| return e,
3422 };
3423 try comp.makeBinFileExecutable();3532 try comp.makeBinFileExecutable();
3424 try runOrTest(3533 try serveUpdateResults(&server, comp);
3534
3535 child_pid = try runOrTestHotSwap(
3425 comp,3536 comp,
3426 gpa,3537 gpa,
3427 arena,3538 test_exec_args,
3428 test_exec_args.items,
3429 self_exe_path.?,3539 self_exe_path.?,
3430 arg_mode,3540 arg_mode,
3431 target_info,
3432 watch,
3433 &comp_destroyed,
3434 all_args,3541 all_args,
3435 runtime_args_start,3542 runtime_args_start,
3436 link_libc,
3437 );3543 );
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);
3439 }3594 }
3440 } else {
3441 break;
3442 }3595 }
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 });
3443 }3622 }
3444 // Skip resource deallocation in release builds; let the OS do it.
3445 return cleanExit();
3446}3623}
34473624
3448const ModuleDepIterator = struct {3625const ModuleDepIterator = struct {
...@@ -3530,7 +3707,6 @@ fn runOrTest(...@@ -3530,7 +3707,6 @@ fn runOrTest(
3530 self_exe_path: []const u8,3707 self_exe_path: []const u8,
3531 arg_mode: ArgMode,3708 arg_mode: ArgMode,
3532 target_info: std.zig.system.NativeTargetInfo,3709 target_info: std.zig.system.NativeTargetInfo,
3533 watch: bool,
3534 comp_destroyed: *bool,3710 comp_destroyed: *bool,
3535 all_args: []const []const u8,3711 all_args: []const []const u8,
3536 runtime_args_start: ?usize,3712 runtime_args_start: ?usize,
...@@ -3561,7 +3737,7 @@ fn runOrTest(...@@ -3561,7 +3737,7 @@ fn runOrTest(
35613737
3562 // We do not execve for tests because if the test fails we want to print3738 // We do not execve for tests because if the test fails we want to print
3563 // the error message and invocation below.3739 // 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) {
3565 // execv releases the locks; no need to destroy the Compilation here.3741 // execv releases the locks; no need to destroy the Compilation here.
3566 const err = std.process.execve(gpa, argv.items, &env_map);3742 const err = std.process.execve(gpa, argv.items, &env_map);
3567 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);3743 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
...@@ -3574,12 +3750,10 @@ fn runOrTest(...@@ -3574,12 +3750,10 @@ fn runOrTest(
3574 child.stdout_behavior = .Inherit;3750 child.stdout_behavior = .Inherit;
3575 child.stderr_behavior = .Inherit;3751 child.stderr_behavior = .Inherit;
35763752
3577 if (!watch) {3753 // Here we release all the locks associated with the Compilation so
3578 // Here we release all the locks associated with the Compilation so3754 // that whatever this child process wants to do won't deadlock.
3579 // that whatever this child process wants to do won't deadlock.3755 comp.destroy();
3580 comp.destroy();3756 comp_destroyed.* = true;
3581 comp_destroyed.* = true;
3582 }
35833757
3584 const term = child.spawnAndWait() catch |err| {3758 const term = child.spawnAndWait() catch |err| {
3585 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);3759 try warnAboutForeignBinaries(arena, arg_mode, target_info, link_libc);
...@@ -3591,19 +3765,13 @@ fn runOrTest(...@@ -3591,19 +3765,13 @@ fn runOrTest(
3591 switch (term) {3765 switch (term) {
3592 .Exited => |code| {3766 .Exited => |code| {
3593 if (code == 0) {3767 if (code == 0) {
3594 if (!watch) return cleanExit();3768 return cleanExit();
3595 } else if (watch) {
3596 warn("process exited with code {d}", .{code});
3597 } else {3769 } else {
3598 process.exit(code);3770 process.exit(code);
3599 }3771 }
3600 },3772 },
3601 else => {3773 else => {
3602 if (watch) {3774 process.exit(1);
3603 warn("process aborted abnormally", .{});
3604 } else {
3605 process.exit(1);
3606 }
3607 },3775 },
3608 }3776 }
3609 },3777 },
...@@ -3611,7 +3779,7 @@ fn runOrTest(...@@ -3611,7 +3779,7 @@ fn runOrTest(
3611 switch (term) {3779 switch (term) {
3612 .Exited => |code| {3780 .Exited => |code| {
3613 if (code == 0) {3781 if (code == 0) {
3614 if (!watch) return cleanExit();3782 return cleanExit();
3615 } else {3783 } else {
3616 const cmd = try std.mem.join(arena, " ", argv.items);3784 const cmd = try std.mem.join(arena, " ", argv.items);
3617 fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd });3785 fatal("the following test command failed with exit code {d}:\n{s}", .{ code, cmd });
...@@ -3631,6 +3799,62 @@ fn runOrTest(...@@ -3631,6 +3799,62 @@ fn runOrTest(
3631 }3799 }
3632}3800}
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
3634const AfterUpdateHook = union(enum) {3858const AfterUpdateHook = union(enum) {
3635 none,3859 none,
3636 print_emit_bin_dir_path,3860 print_emit_bin_dir_path,
...@@ -3638,24 +3862,30 @@ const AfterUpdateHook = union(enum) {...@@ -3638,24 +3862,30 @@ const AfterUpdateHook = union(enum) {
3638};3862};
36393863
3640fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void {3864fn 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
3643 var errors = try comp.getAllErrorsAlloc();3884 var errors = try comp.getAllErrorsAlloc();
3644 defer errors.deinit(comp.gpa);3885 defer errors.deinit(comp.gpa);
36453886
3646 if (errors.list.len != 0) {3887 if (errors.errorMessageCount() > 0) {
3647 const ttyconf: std.debug.TTY.Config = switch (comp.color) {3888 errors.renderToStdErr(renderOptions(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 }
3659 return error.SemanticAnalyzeFail;3889 return error.SemanticAnalyzeFail;
3660 } else switch (hook) {3890 } else switch (hook) {
3661 .none => {},3891 .none => {},
...@@ -3697,7 +3927,12 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void...@@ -3697,7 +3927,12 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void
3697 }3927 }
3698}3928}
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 {
3701 if (!build_options.have_llvm)3936 if (!build_options.have_llvm)
3702 fatal("cannot translate-c: compiler built without LLVM extensions", .{});3937 fatal("cannot translate-c: compiler built without LLVM extensions", .{});
37033938
...@@ -3708,14 +3943,16 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void...@@ -3708,14 +3943,16 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void
37083943
3709 var man: Cache.Manifest = comp.obtainCObjectCacheManifest();3944 var man: Cache.Manifest = comp.obtainCObjectCacheManifest();
3710 man.want_shared_lock = false;3945 man.want_shared_lock = false;
3711 defer if (enable_cache) man.deinit();3946 defer man.deinit();
37123947
3713 man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects3948 man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects
3714 Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err| {3949 Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err| {
3715 fatal("unable to process '{s}': {s}", .{ c_source_file.src_path, @errorName(err) });3950 fatal("unable to process '{s}': {s}", .{ c_source_file.src_path, @errorName(err) });
3716 };3951 };
37173952
3953 if (fancy_output) |p| p.cache_hit = true;
3718 const digest = if (try man.hit()) man.final() else digest: {3954 const digest = if (try man.hit()) man.final() else digest: {
3955 if (fancy_output) |p| p.cache_hit = false;
3719 var argv = std.ArrayList([]const u8).init(arena);3956 var argv = std.ArrayList([]const u8).init(arena);
3720 try argv.append(""); // argv[0] is program name, actual args start at [1]3957 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...@@ -3766,6 +4003,7 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void
3766 error.OutOfMemory => return error.OutOfMemory,4003 error.OutOfMemory => return error.OutOfMemory,
3767 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", .{}),4004 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", .{}),
3768 error.SemanticAnalyzeFail => {4005 error.SemanticAnalyzeFail => {
4006 // TODO convert these to zig errors
3769 for (clang_errors) |clang_err| {4007 for (clang_errors) |clang_err| {
3770 std.debug.print("{s}:{d}:{d}: {s}\n", .{4008 std.debug.print("{s}:{d}:{d}: {s}\n", .{
3771 if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)",4009 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...@@ -3810,12 +4048,11 @@ fn cmdTranslateC(comp: *Compilation, arena: Allocator, enable_cache: bool) !void
3810 break :digest digest;4048 break :digest digest;
3811 };4049 };
38124050
3813 if (enable_cache) {4051 if (fancy_output) |p| {
3814 const full_zig_path = try comp.local_cache_directory.join(arena, &[_][]const u8{4052 const full_zig_path = try comp.local_cache_directory.join(arena, &[_][]const u8{
3815 "o", &digest, translated_zig_basename,4053 "o", &digest, translated_zig_basename,
3816 });4054 });
3817 try io.getStdOut().writer().print("{s}\n", .{full_zig_path});4055 p.path = full_zig_path;
3818 return cleanExit();
3819 } else {4056 } else {
3820 const out_zig_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename });4057 const out_zig_path = try fs.path.join(arena, &[_][]const u8{ "o", &digest, translated_zig_basename });
3821 const zig_file = comp.local_cache_directory.handle.openFile(out_zig_path, .{}) catch |err| {4058 const zig_file = comp.local_cache_directory.handle.openFile(out_zig_path, .{}) catch |err| {
...@@ -4009,6 +4246,8 @@ pub const usage_build =...@@ -4009,6 +4246,8 @@ pub const usage_build =
4009 \\Options:4246 \\Options:
4010 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error4247 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
4011 \\ -fno-reference-trace Disable reference trace4248 \\ -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
4012 \\ --build-file [file] Override path to build.zig4251 \\ --build-file [file] Override path to build.zig
4013 \\ --cache-dir [path] Override path to local Zig cache directory4252 \\ --cache-dir [path] Override path to local Zig cache directory
4014 \\ --global-cache-dir [path] Override path to global Zig cache directory4253 \\ --global-cache-dir [path] Override path to global Zig cache directory
...@@ -4021,7 +4260,6 @@ pub const usage_build =...@@ -4021,7 +4260,6 @@ pub const usage_build =
40214260
4022pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {4261pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4023 var color: Color = .auto;4262 var color: Color = .auto;
4024 var prominent_compile_errors: bool = false;
40254263
4026 // We want to release all the locks before executing the child process, so we make a nice4264 // We want to release all the locks before executing the child process, so we make a nice
4027 // big block here to ensure the cleanup gets run when we extract out our argv.4265 // 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...@@ -4082,8 +4320,6 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
4082 i += 1;4320 i += 1;
4083 override_global_cache_dir = args[i];4321 override_global_cache_dir = args[i];
4084 continue;4322 continue;
4085 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
4086 prominent_compile_errors = true;
4087 } else if (mem.eql(u8, arg, "-freference-trace")) {4323 } else if (mem.eql(u8, arg, "-freference-trace")) {
4088 try child_argv.append(arg);4324 try child_argv.append(arg);
4089 reference_trace = 256;4325 reference_trace = 256;
...@@ -4201,7 +4437,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -4201,7 +4437,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
4201 .basename = exe_basename,4437 .basename = exe_basename,
4202 };4438 };
4203 var thread_pool: ThreadPool = undefined;4439 var thread_pool: ThreadPool = undefined;
4204 try thread_pool.init(gpa);4440 try thread_pool.init(.{ .allocator = gpa });
4205 defer thread_pool.deinit();4441 defer thread_pool.deinit();
42064442
4207 var cleanup_build_runner_dir: ?fs.Dir = null;4443 var cleanup_build_runner_dir: ?fs.Dir = null;
...@@ -4251,9 +4487,13 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -4251,9 +4487,13 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
4251 var all_modules: Package.AllModules = .{};4487 var all_modules: Package.AllModules = .{};
4252 defer all_modules.deinit(gpa);4488 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
4254 // Here we borrow main package's table and will replace it with a fresh4494 // Here we borrow main package's table and will replace it with a fresh
4255 // one after this process completes.4495 // one after this process completes.
4256 build_pkg.fetchAndAddDependencies(4496 const fetch_result = build_pkg.fetchAndAddDependencies(
4257 &main_pkg,4497 &main_pkg,
4258 arena,4498 arena,
4259 &thread_pool,4499 &thread_pool,
...@@ -4264,12 +4504,16 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -4264,12 +4504,16 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
4264 &dependencies_source,4504 &dependencies_source,
4265 &build_roots_source,4505 &build_roots_source,
4266 "",4506 "",
4267 color,4507 &wip_errors,
4268 &all_modules,4508 &all_modules,
4269 ) catch |err| switch (err) {4509 );
4270 error.PackageFetchFailed => process.exit(1),4510 if (wip_errors.root_list.items.len > 0) {
4271 else => |e| return e,4511 var errors = try wip_errors.toOwnedBundle("");
4272 };4512 defer errors.deinit(gpa);
4513 errors.renderToStdErr(renderOptions(color));
4514 process.exit(1);
4515 }
4516 try fetch_result;
42734517
4274 try dependencies_source.appendSlice("};\npub const build_root = struct {\n");4518 try dependencies_source.appendSlice("};\npub const build_root = struct {\n");
4275 try dependencies_source.appendSlice(build_roots_source.items);4519 try dependencies_source.appendSlice(build_roots_source.items);
...@@ -4312,7 +4556,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -4312,7 +4556,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
4312 defer comp.destroy();4556 defer comp.destroy();
43134557
4314 updateModule(gpa, comp, .none) catch |err| switch (err) {4558 updateModule(gpa, comp, .none) catch |err| switch (err) {
4315 error.SemanticAnalyzeFail => process.exit(1),4559 error.SemanticAnalyzeFail => process.exit(2),
4316 else => |e| return e,4560 else => |e| return e,
4317 };4561 };
4318 try comp.makeBinFileExecutable();4562 try comp.makeBinFileExecutable();
...@@ -4336,13 +4580,13 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi...@@ -4336,13 +4580,13 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
4336 switch (term) {4580 switch (term) {
4337 .Exited => |code| {4581 .Exited => |code| {
4338 if (code == 0) return cleanExit();4582 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) {4588 const cmd = try std.mem.join(arena, " ", child_argv);
4341 fatal("the build command failed with exit code {d}", .{code});4589 fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd });
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 }
4346 },4590 },
4347 else => {4591 else => {
4348 const cmd = try std.mem.join(arena, " ", child_argv);4592 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...@@ -4356,7 +4600,7 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
4356}4600}
43574601
4358fn readSourceFileToEndAlloc(4602fn readSourceFileToEndAlloc(
4359 allocator: mem.Allocator,4603 allocator: Allocator,
4360 input: *const fs.File,4604 input: *const fs.File,
4361 size_hint: ?usize,4605 size_hint: ?usize,
4362) ![:0]u8 {4606) ![:0]u8 {
...@@ -4500,12 +4744,7 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void...@@ -4500,12 +4744,7 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
4500 };4744 };
4501 defer tree.deinit(gpa);4745 defer tree.deinit(gpa);
45024746
4503 try printErrsMsgToStdErr(gpa, arena, tree, "<stdin>", color);
4504 var has_ast_error = false;
4505 if (check_ast_flag) {4747 if (check_ast_flag) {
4506 const Module = @import("Module.zig");
4507 const AstGen = @import("AstGen.zig");
4508
4509 var file: Module.File = .{4748 var file: Module.File = .{
4510 .status = .never_loaded,4749 .status = .never_loaded,
4511 .source_loaded = true,4750 .source_loaded = true,
...@@ -4528,25 +4767,18 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void...@@ -4528,25 +4767,18 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void
4528 defer file.zir.deinit(gpa);4767 defer file.zir.deinit(gpa);
45294768
4530 if (file.zir.hasCompileErrors()) {4769 if (file.zir.hasCompileErrors()) {
4531 var arena_instance = std.heap.ArenaAllocator.init(gpa);4770 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
4532 defer arena_instance.deinit();4771 try wip_errors.init(gpa);
4533 var errors = std.ArrayList(Compilation.AllErrors.Message).init(gpa);4772 defer wip_errors.deinit();
4534 defer errors.deinit();4773 try Compilation.addZirErrorMessages(&wip_errors, &file);
45354774 var error_bundle = try wip_errors.toOwnedBundle("");
4536 try Compilation.AllErrors.addZir(arena_instance.allocator(), &errors, &file);4775 defer error_bundle.deinit(gpa);
4537 const ttyconf: std.debug.TTY.Config = switch (color) {4776 error_bundle.renderToStdErr(renderOptions(color));
4538 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),4777 process.exit(2);
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;
4546 }4778 }
4547 }4779 } else if (tree.errors.len != 0) {
4548 if (tree.errors.len != 0 or has_ast_error) {4780 try printAstErrorsToStderr(gpa, tree, "<stdin>", color);
4549 process.exit(1);4781 process.exit(2);
4550 }4782 }
4551 const formatted = try tree.render(gpa);4783 const formatted = try tree.render(gpa);
4552 defer gpa.free(formatted);4784 defer gpa.free(formatted);
...@@ -4688,12 +4920,13 @@ fn fmtPathFile(...@@ -4688,12 +4920,13 @@ fn fmtPathFile(
4688 if (stat.kind == .Directory)4920 if (stat.kind == .Directory)
4689 return error.IsDir;4921 return error.IsDir;
46904922
4923 const gpa = fmt.gpa;
4691 const source_code = try readSourceFileToEndAlloc(4924 const source_code = try readSourceFileToEndAlloc(
4692 fmt.gpa,4925 gpa,
4693 &source_file,4926 &source_file,
4694 std.math.cast(usize, stat.size) orelse return error.FileTooBig,4927 std.math.cast(usize, stat.size) orelse return error.FileTooBig,
4695 );4928 );
4696 defer fmt.gpa.free(source_code);4929 defer gpa.free(source_code);
46974930
4698 source_file.close();4931 source_file.close();
4699 file_closed = true;4932 file_closed = true;
...@@ -4701,19 +4934,16 @@ fn fmtPathFile(...@@ -4701,19 +4934,16 @@ fn fmtPathFile(
4701 // Add to set after no longer possible to get error.IsDir.4934 // Add to set after no longer possible to get error.IsDir.
4702 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;4935 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
47034936
4704 var tree = try Ast.parse(fmt.gpa, source_code, .zig);4937 var tree = try Ast.parse(gpa, source_code, .zig);
4705 defer tree.deinit(fmt.gpa);4938 defer tree.deinit(gpa);
47064939
4707 try printErrsMsgToStdErr(fmt.gpa, fmt.arena, tree, file_path, fmt.color);
4708 if (tree.errors.len != 0) {4940 if (tree.errors.len != 0) {
4941 try printAstErrorsToStderr(gpa, tree, file_path, fmt.color);
4709 fmt.any_error = true;4942 fmt.any_error = true;
4710 return;4943 return;
4711 }4944 }
47124945
4713 if (fmt.check_ast) {4946 if (fmt.check_ast) {
4714 const Module = @import("Module.zig");
4715 const AstGen = @import("AstGen.zig");
4716
4717 var file: Module.File = .{4947 var file: Module.File = .{
4718 .status = .never_loaded,4948 .status = .never_loaded,
4719 .source_loaded = true,4949 .source_loaded = true,
...@@ -4732,31 +4962,24 @@ fn fmtPathFile(...@@ -4732,31 +4962,24 @@ fn fmtPathFile(
4732 .root_decl = .none,4962 .root_decl = .none,
4733 };4963 };
47344964
4735 file.pkg = try Package.create(fmt.gpa, null, file.sub_file_path);4965 file.pkg = try Package.create(gpa, null, file.sub_file_path);
4736 defer file.pkg.destroy(fmt.gpa);4966 defer file.pkg.destroy(gpa);
47374967
4738 if (stat.size > max_src_size)4968 if (stat.size > max_src_size)
4739 return error.FileTooBig;4969 return error.FileTooBig;
47404970
4741 file.zir = try AstGen.generate(fmt.gpa, file.tree);4971 file.zir = try AstGen.generate(gpa, file.tree);
4742 file.zir_loaded = true;4972 file.zir_loaded = true;
4743 defer file.zir.deinit(fmt.gpa);4973 defer file.zir.deinit(gpa);
47444974
4745 if (file.zir.hasCompileErrors()) {4975 if (file.zir.hasCompileErrors()) {
4746 var arena_instance = std.heap.ArenaAllocator.init(fmt.gpa);4976 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
4747 defer arena_instance.deinit();4977 try wip_errors.init(gpa);
4748 var errors = std.ArrayList(Compilation.AllErrors.Message).init(fmt.gpa);4978 defer wip_errors.deinit();
4749 defer errors.deinit();4979 try Compilation.addZirErrorMessages(&wip_errors, &file);
47504980 var error_bundle = try wip_errors.toOwnedBundle("");
4751 try Compilation.AllErrors.addZir(arena_instance.allocator(), &errors, &file);4981 defer error_bundle.deinit(gpa);
4752 const ttyconf: std.debug.TTY.Config = switch (fmt.color) {4982 error_bundle.renderToStdErr(renderOptions(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 }
4760 fmt.any_error = true;4983 fmt.any_error = true;
4761 }4984 }
4762 }4985 }
...@@ -4784,100 +5007,50 @@ fn fmtPathFile(...@@ -4784,100 +5007,50 @@ fn fmtPathFile(
4784 }5007 }
4785}5008}
47865009
4787pub fn printErrsMsgToStdErr(5010fn printAstErrorsToStderr(gpa: Allocator, tree: Ast, path: []const u8, color: Color) !void {
4788 gpa: mem.Allocator,5011 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
4789 arena: mem.Allocator,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,
4790 tree: Ast,5024 tree: Ast,
4791 path: []const u8,5025 path: []const u8,
4792 color: Color,5026 wip_errors: *std.zig.ErrorBundle.Wip,
4793) !void {5027) !void {
4794 const parse_errors: []const Ast.Error = tree.errors;5028 var file: Module.File = .{
4795 var i: usize = 0;5029 .status = .never_loaded,
4796 while (i < parse_errors.len) : (i += 1) {5030 .source_loaded = true,
4797 const parse_error = parse_errors[i];5031 .zir_loaded = false,
4798 const lok_token = parse_error.token;5032 .sub_file_path = path,
4799 const token_tags = tree.tokens.items(.tag);5033 .source = tree.source,
4800 const start_loc = tree.tokenLocation(0, lok_token);5034 .stat = .{
4801 const source_line = tree.source[start_loc.line_start..start_loc.line_end];5035 .size = 0,
48025036 .inode = 0,
4803 var text_buf = std.ArrayList(u8).init(gpa);5037 .mtime = 0,
4804 defer text_buf.deinit();5038 },
4805 const writer = text_buf.writer();5039 .tree = tree,
4806 try tree.renderError(parse_error, writer);5040 .tree_loaded = true,
4807 const text = try arena.dupe(u8, text_buf.items);5041 .zir = undefined,
48085042 .pkg = undefined,
4809 var notes_buffer: [2]Compilation.AllErrors.Message = undefined;5043 .root_decl = .none,
4810 var notes_len: usize = 0;5044 };
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 }
48545045
4855 const extra_offset = tree.errorOffset(parse_error);5046 file.pkg = try Package.create(gpa, null, path);
4856 const byte_offset = @intCast(u32, start_loc.line_start) + extra_offset;5047 defer file.pkg.destroy(gpa);
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 };
48725048
4873 const ttyconf: std.debug.TTY.Config = switch (color) {5049 file.zir = try AstGen.generate(gpa, file.tree);
4874 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),5050 file.zir_loaded = true;
4875 .on => .escape_codes,5051 defer file.zir.deinit(gpa);
4876 .off => .no_color,
4877 };
48785052
4879 message.renderToStdErr(ttyconf);5053 try Compilation.addZirErrorMessages(wip_errors, &file);
4880 }
4881}5054}
48825055
4883pub const info_zen =5056pub const info_zen =
...@@ -5325,19 +5498,6 @@ fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.Nat...@@ -5325,19 +5498,6 @@ fn detectNativeTargetInfo(cross_target: std.zig.CrossTarget) !std.zig.system.Nat
5325 return std.zig.system.NativeTargetInfo.detect(cross_target);5498 return std.zig.system.NativeTargetInfo.detect(cross_target);
5326}5499}
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
5341const usage_ast_check =5501const usage_ast_check =
5342 \\Usage: zig ast-check [file]5502 \\Usage: zig ast-check [file]
5343 \\5503 \\
...@@ -5360,8 +5520,6 @@ pub fn cmdAstCheck(...@@ -5360,8 +5520,6 @@ pub fn cmdAstCheck(
5360 arena: Allocator,5520 arena: Allocator,
5361 args: []const []const u8,5521 args: []const []const u8,
5362) !void {5522) !void {
5363 const Module = @import("Module.zig");
5364 const AstGen = @import("AstGen.zig");
5365 const Zir = @import("Zir.zig");5523 const Zir = @import("Zir.zig");
53665524
5367 var color: Color = .auto;5525 var color: Color = .auto;
...@@ -5451,26 +5609,18 @@ pub fn cmdAstCheck(...@@ -5451,26 +5609,18 @@ pub fn cmdAstCheck(
5451 file.tree_loaded = true;5609 file.tree_loaded = true;
5452 defer file.tree.deinit(gpa);5610 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
5459 file.zir = try AstGen.generate(gpa, file.tree);5612 file.zir = try AstGen.generate(gpa, file.tree);
5460 file.zir_loaded = true;5613 file.zir_loaded = true;
5461 defer file.zir.deinit(gpa);5614 defer file.zir.deinit(gpa);
54625615
5463 if (file.zir.hasCompileErrors()) {5616 if (file.zir.hasCompileErrors()) {
5464 var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);5617 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5465 try Compilation.AllErrors.addZir(arena, &errors, &file);5618 try wip_errors.init(gpa);
5466 const ttyconf: std.debug.TTY.Config = switch (color) {5619 defer wip_errors.deinit();
5467 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),5620 try Compilation.addZirErrorMessages(&wip_errors, &file);
5468 .on => .escape_codes,5621 var error_bundle = try wip_errors.toOwnedBundle("");
5469 .off => .no_color,5622 defer error_bundle.deinit(gpa);
5470 };5623 error_bundle.renderToStdErr(renderOptions(color));
5471 for (errors.items) |full_err_msg| {
5472 full_err_msg.renderToStdErr(ttyconf);
5473 }
5474 process.exit(1);5624 process.exit(1);
5475 }5625 }
54765626
...@@ -5528,8 +5678,7 @@ pub fn cmdChangelist(...@@ -5528,8 +5678,7 @@ pub fn cmdChangelist(
5528 arena: Allocator,5678 arena: Allocator,
5529 args: []const []const u8,5679 args: []const []const u8,
5530) !void {5680) !void {
5531 const Module = @import("Module.zig");5681 const color: Color = .auto;
5532 const AstGen = @import("AstGen.zig");
5533 const Zir = @import("Zir.zig");5682 const Zir = @import("Zir.zig");
55345683
5535 const old_source_file = args[0];5684 const old_source_file = args[0];
...@@ -5577,22 +5726,18 @@ pub fn cmdChangelist(...@@ -5577,22 +5726,18 @@ pub fn cmdChangelist(
5577 file.tree_loaded = true;5726 file.tree_loaded = true;
5578 defer file.tree.deinit(gpa);5727 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
5585 file.zir = try AstGen.generate(gpa, file.tree);5729 file.zir = try AstGen.generate(gpa, file.tree);
5586 file.zir_loaded = true;5730 file.zir_loaded = true;
5587 defer file.zir.deinit(gpa);5731 defer file.zir.deinit(gpa);
55885732
5589 if (file.zir.hasCompileErrors()) {5733 if (file.zir.hasCompileErrors()) {
5590 var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);5734 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5591 try Compilation.AllErrors.addZir(arena, &errors, &file);5735 try wip_errors.init(gpa);
5592 const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr());5736 defer wip_errors.deinit();
5593 for (errors.items) |full_err_msg| {5737 try Compilation.addZirErrorMessages(&wip_errors, &file);
5594 full_err_msg.renderToStdErr(ttyconf);5738 var error_bundle = try wip_errors.toOwnedBundle("");
5595 }5739 defer error_bundle.deinit(gpa);
5740 error_bundle.renderToStdErr(renderOptions(color));
5596 process.exit(1);5741 process.exit(1);
5597 }5742 }
55985743
...@@ -5614,11 +5759,6 @@ pub fn cmdChangelist(...@@ -5614,11 +5759,6 @@ pub fn cmdChangelist(
5614 var new_tree = try Ast.parse(gpa, new_source, .zig);5759 var new_tree = try Ast.parse(gpa, new_source, .zig);
5615 defer new_tree.deinit(gpa);5760 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
5622 var old_zir = file.zir;5762 var old_zir = file.zir;
5623 defer old_zir.deinit(gpa);5763 defer old_zir.deinit(gpa);
5624 file.zir_loaded = false;5764 file.zir_loaded = false;
...@@ -5626,12 +5766,13 @@ pub fn cmdChangelist(...@@ -5626,12 +5766,13 @@ pub fn cmdChangelist(
5626 file.zir_loaded = true;5766 file.zir_loaded = true;
56275767
5628 if (file.zir.hasCompileErrors()) {5768 if (file.zir.hasCompileErrors()) {
5629 var errors = std.ArrayList(Compilation.AllErrors.Message).init(arena);5769 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
5630 try Compilation.AllErrors.addZir(arena, &errors, &file);5770 try wip_errors.init(gpa);
5631 const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr());5771 defer wip_errors.deinit();
5632 for (errors.items) |full_err_msg| {5772 try Compilation.addZirErrorMessages(&wip_errors, &file);
5633 full_err_msg.renderToStdErr(ttyconf);5773 var error_bundle = try wip_errors.toOwnedBundle("");
5634 }5774 defer error_bundle.deinit(gpa);
5775 error_bundle.renderToStdErr(renderOptions(color));
5635 process.exit(1);5776 process.exit(1);
5636 }5777 }
56375778
...@@ -5892,3 +6033,20 @@ const ClangSearchSanitizer = struct {...@@ -5892,3 +6033,20 @@ const ClangSearchSanitizer = struct {
5892 iframework: bool = false,6033 iframework: bool = false,
5893 };6034 };
5894};6035};
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 {...@@ -19,7 +19,7 @@ pub const CRTFile = enum {
19 uuid_lib,19 uuid_lib,
20};20};
2121
22pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {22pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
23 if (!build_options.have_llvm) {23 if (!build_options.have_llvm) {
24 return error.ZigCompilerNotBuiltWithLLVMExtensions;24 return error.ZigCompilerNotBuiltWithLLVMExtensions;
25 }25 }
...@@ -41,7 +41,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -41,7 +41,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
41 //"-D_UNICODE",41 //"-D_UNICODE",
42 //"-DWPRFLAG=1",42 //"-DWPRFLAG=1",
43 });43 });
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, &.{
45 .{45 .{
46 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{46 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
47 "libc", "mingw", "crt", "crtexe.c",47 "libc", "mingw", "crt", "crtexe.c",
...@@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
60 "-U__CRTDLL__",60 "-U__CRTDLL__",
61 "-D__MSVCRT__",61 "-D__MSVCRT__",
62 });62 });
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, &.{
64 .{64 .{
65 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{65 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
66 "libc", "mingw", "crt", "crtdll.c",66 "libc", "mingw", "crt", "crtdll.c",
...@@ -100,7 +100,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -100,7 +100,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
100 .extra_flags = args.items,100 .extra_flags = args.items,
101 };101 };
102 }102 }
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);
104 },104 },
105105
106 .msvcrt_os_lib => {106 .msvcrt_os_lib => {
...@@ -148,7 +148,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -148,7 +148,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
148 };148 };
149 }149 }
150 }150 }
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);
152 },152 },
153153
154 .mingwex_lib => {154 .mingwex_lib => {
...@@ -211,7 +211,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -211,7 +211,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
211 } else {211 } else {
212 @panic("unsupported arch");212 @panic("unsupported arch");
213 }213 }
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);
215 },215 },
216216
217 .uuid_lib => {217 .uuid_lib => {
...@@ -244,7 +244,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -244,7 +244,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
244 .extra_flags = extra_flags,244 .extra_flags = extra_flags,
245 };245 };
246 }246 }
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);
248 },248 },
249 }249 }
250}250}
src/musl.zig+8-8
...@@ -17,7 +17,7 @@ pub const CRTFile = enum {...@@ -17,7 +17,7 @@ pub const CRTFile = enum {
17 libc_so,17 libc_so,
18};18};
1919
20pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {20pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
21 if (!build_options.have_llvm) {21 if (!build_options.have_llvm) {
22 return error.ZigCompilerNotBuiltWithLLVMExtensions;22 return error.ZigCompilerNotBuiltWithLLVMExtensions;
23 }23 }
...@@ -33,7 +33,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -33,7 +33,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
33 try args.appendSlice(&[_][]const u8{33 try args.appendSlice(&[_][]const u8{
34 "-Qunused-arguments",34 "-Qunused-arguments",
35 });35 });
36 return comp.build_crt_file("crti", .Obj, &[1]Compilation.CSourceFile{36 return comp.build_crt_file("crti", .Obj, .@"musl crti.o", prog_node, &.{
37 .{37 .{
38 .src_path = try start_asm_path(comp, arena, "crti.s"),38 .src_path = try start_asm_path(comp, arena, "crti.s"),
39 .extra_flags = args.items,39 .extra_flags = args.items,
...@@ -46,7 +46,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -46,7 +46,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
46 try args.appendSlice(&[_][]const u8{46 try args.appendSlice(&[_][]const u8{
47 "-Qunused-arguments",47 "-Qunused-arguments",
48 });48 });
49 return comp.build_crt_file("crtn", .Obj, &[1]Compilation.CSourceFile{49 return comp.build_crt_file("crtn", .Obj, .@"musl crtn.o", prog_node, &.{
50 .{50 .{
51 .src_path = try start_asm_path(comp, arena, "crtn.s"),51 .src_path = try start_asm_path(comp, arena, "crtn.s"),
52 .extra_flags = args.items,52 .extra_flags = args.items,
...@@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -60,7 +60,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
60 "-fno-stack-protector",60 "-fno-stack-protector",
61 "-DCRT",61 "-DCRT",
62 });62 });
63 return comp.build_crt_file("crt1", .Obj, &[1]Compilation.CSourceFile{63 return comp.build_crt_file("crt1", .Obj, .@"musl crt1.o", prog_node, &.{
64 .{64 .{
65 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{65 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
66 "libc", "musl", "crt", "crt1.c",66 "libc", "musl", "crt", "crt1.c",
...@@ -77,7 +77,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -77,7 +77,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
77 "-fno-stack-protector",77 "-fno-stack-protector",
78 "-DCRT",78 "-DCRT",
79 });79 });
80 return comp.build_crt_file("rcrt1", .Obj, &[1]Compilation.CSourceFile{80 return comp.build_crt_file("rcrt1", .Obj, .@"musl rcrt1.o", prog_node, &.{
81 .{81 .{
82 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{82 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
83 "libc", "musl", "crt", "rcrt1.c",83 "libc", "musl", "crt", "rcrt1.c",
...@@ -94,7 +94,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -94,7 +94,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
94 "-fno-stack-protector",94 "-fno-stack-protector",
95 "-DCRT",95 "-DCRT",
96 });96 });
97 return comp.build_crt_file("Scrt1", .Obj, &[1]Compilation.CSourceFile{97 return comp.build_crt_file("Scrt1", .Obj, .@"musl Scrt1.o", prog_node, &.{
98 .{98 .{
99 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{99 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
100 "libc", "musl", "crt", "Scrt1.c",100 "libc", "musl", "crt", "Scrt1.c",
...@@ -187,7 +187,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -187,7 +187,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
187 .extra_flags = args.items,187 .extra_flags = args.items,
188 };188 };
189 }189 }
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);
191 },191 },
192 .libc_so => {192 .libc_so => {
193 const target = comp.getTarget();193 const target = comp.getTarget();
...@@ -241,7 +241,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -241,7 +241,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
241 });241 });
242 defer sub_compilation.destroy();242 defer sub_compilation.destroy();
243243
244 try sub_compilation.updateSubCompilation();244 try comp.updateSubCompilation(sub_compilation, .@"musl libc.so", prog_node);
245245
246 try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1);246 try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1);
247247
src/objcopy.zig+45-5
...@@ -4,22 +4,25 @@ const fs = std.fs;...@@ -4,22 +4,25 @@ const fs = std.fs;
4const elf = std.elf;4const elf = std.elf;
5const Allocator = std.mem.Allocator;5const Allocator = std.mem.Allocator;
6const File = std.fs.File;6const File = std.fs.File;
7const assert = std.debug.assert;
8
7const main = @import("main.zig");9const main = @import("main.zig");
8const fatal = main.fatal;10const fatal = main.fatal;
9const cleanExit = main.cleanExit;11const Server = std.zig.Server;
12const build_options = @import("build_options");
1013
11pub fn cmdObjCopy(14pub fn cmdObjCopy(
12 gpa: Allocator,15 gpa: Allocator,
13 arena: Allocator,16 arena: Allocator,
14 args: []const []const u8,17 args: []const []const u8,
15) !void {18) !void {
16 _ = gpa;
17 var i: usize = 0;19 var i: usize = 0;
18 var opt_out_fmt: ?std.Target.ObjectFormat = null;20 var opt_out_fmt: ?std.Target.ObjectFormat = null;
19 var opt_input: ?[]const u8 = null;21 var opt_input: ?[]const u8 = null;
20 var opt_output: ?[]const u8 = null;22 var opt_output: ?[]const u8 = null;
21 var only_section: ?[]const u8 = null;23 var only_section: ?[]const u8 = null;
22 var pad_to: ?u64 = null;24 var pad_to: ?u64 = null;
25 var listen = false;
23 while (i < args.len) : (i += 1) {26 while (i < args.len) : (i += 1) {
24 const arg = args[i];27 const arg = args[i];
25 if (!mem.startsWith(u8, arg, "-")) {28 if (!mem.startsWith(u8, arg, "-")) {
...@@ -54,6 +57,8 @@ pub fn cmdObjCopy(...@@ -54,6 +57,8 @@ pub fn cmdObjCopy(
54 i += 1;57 i += 1;
55 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});58 if (i >= args.len) fatal("expected another argument after '{s}'", .{arg});
56 only_section = args[i];59 only_section = args[i];
60 } else if (mem.eql(u8, arg, "--listen=-")) {
61 listen = true;
57 } else if (mem.startsWith(u8, arg, "--only-section=")) {62 } else if (mem.startsWith(u8, arg, "--only-section=")) {
58 only_section = arg["--output-target=".len..];63 only_section = arg["--output-target=".len..];
59 } else if (mem.eql(u8, arg, "--pad-to")) {64 } else if (mem.eql(u8, arg, "--pad-to")) {
...@@ -102,10 +107,45 @@ pub fn cmdObjCopy(...@@ -102,10 +107,45 @@ pub fn cmdObjCopy(
102 .only_section = only_section,107 .only_section = only_section,
103 .pad_to = pad_to,108 .pad_to = pad_to,
104 });109 });
105 return cleanExit();
106 },110 },
107 else => fatal("unsupported output object format: {s}", .{@tagName(out_fmt)}),111 else => fatal("unsupported output object format: {s}", .{@tagName(out_fmt)}),
108 }112 }
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();
109}149}
110150
111const usage =151const usage =
...@@ -417,7 +457,7 @@ const HexWriter = struct {...@@ -417,7 +457,7 @@ const HexWriter = struct {
417 }457 }
418458
419 fn Address(address: u32) Record {459 fn Address(address: u32) Record {
420 std.debug.assert(address > 0xFFFF);460 assert(address > 0xFFFF);
421 const segment = @intCast(u16, address / 0x10000);461 const segment = @intCast(u16, address / 0x10000);
422 if (address > 0xFFFFF) {462 if (address > 0xFFFFF) {
423 return Record{463 return Record{
...@@ -460,7 +500,7 @@ const HexWriter = struct {...@@ -460,7 +500,7 @@ const HexWriter = struct {
460 const BUFSIZE = 1 + (1 + 2 + 1 + MAX_PAYLOAD_LEN + 1) * 2 + linesep.len;500 const BUFSIZE = 1 + (1 + 2 + 1 + MAX_PAYLOAD_LEN + 1) * 2 + linesep.len;
461 var outbuf: [BUFSIZE]u8 = undefined;501 var outbuf: [BUFSIZE]u8 = undefined;
462 const payload_bytes = self.getPayloadBytes();502 const payload_bytes = self.getPayloadBytes();
463 std.debug.assert(payload_bytes.len <= MAX_PAYLOAD_LEN);503 assert(payload_bytes.len <= MAX_PAYLOAD_LEN);
464504
465 const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3s}{4X:0>2}" ++ linesep, .{505 const line = try std.fmt.bufPrint(&outbuf, ":{0X:0>2}{1X:0>4}{2X:0>2}{3s}{4X:0>2}" ++ linesep, .{
466 @intCast(u8, payload_bytes.len),506 @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...@@ -59,7 +59,7 @@ pub fn execModelCrtFileFullName(wasi_exec_model: std.builtin.WasiExecModel) []co
59 };59 };
60}60}
6161
62pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {62pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progress.Node) !void {
63 if (!build_options.have_llvm) {63 if (!build_options.have_llvm) {
64 return error.ZigCompilerNotBuiltWithLLVMExtensions;64 return error.ZigCompilerNotBuiltWithLLVMExtensions;
65 }65 }
...@@ -74,7 +74,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -74,7 +74,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
74 var args = std.ArrayList([]const u8).init(arena);74 var args = std.ArrayList([]const u8).init(arena);
75 try addCCArgs(comp, arena, &args, false);75 try addCCArgs(comp, arena, &args, false);
76 try addLibcBottomHalfIncludes(comp, arena, &args);76 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, &.{
78 .{78 .{
79 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{79 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
80 "libc", try sanitize(arena, crt1_reactor_src_file),80 "libc", try sanitize(arena, crt1_reactor_src_file),
...@@ -87,7 +87,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -87,7 +87,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
87 var args = std.ArrayList([]const u8).init(arena);87 var args = std.ArrayList([]const u8).init(arena);
88 try addCCArgs(comp, arena, &args, false);88 try addCCArgs(comp, arena, &args, false);
89 try addLibcBottomHalfIncludes(comp, arena, &args);89 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, &.{
91 .{91 .{
92 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{92 .src_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
93 "libc", try sanitize(arena, crt1_command_src_file),93 "libc", try sanitize(arena, crt1_command_src_file),
...@@ -145,7 +145,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -145,7 +145,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
145 }145 }
146 }146 }
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);
149 },149 },
150 .libwasi_emulated_process_clocks_a => {150 .libwasi_emulated_process_clocks_a => {
151 var args = std.ArrayList([]const u8).init(arena);151 var args = std.ArrayList([]const u8).init(arena);
...@@ -161,7 +161,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -161,7 +161,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
161 .extra_flags = args.items,161 .extra_flags = args.items,
162 });162 });
163 }163 }
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);
165 },165 },
166 .libwasi_emulated_getpid_a => {166 .libwasi_emulated_getpid_a => {
167 var args = std.ArrayList([]const u8).init(arena);167 var args = std.ArrayList([]const u8).init(arena);
...@@ -177,7 +177,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -177,7 +177,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
177 .extra_flags = args.items,177 .extra_flags = args.items,
178 });178 });
179 }179 }
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);
181 },181 },
182 .libwasi_emulated_mman_a => {182 .libwasi_emulated_mman_a => {
183 var args = std.ArrayList([]const u8).init(arena);183 var args = std.ArrayList([]const u8).init(arena);
...@@ -193,7 +193,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -193,7 +193,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
193 .extra_flags = args.items,193 .extra_flags = args.items,
194 });194 });
195 }195 }
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);
197 },197 },
198 .libwasi_emulated_signal_a => {198 .libwasi_emulated_signal_a => {
199 var emu_signal_sources = std.ArrayList(Compilation.CSourceFile).init(arena);199 var emu_signal_sources = std.ArrayList(Compilation.CSourceFile).init(arena);
...@@ -228,7 +228,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {...@@ -228,7 +228,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void {
228 }228 }
229 }229 }
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);
232 },232 },
233 }233 }
234}234}
test/behavior/array.zig+1
...@@ -84,6 +84,7 @@ test "array concat with tuple" {...@@ -84,6 +84,7 @@ test "array concat with tuple" {
84}84}
8585
86test "array init with concat" {86test "array init with concat" {
87 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
87 const a = 'a';88 const a = 'a';
88 var i: [4]u8 = [2]u8{ a, 'b' } ++ [2]u8{ 'c', 'd' };89 var i: [4]u8 = [2]u8{ a, 'b' } ++ [2]u8{ 'c', 'd' };
89 try expect(std.mem.eql(u8, &i, "abcd"));90 try expect(std.mem.eql(u8, &i, "abcd"));
test/behavior/ptrcast.zig+2
...@@ -170,6 +170,7 @@ test "lower reinterpreted comptime field ptr" {...@@ -170,6 +170,7 @@ test "lower reinterpreted comptime field ptr" {
170170
171test "reinterpret struct field at comptime" {171test "reinterpret struct field at comptime" {
172 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO172 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
173 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
173174
174 const numNative = comptime Bytes.init(0x12345678);175 const numNative = comptime Bytes.init(0x12345678);
175 if (native_endian != .Little) {176 if (native_endian != .Little) {
...@@ -232,6 +233,7 @@ test "ptrcast of const integer has the correct object size" {...@@ -232,6 +233,7 @@ test "ptrcast of const integer has the correct object size" {
232test "implicit optional pointer to optional anyopaque pointer" {233test "implicit optional pointer to optional anyopaque pointer" {
233 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO234 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
234 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO235 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
236 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
235237
236 var buf: [4]u8 = "aoeu".*;238 var buf: [4]u8 = "aoeu".*;
237 var x: ?[*]u8 = &buf;239 var x: ?[*]u8 = &buf;
test/behavior/slice.zig+1
...@@ -227,6 +227,7 @@ fn sliceFromLenToLen(a_slice: []u8, start: usize, end: usize) []u8 {...@@ -227,6 +227,7 @@ fn sliceFromLenToLen(a_slice: []u8, start: usize, end: usize) []u8 {
227227
228test "C pointer" {228test "C pointer" {
229 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO229 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
230 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
230231
231 var buf: [*c]const u8 = "kjdhfkjdhfdkjhfkfjhdfkjdhfkdjhfdkjhf";232 var buf: [*c]const u8 = "kjdhfkjdhfdkjhfkfjhdfkjdhfkdjhfdkjhf";
232 var len: u32 = 10;233 var len: u32 = 10;
test/cases.zig+5-5
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1const std = @import("std");1const std = @import("std");
2const TestContext = @import("../src/test.zig").TestContext;2const Cases = @import("src/Cases.zig");
33
4pub fn addCases(ctx: *TestContext) !void {4pub fn addCases(cases: *Cases) !void {
5 try @import("compile_errors.zig").addCases(ctx);5 try @import("compile_errors.zig").addCases(cases);
6 try @import("stage2/cbe.zig").addCases(ctx);6 try @import("cbe.zig").addCases(cases);
7 try @import("stage2/nvptx.zig").addCases(ctx);7 try @import("nvptx.zig").addCases(cases);
8}8}
test/cases/compile_errors/access_inactive_union_field_comptime.zig+1
...@@ -21,3 +21,4 @@ pub export fn entry1() void {...@@ -21,3 +21,4 @@ pub export fn entry1() void {
21// :9:15: error: access of union field 'a' while field 'b' is active21// :9:15: error: access of union field 'a' while field 'b' is active
22// :2:21: note: union declared here22// :2:21: note: union declared here
23// :14:16: error: access of union field 'a' while field 'b' is active23// :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",);...@@ -4,4 +4,4 @@ const bogus = @import("bogus-does-not-exist.zig",);
4// backend=stage24// backend=stage2
5// target=native5// target=native
6//6//
7// :1:23: error: unable to load '${DIR}bogus-does-not-exist.zig': FileNotFound7// 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 {...@@ -15,3 +15,7 @@ pub export fn entry() void {
15// target=native15// target=native
16//16//
17// :6:5: error: found compile log statement17// :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 {...@@ -17,3 +17,12 @@ export fn baz() void {
17//17//
18// :5:5: error: found compile log statement18// :5:5: error: found compile log statement
19// :11:5: note: also here19// :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 @@...@@ -1,5 +1,5 @@
1export fn entry() void {1export fn entry() void {
2 @compileLog(@ptrCast(*const anyopaque, &entry));2 @compileLog(@as(*align(1) const anyopaque, @ptrCast(*const anyopaque, &entry)));
3}3}
44
5// error5// error
...@@ -7,3 +7,6 @@ export fn entry() void {...@@ -7,3 +7,6 @@ export fn entry() void {
7// target=native7// target=native
8//8//
9// :2:5: error: found compile log statement9// :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 {...@@ -12,3 +12,6 @@ export fn entry() void {
12// target=native12// target=native
13//13//
14// :2:5: error: found compile log statement14// :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 {...@@ -13,3 +13,8 @@ fn inner(comptime n: usize) void {
13//13//
14// :7:39: error: found compile log statement14// :7:39: error: found compile log statement
15// :7:39: note: also here15// :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 {...@@ -45,4 +45,6 @@ pub export fn entry2() void {
45// :22:13: error: unable to resolve comptime value45// :22:13: error: unable to resolve comptime value
46// :22:13: note: condition in comptime switch must be comptime-known46// :22:13: note: condition in comptime switch must be comptime-known
47// :21:17: note: expression is evaluated at comptime because the function returns a comptime-only type 'tmp.S'47// :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
48// :32:19: note: called from here50// :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 {...@@ -32,6 +32,7 @@ export fn d() void {
32// :3:8: error: opaque types have unknown size and therefore cannot be directly embedded in structs32// :3:8: error: opaque types have unknown size and therefore cannot be directly embedded in structs
33// :1:11: note: opaque declared here33// :1:11: note: opaque declared here
34// :7:10: error: opaque types have unknown size and therefore cannot be directly embedded in unions34// :7:10: error: opaque types have unknown size and therefore cannot be directly embedded in unions
35// :1:11: note: opaque declared here
35// :19:18: error: opaque types have unknown size and therefore cannot be directly embedded in structs36// :19:18: error: opaque types have unknown size and therefore cannot be directly embedded in structs
36// :18:22: note: opaque declared here37// :18:22: note: opaque declared here
37// :24:23: error: opaque types have unknown size and therefore cannot be directly embedded in structs38// :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; }...@@ -12,6 +12,6 @@ comptime { _ = entry2; }
12// backend=stage212// backend=stage2
13// target=native13// target=native
14//14//
15// :1:15: error: comptime parameters not allowed in function with calling convention 'C'
16// :5:30: error: comptime parameters not allowed in function with calling convention 'C'15// :5:30: error: comptime parameters not allowed in function with calling convention 'C'
17// :6:30: error: generic parameters not allowed in function with calling convention 'C'16// :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 {...@@ -27,4 +27,5 @@ export fn entry4() void {
27// :1:17: note: opaque declared here27// :1:17: note: opaque declared here
28// :8:28: error: parameter of type '@TypeOf(null)' not allowed28// :8:28: error: parameter of type '@TypeOf(null)' not allowed
29// :12:8: error: parameter of opaque type 'tmp.FooType' not allowed29// :12:8: error: parameter of opaque type 'tmp.FooType' not allowed
30// :1:17: note: opaque declared here
30// :17:8: error: parameter of type '@TypeOf(null)' not allowed31// :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 {...@@ -24,9 +24,9 @@ export fn quux() u32 {
24// :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set'24// :8:5: error: expected type 'void', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set'
25// :7:17: note: function cannot return an error25// :7:17: note: function cannot return an error
26// :11:15: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32'26// :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
28// :11:15: note: cannot convert error union to payload type27// :11:15: note: cannot convert error union to payload type
29// :11:15: note: consider using 'try', 'catch', or 'if'28// :11:15: note: consider using 'try', 'catch', or 'if'
29// :10:17: note: function cannot return an error
30// :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32'30// :15:14: error: expected type 'u32', found '@typeInfo(@typeInfo(@TypeOf(tmp.bar)).Fn.return_type.?).ErrorUnion.error_set!u32'
31// :15:14: note: cannot convert error union to payload type31// :15:14: note: cannot convert error union to payload type
32// :15:14: note: consider using 'try', 'catch', or 'if'32// :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 {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = {}4 _ = {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-block_statement.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 ({})4 ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-comptime_expression.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = comptime {}4 _ = comptime {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-comptime_statement.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 comptime ({})4 comptime ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-defer.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 defer ({})4 defer ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-for_expression.zig+3
...@@ -3,7 +3,10 @@ export fn entry() void {...@@ -3,7 +3,10 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = for(foo()) |_| {}4 _ = for(foo()) |_| {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
9fn foo() void {}
710
8// error11// error
9// backend=stage212// backend=stage2
test/cases/compile_errors/implicit_semicolon-for_statement.zig+3
...@@ -3,7 +3,10 @@ export fn entry() void {...@@ -3,7 +3,10 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 for(foo()) |_| ({})4 for(foo()) |_| ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
9fn foo() void {}
710
8// error11// error
9// backend=stage212// backend=stage2
test/cases/compile_errors/implicit_semicolon-if-else-if-else_expression.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = if(true) {} else if(true) {} else {}4 _ = if(true) {} else if(true) {} else {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-if-else-if-else_statement.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 if(true) ({}) else if(true) ({}) else ({})4 if(true) ({}) else if(true) ({}) else ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-if-else-if_expression.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = if(true) {} else if(true) {}4 _ = if(true) {} else if(true) {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-if-else-if_statement.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 if(true) ({}) else if(true) ({})4 if(true) ({}) else if(true) ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-if-else_expression.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = if(true) {} else {}4 _ = if(true) {} else {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-if-else_statement.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 if(true) ({}) else ({})4 if(true) ({}) else ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-if_expression.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = if(true) {}4 _ = if(true) {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-if_statement.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 if(true) ({})4 if(true) ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-test_expression.zig+3
...@@ -3,7 +3,10 @@ export fn entry() void {...@@ -3,7 +3,10 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = if (foo()) |_| {}4 _ = if (foo()) |_| {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
9fn foo() void {}
710
8// error11// error
9// backend=stage212// backend=stage2
test/cases/compile_errors/implicit_semicolon-test_statement.zig+3
...@@ -3,7 +3,10 @@ export fn entry() void {...@@ -3,7 +3,10 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 if (foo()) |_| ({})4 if (foo()) |_| ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
9fn foo() void {}
710
8// error11// error
9// backend=stage212// backend=stage2
test/cases/compile_errors/implicit_semicolon-while-continue_expression.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = while(true):({}) {}4 _ = while(true):({}) {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-while-continue_statement.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 while(true):({}) ({})4 while(true):({}) ({})
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-while_expression.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 _ = while(true) {}4 _ = while(true) {}
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/implicit_semicolon-while_statement.zig+2
...@@ -3,6 +3,8 @@ export fn entry() void {...@@ -3,6 +3,8 @@ export fn entry() void {
3 var good = {};3 var good = {};
4 while(true) 14 while(true) 1
5 var bad = {};5 var bad = {};
6 _ = good;
7 _ = bad;
6}8}
79
8// error10// error
test/cases/compile_errors/invalid_member_of_builtin_enum.zig+1-1
...@@ -9,4 +9,4 @@ export fn entry() void {...@@ -9,4 +9,4 @@ export fn entry() void {
9// target=native9// target=native
10//10//
11// :3:38: error: enum 'builtin.OptimizeMode' has no member named 'x86'11// :3:38: error: enum 'builtin.OptimizeMode' has no member named 'x86'
12// :?:18: note: enum declared here12// : 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 {...@@ -73,11 +73,11 @@ pub export fn entry8() void {
73//73//
74// :6:19: error: value stored in comptime field does not match the default value of the field74// :6:19: error: value stored in comptime field does not match the default value of the field
75// :14:19: error: value stored in comptime field does not match the default value of the field75// :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
77// :19:38: error: value stored in comptime field does not match the default value of the field76// :19:38: error: value stored in comptime field does not match the default value of the field
78// :31:19: error: value stored in comptime field does not match the default value of the field77// :31:19: error: value stored in comptime field does not match the default value of the field
79// :25:29: note: default value set here78// :25:29: note: default value set here
80// :41:16: error: value stored in comptime field does not match the default value of the field79// :41:16: error: value stored in comptime field does not match the default value of the field
81// :45:12: error: value stored in comptime field does not match the default value of the field80// :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
82// :66:43: error: value stored in comptime field does not match the default value of the field82// :66:43: error: value stored in comptime field does not match the default value of the field
83// :59:35: error: value stored in comptime field does not match the default value of the field83// :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 {...@@ -25,5 +25,6 @@ export fn e() void {
25// :4:7: error: no field named 'foo' in struct 'tmp.A'25// :4:7: error: no field named 'foo' in struct 'tmp.A'
26// :1:11: note: struct declared here26// :1:11: note: struct declared here
27// :10:17: error: no field named 'bar' in struct 'tmp.A'27// :10:17: error: no field named 'bar' in struct 'tmp.A'
28// :1:11: note: struct declared here
28// :18:45: error: no field named 'f' in struct 'tmp.e.B'29// :18:45: error: no field named 'f' in struct 'tmp.e.B'
29// :14:15: note: struct declared here30// :14:15: note: struct declared here
test/cases/compile_errors/missing_main_fn_in_executable.zig+4-2
...@@ -5,5 +5,7 @@...@@ -5,5 +5,7 @@
5// target=x86_64-linux5// target=x86_64-linux
6// output_mode=Exe6// output_mode=Exe
7//7//
8// :?:?: error: root struct of file 'tmp' has no member named 'main'8// : error: root struct of file 'tmp' has no member named 'main'
9// :?:?: note: called from here9// : 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 {}...@@ -5,6 +5,8 @@ fn main() void {}
5// target=x86_64-linux5// target=x86_64-linux
6// output_mode=Exe6// output_mode=Exe
7//7//
8// :?:?: error: 'main' is not marked 'pub'8// : error: 'main' is not marked 'pub'
9// :1:1: note: declared here9// :1:1: note: declared here
10// :?:?: note: called from here10// : 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 {...@@ -15,5 +15,6 @@ export fn entry() void {
15// target=native15// target=native
16//16//
17// :9:51: error: values of type '[]const builtin.Type.StructField' must be comptime-known, but index value is runtime-known17// :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 field18// : note: struct requires comptime because of this field
19// :?:21: note: types are not available at runtime19// : 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 {...@@ -13,6 +13,6 @@ comptime {
13// target=native13// target=native
14//14//
15// :7:16: error: expected type 'tmp.Foo', found 'tmp.Bar'15// :7:16: error: expected type 'tmp.Foo', found 'tmp.Bar'
16// :1:13: note: struct declared here
17// :2:13: note: struct declared here16// :2:13: note: struct declared here
17// :1:13: note: struct declared here
18// :4:18: note: parameter type declared here18// :4:18: note: parameter type declared here
test/cases/compile_errors/undefined_as_field_type_is_rejected.zig+8-4
...@@ -1,9 +1,13 @@...@@ -1,9 +1,13 @@
1export fn a() void {1const Foo = struct {
2 b();2 a: undefined,
3};
4export fn entry1() void {
5 const foo: Foo = undefined;
6 _ = foo;
3}7}
48
5// error9// error
6// backend=stage210// backend=stage1
7// target=native11// target=native
8//12//
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 {...@@ -28,10 +28,11 @@ export fn u2m() void {
28// target=native28// target=native
29//29//
30// :9:1: error: union initializer must initialize one field30// :9:1: error: union initializer must initialize one field
31// :1:12: note: union declared here
31// :14:20: error: cannot initialize multiple union fields at once, unions can only have one active field32// :14:20: error: cannot initialize multiple union fields at once, unions can only have one active field
32// :14:31: note: additional initializer here33// :14:31: note: additional initializer here
34// :1:12: note: union declared here
33// :18:21: error: union initializer must initialize one field35// :18:21: error: union initializer must initialize one field
34// :22:20: error: cannot initialize multiple union fields at once, unions can only have one active field36// :22:20: error: cannot initialize multiple union fields at once, unions can only have one active field
35// :22:31: note: additional initializer here37// :22:31: note: additional initializer here
36// :1:12: note: union declared here
37// :5:12: note: union declared here38// :5:12: note: union declared here
test/cases/compile_log.0.zig+5
...@@ -15,3 +15,8 @@ fn x() void {}...@@ -15,3 +15,8 @@ fn x() void {}
15// error15// error
16//16//
17// :6:23: error: expected type 'usize', found 'bool'17// :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 {}...@@ -14,3 +14,7 @@ fn x() void {}
14//14//
15// :9:5: error: found compile log statement15// :9:5: error: found compile log statement
16// :4:5: note: also here16// :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 {...@@ -9,7 +9,8 @@ pub fn main() void {
9// run9// run
10// backend=llvm10// backend=llvm
11// target=x86_64-linux-gnu11// target=x86_64-linux-gnu
12// link_libc=1
12//13//
13// f64: 2.00000014// f64: 2.000000
14// f32: 10.00000015// 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 {...@@ -14,4 +14,5 @@ fn foo(comptime info: std.builtin.Type) !void {
1414
15// run15// run
16// is_test=116// is_test=1
17// backend=llvm
17//18//
test/cases/llvm/address_space_pointer_access_chaining_pointer_to_optional_array.zig+1-1
...@@ -5,7 +5,7 @@ pub fn main() void {...@@ -5,7 +5,7 @@ pub fn main() void {
5 _ = entry;5 _ = entry;
6}6}
77
8// error8// compile
9// output_mode=Exe9// output_mode=Exe
10// backend=llvm10// backend=llvm
11// target=x86_64-linux,x86_64-macos11// 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 {...@@ -5,7 +5,7 @@ pub fn main() void {
5 _ = entry;5 _ = entry;
6}6}
77
8// error8// compile
9// output_mode=Exe9// output_mode=Exe
10// backend=stage2,llvm10// backend=stage2,llvm
11// target=x86_64-linux,x86_64-macos11// 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 {...@@ -6,7 +6,7 @@ pub fn main() void {
6 _ = entry;6 _ = entry;
7}7}
88
9// error9// compile
10// output_mode=Exe10// output_mode=Exe
11// backend=llvm11// backend=llvm
12// target=x86_64-linux,x86_64-macos12// 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 {...@@ -6,7 +6,7 @@ pub fn main() void {
6 _ = entry;6 _ = entry;
7}7}
88
9// error9// compile
10// output_mode=Exe10// output_mode=Exe
11// backend=stage2,llvm11// backend=stage2,llvm
12// target=x86_64-linux,x86_64-macos12// 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 {...@@ -5,7 +5,7 @@ pub fn main() void {
5 _ = entry;5 _ = entry;
6}6}
77
8// error8// compile
9// output_mode=Exe9// output_mode=Exe
10// backend=stage2,llvm10// backend=stage2,llvm
11// target=x86_64-linux,x86_64-macos11// target=x86_64-linux,x86_64-macos
test/cases/llvm/hello_world.zig+1
...@@ -7,6 +7,7 @@ pub fn main() void {...@@ -7,6 +7,7 @@ pub fn main() void {
7// run7// run
8// backend=llvm8// backend=llvm
9// target=x86_64-linux,x86_64-macos9// target=x86_64-linux,x86_64-macos
10// link_libc=1
10//11//
11// hello world!12// hello world!
12//13//
test/cases/llvm/pointer_keeps_address_space.zig+1-1
...@@ -5,7 +5,7 @@ pub fn main() void {...@@ -5,7 +5,7 @@ pub fn main() void {
5 _ = entry;5 _ = entry;
6}6}
77
8// error8// compile
9// output_mode=Exe9// output_mode=Exe
10// backend=stage2,llvm10// backend=stage2,llvm
11// target=x86_64-linux,x86_64-macos11// 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 {...@@ -5,7 +5,7 @@ pub fn main() void {
5 _ = entry;5 _ = entry;
6}6}
77
8// error8// compile
9// output_mode=Exe9// output_mode=Exe
10// backend=stage2,llvm10// backend=stage2,llvm
11// target=x86_64-linux,x86_64-macos11// 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 {...@@ -5,7 +5,7 @@ pub fn main() void {
5 _ = entry;5 _ = entry;
6}6}
77
8// error8// compile
9// output_mode=Exe9// output_mode=Exe
10// backend=stage2,llvm10// backend=stage2,llvm
11// target=x86_64-linux,x86_64-macos11// 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 @@...@@ -1,146 +1,10 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const TestContext = @import("../src/test.zig").TestContext;3const Cases = @import("src/Cases.zig");
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 });
1404
5pub fn addCases(ctx: *Cases) !void {
141 {6 {
142 const case = ctx.obj("multiline error messages", .{});7 const case = ctx.obj("multiline error messages", .{});
143 case.backend = .stage2;
1448
145 case.addError(9 case.addError(
146 \\comptime {10 \\comptime {
...@@ -176,7 +40,6 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -176,7 +40,6 @@ pub fn addCases(ctx: *TestContext) !void {
17640
177 {41 {
178 const case = ctx.obj("isolated carriage return in multiline string literal", .{});42 const case = ctx.obj("isolated carriage return in multiline string literal", .{});
179 case.backend = .stage2;
18043
181 case.addError("const foo = \\\\\test\r\r rogue carriage return\n;", &[_][]const u8{44 case.addError("const foo = \\\\\test\r\r rogue carriage return\n;", &[_][]const u8{
182 ":1:19: error: expected ';' after declaration",45 ":1:19: error: expected ';' after declaration",
...@@ -195,16 +58,6 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -195,16 +58,6 @@ pub fn addCases(ctx: *TestContext) !void {
19558
196 {59 {
197 const case = ctx.obj("argument causes error", .{});60 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
209 case.addError(62 case.addError(
210 \\pub export fn entry() void {63 \\pub export fn entry() void {
...@@ -216,15 +69,18 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -216,15 +69,18 @@ pub fn addCases(ctx: *TestContext) !void {
216 ":3:12: note: argument to function being called at comptime must be comptime-known",69 ":3:12: note: argument to function being called at comptime must be comptime-known",
217 ":2:55: note: expression is evaluated at comptime because the generic function was instantiated with a comptime-only return type",70 ":2:55: note: expression is evaluated at comptime because the generic function was instantiated with a comptime-only return type",
218 });71 });
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 );
219 }80 }
22081
221 {82 {
222 const case = ctx.obj("astgen failure in file struct", .{});83 const case = ctx.obj("astgen failure in file struct", .{});
223 case.backend = .stage2;
224
225 case.addSourceFile("b.zig",
226 \\+
227 );
22884
229 case.addError(85 case.addError(
230 \\pub export fn entry() void {86 \\pub export fn entry() void {
...@@ -233,21 +89,13 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -233,21 +89,13 @@ pub fn addCases(ctx: *TestContext) !void {
233 , &[_][]const u8{89 , &[_][]const u8{
234 ":1:1: error: expected type expression, found '+'",90 ":1:1: error: expected type expression, found '+'",
235 });91 });
92 case.addSourceFile("b.zig",
93 \\+
94 );
236 }95 }
23796
238 {97 {
239 const case = ctx.obj("invalid store to comptime field", .{});98 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
252 case.addError(100 case.addError(
253 \\const a = @import("a.zig");101 \\const a = @import("a.zig");
...@@ -259,44 +107,19 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -259,44 +107,19 @@ pub fn addCases(ctx: *TestContext) !void {
259 ":4:23: error: value stored in comptime field does not match the default value of the field",107 ":4:23: error: value stored in comptime field does not match the default value of the field",
260 ":2:25: note: default value set here",108 ":2:25: note: default value set here",
261 });109 });
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 );
262 }119 }
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
292 {121 {
293 const case = ctx.obj("file in multiple modules", .{});122 const case = ctx.obj("file in multiple modules", .{});
294 case.backend = .stage2;
295
296 case.addSourceFile("foo.zig",
297 \\const dummy = 0;
298 );
299
300 case.addDepModule("foo", "foo.zig");123 case.addDepModule("foo", "foo.zig");
301124
302 case.addError(125 case.addError(
...@@ -309,5 +132,8 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -309,5 +132,8 @@ pub fn addCases(ctx: *TestContext) !void {
309 ":1:1: note: root of module root.foo",132 ":1:1: note: root of module root.foo",
310 ":3:17: note: imported from module root",133 ":3:17: note: imported from module root",
311 });134 });
135 case.addSourceFile("foo.zig",
136 \\const dummy = 0;
137 );
312 }138 }
313}139}
test/link.zig+172-213
...@@ -1,213 +1,172 @@...@@ -1,213 +1,172 @@
1const std = @import("std");1pub const Case = struct {
2const builtin = @import("builtin");2 build_root: []const u8,
3const tests = @import("tests.zig");3 import: type,
44};
5pub fn addCases(cases: *tests.StandaloneContext) void {5
6 cases.addBuildFile("test/link/bss/build.zig", .{6pub const cases = [_]Case{
7 .build_modes = false, // we only guarantee zerofill for undefined in Debug7 .{
8 });8 .build_root = "test/link/bss",
99 .import = @import("link/bss/build.zig"),
10 cases.addBuildFile("test/link/common_symbols/build.zig", .{10 },
11 .build_modes = true,11 .{
12 });12 .build_root = "test/link/common_symbols",
1313 .import = @import("link/common_symbols/build.zig"),
14 cases.addBuildFile("test/link/common_symbols_alignment/build.zig", .{14 },
15 .build_modes = true,15 .{
16 });16 .build_root = "test/link/common_symbols_alignment",
1717 .import = @import("link/common_symbols_alignment/build.zig"),
18 cases.addBuildFile("test/link/interdependent_static_c_libs/build.zig", .{18 },
19 .build_modes = true,19 .{
20 });20 .build_root = "test/link/interdependent_static_c_libs",
2121 .import = @import("link/interdependent_static_c_libs/build.zig"),
22 cases.addBuildFile("test/link/static_lib_as_system_lib/build.zig", .{22 },
23 .build_modes = true,23
24 });24 // WASM Cases
2525 .{
26 addWasmCases(cases);26 .build_root = "test/link/wasm/archive",
27 addMachOCases(cases);27 .import = @import("link/wasm/archive/build.zig"),
28}28 },
2929 .{
30fn addWasmCases(cases: *tests.StandaloneContext) void {30 .build_root = "test/link/wasm/basic-features",
31 cases.addBuildFile("test/link/wasm/archive/build.zig", .{31 .import = @import("link/wasm/basic-features/build.zig"),
32 .build_modes = true,32 },
33 .requires_stage2 = true,33 .{
34 });34 .build_root = "test/link/wasm/bss",
3535 .import = @import("link/wasm/bss/build.zig"),
36 cases.addBuildFile("test/link/wasm/basic-features/build.zig", .{36 },
37 .requires_stage2 = true,37 .{
38 });38 .build_root = "test/link/wasm/export",
3939 .import = @import("link/wasm/export/build.zig"),
40 cases.addBuildFile("test/link/wasm/bss/build.zig", .{40 },
41 .build_modes = false,41 .{
42 .requires_stage2 = true,42 .build_root = "test/link/wasm/export-data",
43 });43 .import = @import("link/wasm/export-data/build.zig"),
4444 },
45 cases.addBuildFile("test/link/wasm/export/build.zig", .{45 .{
46 .build_modes = true,46 .build_root = "test/link/wasm/extern",
47 .requires_stage2 = true,47 .import = @import("link/wasm/extern/build.zig"),
48 });48 },
4949 .{
50 // TODO: Fix open handle in wasm-linker refraining rename from working on Windows.50 .build_root = "test/link/wasm/extern-mangle",
51 if (builtin.os.tag != .windows) {51 .import = @import("link/wasm/extern-mangle/build.zig"),
52 cases.addBuildFile("test/link/wasm/export-data/build.zig", .{});52 },
53 }53 .{
5454 .build_root = "test/link/wasm/function-table",
55 cases.addBuildFile("test/link/wasm/extern/build.zig", .{55 .import = @import("link/wasm/function-table/build.zig"),
56 .build_modes = true,56 },
57 .requires_stage2 = true,57 .{
58 .use_emulation = true,58 .build_root = "test/link/wasm/infer-features",
59 });59 .import = @import("link/wasm/infer-features/build.zig"),
6060 },
61 cases.addBuildFile("test/link/wasm/extern-mangle/build.zig", .{61 .{
62 .build_modes = true,62 .build_root = "test/link/wasm/producers",
63 .requires_stage2 = true,63 .import = @import("link/wasm/producers/build.zig"),
64 });64 },
6565 .{
66 cases.addBuildFile("test/link/wasm/function-table/build.zig", .{66 .build_root = "test/link/wasm/segments",
67 .build_modes = true,67 .import = @import("link/wasm/segments/build.zig"),
68 .requires_stage2 = true,68 },
69 });69 .{
7070 .build_root = "test/link/wasm/stack_pointer",
71 cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{71 .import = @import("link/wasm/stack_pointer/build.zig"),
72 .requires_stage2 = true,72 },
73 });73 .{
7474 .build_root = "test/link/wasm/type",
75 cases.addBuildFile("test/link/wasm/producers/build.zig", .{75 .import = @import("link/wasm/type/build.zig"),
76 .build_modes = true,76 },
77 .requires_stage2 = true,77
78 });78 // Mach-O Cases
7979 .{
80 cases.addBuildFile("test/link/wasm/segments/build.zig", .{80 .build_root = "test/link/macho/bugs/13056",
81 .build_modes = true,81 .import = @import("link/macho/bugs/13056/build.zig"),
82 .requires_stage2 = true,82 },
83 });83 .{
8484 .build_root = "test/link/macho/bugs/13457",
85 cases.addBuildFile("test/link/wasm/stack_pointer/build.zig", .{85 .import = @import("link/macho/bugs/13457/build.zig"),
86 .build_modes = true,86 },
87 .requires_stage2 = true,87 .{
88 });88 .build_root = "test/link/macho/dead_strip",
8989 .import = @import("link/macho/dead_strip/build.zig"),
90 cases.addBuildFile("test/link/wasm/type/build.zig", .{90 },
91 .build_modes = true,91 .{
92 .requires_stage2 = true,92 .build_root = "test/link/macho/dead_strip_dylibs",
93 });93 .import = @import("link/macho/dead_strip_dylibs/build.zig"),
94}94 },
9595 .{
96fn addMachOCases(cases: *tests.StandaloneContext) void {96 .build_root = "test/link/macho/dylib",
97 cases.addBuildFile("test/link/macho/bugs/13056/build.zig", .{97 .import = @import("link/macho/dylib/build.zig"),
98 .build_modes = true,98 },
99 .requires_macos_sdk = true,99 .{
100 .requires_symlinks = true,100 .build_root = "test/link/macho/empty",
101 });101 .import = @import("link/macho/empty/build.zig"),
102102 },
103 cases.addBuildFile("test/link/macho/bugs/13457/build.zig", .{103 .{
104 .build_modes = true,104 .build_root = "test/link/macho/entry",
105 .requires_symlinks = true,105 .import = @import("link/macho/entry/build.zig"),
106 });106 },
107107 .{
108 cases.addBuildFile("test/link/macho/dead_strip/build.zig", .{108 .build_root = "test/link/macho/headerpad",
109 .build_modes = false,109 .import = @import("link/macho/headerpad/build.zig"),
110 .requires_symlinks = true,110 },
111 });111 .{
112112 .build_root = "test/link/macho/linksection",
113 cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{113 .import = @import("link/macho/linksection/build.zig"),
114 .build_modes = true,114 },
115 .requires_macos_sdk = true,115 .{
116 .requires_symlinks = true,116 .build_root = "test/link/macho/needed_framework",
117 });117 .import = @import("link/macho/needed_framework/build.zig"),
118118 },
119 cases.addBuildFile("test/link/macho/dylib/build.zig", .{119 .{
120 .build_modes = true,120 .build_root = "test/link/macho/needed_library",
121 .requires_symlinks = true,121 .import = @import("link/macho/needed_library/build.zig"),
122 });122 },
123123 .{
124 cases.addBuildFile("test/link/macho/empty/build.zig", .{124 .build_root = "test/link/macho/objc",
125 .build_modes = true,125 .import = @import("link/macho/objc/build.zig"),
126 .requires_symlinks = true,126 },
127 });127 .{
128128 .build_root = "test/link/macho/objcpp",
129 cases.addBuildFile("test/link/macho/entry/build.zig", .{129 .import = @import("link/macho/objcpp/build.zig"),
130 .build_modes = true,130 },
131 .requires_symlinks = true,131 .{
132 });132 .build_root = "test/link/macho/pagezero",
133133 .import = @import("link/macho/pagezero/build.zig"),
134 cases.addBuildFile("test/link/macho/headerpad/build.zig", .{134 },
135 .build_modes = true,135 .{
136 .requires_macos_sdk = true,136 .build_root = "test/link/macho/search_strategy",
137 .requires_symlinks = true,137 .import = @import("link/macho/search_strategy/build.zig"),
138 });138 },
139139 .{
140 cases.addBuildFile("test/link/macho/linksection/build.zig", .{140 .build_root = "test/link/macho/stack_size",
141 .build_modes = true,141 .import = @import("link/macho/stack_size/build.zig"),
142 .requires_symlinks = true,142 },
143 });143 .{
144144 .build_root = "test/link/macho/strict_validation",
145 cases.addBuildFile("test/link/macho/needed_framework/build.zig", .{145 .import = @import("link/macho/strict_validation/build.zig"),
146 .build_modes = true,146 },
147 .requires_macos_sdk = true,147 .{
148 .requires_symlinks = true,148 .build_root = "test/link/macho/tls",
149 });149 .import = @import("link/macho/tls/build.zig"),
150150 },
151 cases.addBuildFile("test/link/macho/needed_library/build.zig", .{151 .{
152 .build_modes = true,152 .build_root = "test/link/macho/unwind_info",
153 .requires_symlinks = true,153 .import = @import("link/macho/unwind_info/build.zig"),
154 });154 },
155155 // TODO: re-enable this test. It currently has some incompatibilities with
156 cases.addBuildFile("test/link/macho/objc/build.zig", .{156 // the new build system API. In particular, it depends on installing the build
157 .build_modes = true,157 // artifacts, which should be unnecessary, and it has a custom build step that
158 .requires_macos_sdk = true,158 // prints directly to stderr instead of failing the step with an error message.
159 .requires_symlinks = true,159 //.{
160 });160 // .build_root = "test/link/macho/uuid",
161161 // .import = @import("link/macho/uuid/build.zig"),
162 cases.addBuildFile("test/link/macho/objcpp/build.zig", .{162 //},
163 .build_modes = true,163
164 .requires_macos_sdk = true,164 .{
165 .requires_symlinks = true,165 .build_root = "test/link/macho/weak_library",
166 });166 .import = @import("link/macho/weak_library/build.zig"),
167167 },
168 cases.addBuildFile("test/link/macho/pagezero/build.zig", .{168 .{
169 .build_modes = false,169 .build_root = "test/link/macho/weak_framework",
170 .requires_symlinks = true,170 .import = @import("link/macho/weak_framework/build.zig"),
171 });171 },
172172};
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}
test/link/bss/build.zig+3-3
...@@ -1,17 +1,17 @@...@@ -1,17 +1,17 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});
5 const test_step = b.step("test", "Test");4 const test_step = b.step("test", "Test");
5 b.default_step = test_step;
66
7 const exe = b.addExecutable(.{7 const exe = b.addExecutable(.{
8 .name = "bss",8 .name = "bss",
9 .root_source_file = .{ .path = "main.zig" },9 .root_source_file = .{ .path = "main.zig" },
10 .optimize = optimize,10 .optimize = .Debug,
11 });11 });
12 b.default_step.dependOn(&exe.step);
1312
14 const run = exe.run();13 const run = exe.run();
15 run.expectStdOutEqual("0, 1, 0\n");14 run.expectStdOutEqual("0, 1, 0\n");
15
16 test_step.dependOn(&run.step);16 test_step.dependOn(&run.step);
17}17}
test/link/bss/main.zig+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1const std = @import("std");1const std = @import("std");
22
3// Stress test zerofill layout3// Stress test zerofill layout
4var buffer: [0x1000000]u64 = undefined;4var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000;
55
6pub fn main() anyerror!void {6pub fn main() anyerror!void {
7 buffer[0x10] = 1;7 buffer[0x10] = 1;
test/link/common_symbols/build.zig+10-3
...@@ -1,8 +1,16 @@...@@ -1,8 +1,16 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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 {
6 const lib_a = b.addStaticLibrary(.{14 const lib_a = b.addStaticLibrary(.{
7 .name = "a",15 .name = "a",
8 .optimize = optimize,16 .optimize = optimize,
...@@ -16,6 +24,5 @@ pub fn build(b: *std.Build) void {...@@ -16,6 +24,5 @@ pub fn build(b: *std.Build) void {
16 });24 });
17 test_exe.linkLibrary(lib_a);25 test_exe.linkLibrary(lib_a);
1826
19 const test_step = b.step("test", "Test it");27 test_step.dependOn(&test_exe.run().step);
20 test_step.dependOn(&test_exe.step);
21}28}
test/link/common_symbols_alignment/build.zig+11-6
...@@ -1,23 +1,28 @@...@@ -1,23 +1,28 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});4 const test_step = b.step("test", "Test it");
5 const target = b.standardTargetOptions(.{});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 {
7 const lib_a = b.addStaticLibrary(.{14 const lib_a = b.addStaticLibrary(.{
8 .name = "a",15 .name = "a",
9 .optimize = optimize,16 .optimize = optimize,
10 .target = target,17 .target = .{},
11 });18 });
12 lib_a.addCSourceFiles(&.{"a.c"}, &.{"-fcommon"});19 lib_a.addCSourceFiles(&.{"a.c"}, &.{"-fcommon"});
1320
14 const test_exe = b.addTest(.{21 const test_exe = b.addTest(.{
15 .root_source_file = .{ .path = "main.zig" },22 .root_source_file = .{ .path = "main.zig" },
16 .optimize = optimize,23 .optimize = optimize,
17 .target = target,
18 });24 });
19 test_exe.linkLibrary(lib_a);25 test_exe.linkLibrary(lib_a);
2026
21 const test_step = b.step("test", "Test it");27 test_step.dependOn(&test_exe.run().step);
22 test_step.dependOn(&test_exe.step);
23}28}
test/link/interdependent_static_c_libs/build.zig+12-7
...@@ -1,13 +1,20 @@...@@ -1,13 +1,20 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});4 const test_step = b.step("test", "Test it");
5 const target = b.standardTargetOptions(.{});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 {
7 const lib_a = b.addStaticLibrary(.{14 const lib_a = b.addStaticLibrary(.{
8 .name = "a",15 .name = "a",
9 .optimize = optimize,16 .optimize = optimize,
10 .target = target,17 .target = .{},
11 });18 });
12 lib_a.addCSourceFile("a.c", &[_][]const u8{});19 lib_a.addCSourceFile("a.c", &[_][]const u8{});
13 lib_a.addIncludePath(".");20 lib_a.addIncludePath(".");
...@@ -15,7 +22,7 @@ pub fn build(b: *std.Build) void {...@@ -15,7 +22,7 @@ pub fn build(b: *std.Build) void {
15 const lib_b = b.addStaticLibrary(.{22 const lib_b = b.addStaticLibrary(.{
16 .name = "b",23 .name = "b",
17 .optimize = optimize,24 .optimize = optimize,
18 .target = target,25 .target = .{},
19 });26 });
20 lib_b.addCSourceFile("b.c", &[_][]const u8{});27 lib_b.addCSourceFile("b.c", &[_][]const u8{});
21 lib_b.addIncludePath(".");28 lib_b.addIncludePath(".");
...@@ -23,12 +30,10 @@ pub fn build(b: *std.Build) void {...@@ -23,12 +30,10 @@ pub fn build(b: *std.Build) void {
23 const test_exe = b.addTest(.{30 const test_exe = b.addTest(.{
24 .root_source_file = .{ .path = "main.zig" },31 .root_source_file = .{ .path = "main.zig" },
25 .optimize = optimize,32 .optimize = optimize,
26 .target = target,
27 });33 });
28 test_exe.linkLibrary(lib_a);34 test_exe.linkLibrary(lib_a);
29 test_exe.linkLibrary(lib_b);35 test_exe.linkLibrary(lib_b);
30 test_exe.addIncludePath(".");36 test_exe.addIncludePath(".");
3137
32 const test_step = b.step("test", "Test it");38 test_step.dependOn(&test_exe.run().step);
33 test_step.dependOn(&test_exe.step);
34}39}
test/link/macho/bugs/13056/build.zig+12-4
...@@ -1,20 +1,28 @@...@@ -1,20 +1,28 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_macos_sdk = true;
4pub const requires_symlinks = true;
5
3pub fn build(b: *std.Build) void {6pub 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 {
6 const target: std.zig.CrossTarget = .{ .os_tag = .macos };17 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
7 const target_info = std.zig.system.NativeTargetInfo.detect(target) catch unreachable;18 const target_info = std.zig.system.NativeTargetInfo.detect(target) catch unreachable;
8 const sdk = std.zig.system.darwin.getDarwinSDK(b.allocator, target_info.target) orelse19 const sdk = std.zig.system.darwin.getDarwinSDK(b.allocator, target_info.target) orelse
9 @panic("macOS SDK is required to run the test");20 @panic("macOS SDK is required to run the test");
1021
11 const test_step = b.step("test", "Test the program");
12
13 const exe = b.addExecutable(.{22 const exe = b.addExecutable(.{
14 .name = "test",23 .name = "test",
15 .optimize = optimize,24 .optimize = optimize,
16 });25 });
17 b.default_step.dependOn(&exe.step);
18 exe.addIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include" }) catch unreachable);26 exe.addIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include" }) catch unreachable);
19 exe.addIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include/c++/v1" }) catch unreachable);27 exe.addIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include/c++/v1" }) catch unreachable);
20 exe.addCSourceFile("test.cpp", &.{28 exe.addCSourceFile("test.cpp", &.{
test/link/macho/bugs/13457/build.zig+16-4
...@@ -1,10 +1,19 @@...@@ -1,10 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };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
9 const exe = b.addExecutable(.{18 const exe = b.addExecutable(.{
10 .name = "test",19 .name = "test",
...@@ -13,6 +22,9 @@ pub fn build(b: *std.Build) void {...@@ -13,6 +22,9 @@ pub fn build(b: *std.Build) void {
13 .target = target,22 .target = target,
14 });23 });
1524
16 const run = exe.runEmulatable();25 const run = b.addRunArtifact(exe);
26 run.skip_foreign_checks = true;
27 run.expectStdOutEqual("");
28
17 test_step.dependOn(&run.step);29 test_step.dependOn(&run.step);
18}30}
test/link/macho/dead_strip/build.zig+10-7
...@@ -1,17 +1,19 @@...@@ -1,17 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const optimize: std.builtin.OptimizeMode = .Debug;
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };7 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
68
7 const test_step = b.step("test", "Test the program");9 const test_step = b.step("test", "Test the program");
8 test_step.dependOn(b.getInstallStep());10 b.default_step = test_step;
911
10 {12 {
11 // Without -dead_strip, we expect `iAmUnused` symbol present13 // 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();
15 check.checkInSymtab();17 check.checkInSymtab();
16 check.checkNext("{*} (__TEXT,__text) external _iAmUnused");18 check.checkNext("{*} (__TEXT,__text) external _iAmUnused");
1719
...@@ -22,10 +24,10 @@ pub fn build(b: *std.Build) void {...@@ -22,10 +24,10 @@ pub fn build(b: *std.Build) void {
2224
23 {25 {
24 // With -dead_strip, no `iAmUnused` symbol should be present26 // 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");
26 exe.link_gc_sections = true;28 exe.link_gc_sections = true;
2729
28 const check = exe.checkObject(.macho);30 const check = exe.checkObject();
29 check.checkInSymtab();31 check.checkInSymtab();
30 check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused");32 check.checkNotPresent("{*} (__TEXT,__text) external _iAmUnused");
3133
...@@ -39,9 +41,10 @@ fn createScenario(...@@ -39,9 +41,10 @@ fn createScenario(
39 b: *std.Build,41 b: *std.Build,
40 optimize: std.builtin.OptimizeMode,42 optimize: std.builtin.OptimizeMode,
41 target: std.zig.CrossTarget,43 target: std.zig.CrossTarget,
44 name: []const u8,
42) *std.Build.CompileStep {45) *std.Build.CompileStep {
43 const exe = b.addExecutable(.{46 const exe = b.addExecutable(.{
44 .name = "test",47 .name = name,
45 .optimize = optimize,48 .optimize = optimize,
46 .target = target,49 .target = target,
47 });50 });
test/link/macho/dead_strip_dylibs/build.zig+22-10
...@@ -1,16 +1,24 @@...@@ -1,16 +1,24 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_macos_sdk = true;
4pub const requires_symlinks = true;
5
3pub fn build(b: *std.Build) void {6pub 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);
7 test_step.dependOn(b.getInstallStep());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 {
9 {17 {
10 // Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable18 // 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();
14 check.checkStart("cmd LOAD_DYLIB");22 check.checkStart("cmd LOAD_DYLIB");
15 check.checkNext("name {*}Cocoa");23 check.checkNext("name {*}Cocoa");
1624
...@@ -25,18 +33,22 @@ pub fn build(b: *std.Build) void {...@@ -25,18 +33,22 @@ pub fn build(b: *std.Build) void {
2533
26 {34 {
27 // With -dead_strip_dylibs, we should include liba.dylib as it's unreachable35 // 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");
29 exe.dead_strip_dylibs = true;37 exe.dead_strip_dylibs = true;
3038
31 const run_cmd = exe.run();39 const run_cmd = b.addRunArtifact(exe);
32 run_cmd.expected_term = .{ .Exited = @bitCast(u8, @as(i8, -2)) }; // should fail40 run_cmd.expectExitCode(@bitCast(u8, @as(i8, -2))); // should fail
33 test_step.dependOn(&run_cmd.step);41 test_step.dependOn(&run_cmd.step);
34 }42 }
35}43}
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 {
38 const exe = b.addExecutable(.{50 const exe = b.addExecutable(.{
39 .name = "test",51 .name = name,
40 .optimize = optimize,52 .optimize = optimize,
41 });53 });
42 exe.addCSourceFile("main.c", &[0][]const u8{});54 exe.addCSourceFile("main.c", &[0][]const u8{});
test/link/macho/dylib/build.zig+20-11
...@@ -1,11 +1,19 @@...@@ -1,11 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");9 add(b, test_step, .Debug);
8 test_step.dependOn(b.getInstallStep());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
10 const dylib = b.addSharedLibrary(.{18 const dylib = b.addSharedLibrary(.{
11 .name = "a",19 .name = "a",
...@@ -15,9 +23,8 @@ pub fn build(b: *std.Build) void {...@@ -15,9 +23,8 @@ pub fn build(b: *std.Build) void {
15 });23 });
16 dylib.addCSourceFile("a.c", &.{});24 dylib.addCSourceFile("a.c", &.{});
17 dylib.linkLibC();25 dylib.linkLibC();
18 dylib.install();
1926
20 const check_dylib = dylib.checkObject(.macho);27 const check_dylib = dylib.checkObject();
21 check_dylib.checkStart("cmd ID_DYLIB");28 check_dylib.checkStart("cmd ID_DYLIB");
22 check_dylib.checkNext("name @rpath/liba.dylib");29 check_dylib.checkNext("name @rpath/liba.dylib");
23 check_dylib.checkNext("timestamp 2");30 check_dylib.checkNext("timestamp 2");
...@@ -33,11 +40,11 @@ pub fn build(b: *std.Build) void {...@@ -33,11 +40,11 @@ pub fn build(b: *std.Build) void {
33 });40 });
34 exe.addCSourceFile("main.c", &.{});41 exe.addCSourceFile("main.c", &.{});
35 exe.linkSystemLibrary("a");42 exe.linkSystemLibrary("a");
43 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
44 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
36 exe.linkLibC();45 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();
41 check_exe.checkStart("cmd LOAD_DYLIB");48 check_exe.checkStart("cmd LOAD_DYLIB");
42 check_exe.checkNext("name @rpath/liba.dylib");49 check_exe.checkNext("name @rpath/liba.dylib");
43 check_exe.checkNext("timestamp 2");50 check_exe.checkNext("timestamp 2");
...@@ -45,10 +52,12 @@ pub fn build(b: *std.Build) void {...@@ -45,10 +52,12 @@ pub fn build(b: *std.Build) void {
45 check_exe.checkNext("compatibility version 10000");52 check_exe.checkNext("compatibility version 10000");
4653
47 check_exe.checkStart("cmd RPATH");54 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
50 const run = check_exe.runAndCompare();60 const run = check_exe.runAndCompare();
51 run.cwd = b.pathFromRoot(".");
52 run.expectStdOutEqual("Hello world");61 run.expectStdOutEqual("Hello world");
53 test_step.dependOn(&run.step);62 test_step.dependOn(&run.step);
54}63}
test/link/macho/empty/build.zig+14-5
...@@ -1,11 +1,19 @@...@@ -1,11 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test the program");9 add(b, test_step, .Debug);
8 test_step.dependOn(b.getInstallStep());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
10 const exe = b.addExecutable(.{18 const exe = b.addExecutable(.{
11 .name = "test",19 .name = "test",
...@@ -16,7 +24,8 @@ pub fn build(b: *std.Build) void {...@@ -16,7 +24,8 @@ pub fn build(b: *std.Build) void {
16 exe.addCSourceFile("empty.c", &[0][]const u8{});24 exe.addCSourceFile("empty.c", &[0][]const u8{});
17 exe.linkLibC();25 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;
20 run_cmd.expectStdOutEqual("Hello!\n");29 run_cmd.expectStdOutEqual("Hello!\n");
21 test_step.dependOn(&run_cmd.step);30 test_step.dependOn(&run_cmd.step);
22}31}
test/link/macho/entry/build.zig+11-4
...@@ -1,11 +1,18 @@...@@ -1,11 +1,18 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub 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");9 add(b, test_step, .Debug);
7 test_step.dependOn(b.getInstallStep());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 {
9 const exe = b.addExecutable(.{16 const exe = b.addExecutable(.{
10 .name = "main",17 .name = "main",
11 .optimize = optimize,18 .optimize = optimize,
...@@ -15,7 +22,7 @@ pub fn build(b: *std.Build) void {...@@ -15,7 +22,7 @@ pub fn build(b: *std.Build) void {
15 exe.linkLibC();22 exe.linkLibC();
16 exe.entry_symbol_name = "_non_main";23 exe.entry_symbol_name = "_non_main";
1724
18 const check_exe = exe.checkObject(.macho);25 const check_exe = exe.checkObject();
1926
20 check_exe.checkStart("segname __TEXT");27 check_exe.checkStart("segname __TEXT");
21 check_exe.checkNext("vmaddr {vmaddr}");28 check_exe.checkNext("vmaddr {vmaddr}");
test/link/macho/headerpad/build.zig+25-13
...@@ -1,18 +1,26 @@...@@ -1,18 +1,26 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
33
4pub const requires_symlinks = true;
5pub const requires_macos_sdk = true;
6
4pub fn build(b: *std.Build) void {7pub 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");11 add(b, test_step, .Debug);
8 test_step.dependOn(b.getInstallStep());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 {
10 {18 {
11 // Test -headerpad_max_install_names19 // Test -headerpad_max_install_names
12 const exe = simpleExe(b, optimize);20 const exe = simpleExe(b, optimize, "headerpad_max_install_names");
13 exe.headerpad_max_install_names = true;21 exe.headerpad_max_install_names = true;
1422
15 const check = exe.checkObject(.macho);23 const check = exe.checkObject();
16 check.checkStart("sectname __text");24 check.checkStart("sectname __text");
17 check.checkNext("offset {offset}");25 check.checkNext("offset {offset}");
1826
...@@ -34,10 +42,10 @@ pub fn build(b: *std.Build) void {...@@ -34,10 +42,10 @@ pub fn build(b: *std.Build) void {
3442
35 {43 {
36 // Test -headerpad44 // Test -headerpad
37 const exe = simpleExe(b, optimize);45 const exe = simpleExe(b, optimize, "headerpad");
38 exe.headerpad_size = 0x10000;46 exe.headerpad_size = 0x10000;
3947
40 const check = exe.checkObject(.macho);48 const check = exe.checkObject();
41 check.checkStart("sectname __text");49 check.checkStart("sectname __text");
42 check.checkNext("offset {offset}");50 check.checkNext("offset {offset}");
43 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });51 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
...@@ -50,11 +58,11 @@ pub fn build(b: *std.Build) void {...@@ -50,11 +58,11 @@ pub fn build(b: *std.Build) void {
5058
51 {59 {
52 // Test both flags with -headerpad overriding -headerpad_max_install_names60 // Test both flags with -headerpad overriding -headerpad_max_install_names
53 const exe = simpleExe(b, optimize);61 const exe = simpleExe(b, optimize, "headerpad_overriding");
54 exe.headerpad_max_install_names = true;62 exe.headerpad_max_install_names = true;
55 exe.headerpad_size = 0x10000;63 exe.headerpad_size = 0x10000;
5664
57 const check = exe.checkObject(.macho);65 const check = exe.checkObject();
58 check.checkStart("sectname __text");66 check.checkStart("sectname __text");
59 check.checkNext("offset {offset}");67 check.checkNext("offset {offset}");
60 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });68 check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
...@@ -67,11 +75,11 @@ pub fn build(b: *std.Build) void {...@@ -67,11 +75,11 @@ pub fn build(b: *std.Build) void {
6775
68 {76 {
69 // Test both flags with -headerpad_max_install_names overriding -headerpad77 // 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");
71 exe.headerpad_size = 0x1000;79 exe.headerpad_size = 0x1000;
72 exe.headerpad_max_install_names = true;80 exe.headerpad_max_install_names = true;
7381
74 const check = exe.checkObject(.macho);82 const check = exe.checkObject();
75 check.checkStart("sectname __text");83 check.checkStart("sectname __text");
76 check.checkNext("offset {offset}");84 check.checkNext("offset {offset}");
7785
...@@ -92,9 +100,13 @@ pub fn build(b: *std.Build) void {...@@ -92,9 +100,13 @@ pub fn build(b: *std.Build) void {
92 }100 }
93}101}
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 {
96 const exe = b.addExecutable(.{108 const exe = b.addExecutable(.{
97 .name = "main",109 .name = name,
98 .optimize = optimize,110 .optimize = optimize,
99 });111 });
100 exe.addCSourceFile("main.c", &.{});112 exe.addCSourceFile("main.c", &.{});
test/link/macho/linksection/build.zig+13-5
...@@ -1,11 +1,19 @@...@@ -1,11 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
5 const target = std.zig.CrossTarget{ .os_tag = .macos };7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");9 add(b, test_step, .Debug);
8 test_step.dependOn(b.getInstallStep());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
10 const obj = b.addObject(.{18 const obj = b.addObject(.{
11 .name = "test",19 .name = "test",
...@@ -14,7 +22,7 @@ pub fn build(b: *std.Build) void {...@@ -14,7 +22,7 @@ pub fn build(b: *std.Build) void {
14 .target = target,22 .target = target,
15 });23 });
1624
17 const check = obj.checkObject(.macho);25 const check = obj.checkObject();
1826
19 check.checkInSymtab();27 check.checkInSymtab();
20 check.checkNext("{*} (__DATA,__TestGlobal) external _test_global");28 check.checkNext("{*} (__DATA,__TestGlobal) external _test_global");
test/link/macho/needed_framework/build.zig+12-4
...@@ -1,11 +1,19 @@...@@ -1,11 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
3pub fn build(b: *std.Build) void {6pub 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);
7 test_step.dependOn(b.getInstallStep());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 {
9 // -dead_strip_dylibs17 // -dead_strip_dylibs
10 // -needed_framework Cocoa18 // -needed_framework Cocoa
11 const exe = b.addExecutable(.{19 const exe = b.addExecutable(.{
...@@ -17,7 +25,7 @@ pub fn build(b: *std.Build) void {...@@ -17,7 +25,7 @@ pub fn build(b: *std.Build) void {
17 exe.linkFrameworkNeeded("Cocoa");25 exe.linkFrameworkNeeded("Cocoa");
18 exe.dead_strip_dylibs = true;26 exe.dead_strip_dylibs = true;
1927
20 const check = exe.checkObject(.macho);28 const check = exe.checkObject();
21 check.checkStart("cmd LOAD_DYLIB");29 check.checkStart("cmd LOAD_DYLIB");
22 check.checkNext("name {*}Cocoa");30 check.checkNext("name {*}Cocoa");
23 test_step.dependOn(&check.step);31 test_step.dependOn(&check.step);
test/link/macho/needed_library/build.zig+16-8
...@@ -1,11 +1,19 @@...@@ -1,11 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test the program");9 add(b, test_step, .Debug);
8 test_step.dependOn(b.getInstallStep());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
10 const dylib = b.addSharedLibrary(.{18 const dylib = b.addSharedLibrary(.{
11 .name = "a",19 .name = "a",
...@@ -15,7 +23,6 @@ pub fn build(b: *std.Build) void {...@@ -15,7 +23,6 @@ pub fn build(b: *std.Build) void {
15 });23 });
16 dylib.addCSourceFile("a.c", &.{});24 dylib.addCSourceFile("a.c", &.{});
17 dylib.linkLibC();25 dylib.linkLibC();
18 dylib.install();
1926
20 // -dead_strip_dylibs27 // -dead_strip_dylibs
21 // -needed-la28 // -needed-la
...@@ -27,14 +34,15 @@ pub fn build(b: *std.Build) void {...@@ -27,14 +34,15 @@ pub fn build(b: *std.Build) void {
27 exe.addCSourceFile("main.c", &[0][]const u8{});34 exe.addCSourceFile("main.c", &[0][]const u8{});
28 exe.linkLibC();35 exe.linkLibC();
29 exe.linkSystemLibraryNeeded("a");36 exe.linkSystemLibraryNeeded("a");
30 exe.addLibraryPath(b.pathFromRoot("zig-out/lib"));37 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
31 exe.addRPath(b.pathFromRoot("zig-out/lib"));38 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
32 exe.dead_strip_dylibs = true;39 exe.dead_strip_dylibs = true;
3340
34 const check = exe.checkObject(.macho);41 const check = exe.checkObject();
35 check.checkStart("cmd LOAD_DYLIB");42 check.checkStart("cmd LOAD_DYLIB");
36 check.checkNext("name @rpath/liba.dylib");43 check.checkNext("name @rpath/liba.dylib");
3744
38 const run_cmd = check.runAndCompare();45 const run_cmd = check.runAndCompare();
46 run_cmd.expectStdOutEqual("");
39 test_step.dependOn(&run_cmd.step);47 test_step.dependOn(&run_cmd.step);
40}48}
test/link/macho/objc/build.zig+14-3
...@@ -1,10 +1,19 @@...@@ -1,10 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
3pub fn build(b: *std.Build) void {6pub 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 {
8 const exe = b.addExecutable(.{17 const exe = b.addExecutable(.{
9 .name = "test",18 .name = "test",
10 .optimize = optimize,19 .optimize = optimize,
...@@ -17,6 +26,8 @@ pub fn build(b: *std.Build) void {...@@ -17,6 +26,8 @@ pub fn build(b: *std.Build) void {
17 // populate paths to the sysroot here.26 // populate paths to the sysroot here.
18 exe.linkFramework("Foundation");27 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("");
21 test_step.dependOn(&run_cmd.step);32 test_step.dependOn(&run_cmd.step);
22}33}
test/link/macho/objcpp/build.zig+11-2
...@@ -1,10 +1,19 @@...@@ -1,10 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
3pub fn build(b: *std.Build) void {6pub 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 {
8 const exe = b.addExecutable(.{17 const exe = b.addExecutable(.{
9 .name = "test",18 .name = "test",
10 .optimize = optimize,19 .optimize = optimize,
test/link/macho/pagezero/build.zig+8-6
...@@ -1,11 +1,13 @@...@@ -1,11 +1,13 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");9 const optimize: std.builtin.OptimizeMode = .Debug;
8 test_step.dependOn(b.getInstallStep());10 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
911
10 {12 {
11 const exe = b.addExecutable(.{13 const exe = b.addExecutable(.{
...@@ -17,7 +19,7 @@ pub fn build(b: *std.Build) void {...@@ -17,7 +19,7 @@ pub fn build(b: *std.Build) void {
17 exe.linkLibC();19 exe.linkLibC();
18 exe.pagezero_size = 0x4000;20 exe.pagezero_size = 0x4000;
1921
20 const check = exe.checkObject(.macho);22 const check = exe.checkObject();
21 check.checkStart("LC 0");23 check.checkStart("LC 0");
22 check.checkNext("segname __PAGEZERO");24 check.checkNext("segname __PAGEZERO");
23 check.checkNext("vmaddr 0");25 check.checkNext("vmaddr 0");
...@@ -39,7 +41,7 @@ pub fn build(b: *std.Build) void {...@@ -39,7 +41,7 @@ pub fn build(b: *std.Build) void {
39 exe.linkLibC();41 exe.linkLibC();
40 exe.pagezero_size = 0;42 exe.pagezero_size = 0;
4143
42 const check = exe.checkObject(.macho);44 const check = exe.checkObject();
43 check.checkStart("LC 0");45 check.checkStart("LC 0");
44 check.checkNext("segname __TEXT");46 check.checkNext("segname __TEXT");
45 check.checkNext("vmaddr 0");47 check.checkNext("vmaddr 0");
test/link/macho/search_strategy/build.zig+26-20
...@@ -1,34 +1,41 @@...@@ -1,34 +1,41 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");9 add(b, test_step, .Debug);
8 test_step.dependOn(b.getInstallStep());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
10 {18 {
11 // -search_dylibs_first19 // -search_dylibs_first
12 const exe = createScenario(b, optimize, target);20 const exe = createScenario(b, optimize, target, "search_dylibs_first");
13 exe.search_strategy = .dylibs_first;21 exe.search_strategy = .dylibs_first;
1422
15 const check = exe.checkObject(.macho);23 const check = exe.checkObject();
16 check.checkStart("cmd LOAD_DYLIB");24 check.checkStart("cmd LOAD_DYLIB");
17 check.checkNext("name @rpath/liba.dylib");25 check.checkNext("name @rpath/libsearch_dylibs_first.dylib");
1826
19 const run = check.runAndCompare();27 const run = check.runAndCompare();
20 run.cwd = b.pathFromRoot(".");
21 run.expectStdOutEqual("Hello world");28 run.expectStdOutEqual("Hello world");
22 test_step.dependOn(&run.step);29 test_step.dependOn(&run.step);
23 }30 }
2431
25 {32 {
26 // -search_paths_first33 // -search_paths_first
27 const exe = createScenario(b, optimize, target);34 const exe = createScenario(b, optimize, target, "search_paths_first");
28 exe.search_strategy = .paths_first;35 exe.search_strategy = .paths_first;
2936
30 const run = std.Build.EmulatableRunStep.create(b, "run", exe);37 const run = b.addRunArtifact(exe);
31 run.cwd = b.pathFromRoot(".");38 run.skip_foreign_checks = true;
32 run.expectStdOutEqual("Hello world");39 run.expectStdOutEqual("Hello world");
33 test_step.dependOn(&run.step);40 test_step.dependOn(&run.step);
34 }41 }
...@@ -38,9 +45,10 @@ fn createScenario(...@@ -38,9 +45,10 @@ fn createScenario(
38 b: *std.Build,45 b: *std.Build,
39 optimize: std.builtin.OptimizeMode,46 optimize: std.builtin.OptimizeMode,
40 target: std.zig.CrossTarget,47 target: std.zig.CrossTarget,
48 name: []const u8,
41) *std.Build.CompileStep {49) *std.Build.CompileStep {
42 const static = b.addStaticLibrary(.{50 const static = b.addStaticLibrary(.{
43 .name = "a",51 .name = name,
44 .optimize = optimize,52 .optimize = optimize,
45 .target = target,53 .target = target,
46 });54 });
...@@ -49,10 +57,9 @@ fn createScenario(...@@ -49,10 +57,9 @@ fn createScenario(
49 static.override_dest_dir = std.Build.InstallDir{57 static.override_dest_dir = std.Build.InstallDir{
50 .custom = "static",58 .custom = "static",
51 };59 };
52 static.install();
5360
54 const dylib = b.addSharedLibrary(.{61 const dylib = b.addSharedLibrary(.{
55 .name = "a",62 .name = name,
56 .version = .{ .major = 1, .minor = 0 },63 .version = .{ .major = 1, .minor = 0 },
57 .optimize = optimize,64 .optimize = optimize,
58 .target = target,65 .target = target,
...@@ -62,18 +69,17 @@ fn createScenario(...@@ -62,18 +69,17 @@ fn createScenario(
62 dylib.override_dest_dir = std.Build.InstallDir{69 dylib.override_dest_dir = std.Build.InstallDir{
63 .custom = "dynamic",70 .custom = "dynamic",
64 };71 };
65 dylib.install();
6672
67 const exe = b.addExecutable(.{73 const exe = b.addExecutable(.{
68 .name = "main",74 .name = name,
69 .optimize = optimize,75 .optimize = optimize,
70 .target = target,76 .target = target,
71 });77 });
72 exe.addCSourceFile("main.c", &.{});78 exe.addCSourceFile("main.c", &.{});
73 exe.linkSystemLibraryName("a");79 exe.linkSystemLibraryName(name);
74 exe.linkLibC();80 exe.linkLibC();
75 exe.addLibraryPath(b.pathFromRoot("zig-out/static"));81 exe.addLibraryPathDirectorySource(static.getOutputDirectorySource());
76 exe.addLibraryPath(b.pathFromRoot("zig-out/dynamic"));82 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
77 exe.addRPath(b.pathFromRoot("zig-out/dynamic"));83 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
78 return exe;84 return exe;
79}85}
test/link/macho/stack_size/build.zig+14-5
...@@ -1,11 +1,19 @@...@@ -1,11 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test");9 add(b, test_step, .Debug);
8 test_step.dependOn(b.getInstallStep());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
10 const exe = b.addExecutable(.{18 const exe = b.addExecutable(.{
11 .name = "main",19 .name = "main",
...@@ -16,10 +24,11 @@ pub fn build(b: *std.Build) void {...@@ -16,10 +24,11 @@ pub fn build(b: *std.Build) void {
16 exe.linkLibC();24 exe.linkLibC();
17 exe.stack_size = 0x100000000;25 exe.stack_size = 0x100000000;
1826
19 const check_exe = exe.checkObject(.macho);27 const check_exe = exe.checkObject();
20 check_exe.checkStart("cmd MAIN");28 check_exe.checkStart("cmd MAIN");
21 check_exe.checkNext("stacksize 100000000");29 check_exe.checkNext("stacksize 100000000");
2230
23 const run = check_exe.runAndCompare();31 const run = check_exe.runAndCompare();
32 run.expectStdOutEqual("");
24 test_step.dependOn(&run.step);33 test_step.dependOn(&run.step);
25}34}
test/link/macho/strict_validation/build.zig+13-5
...@@ -1,12 +1,20 @@...@@ -1,12 +1,20 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
33
4pub const requires_symlinks = true;
5
4pub fn build(b: *std.Build) void {6pub fn build(b: *std.Build) void {
5 const optimize = b.standardOptimizeOption(.{});7 const test_step = b.step("test", "Test it");
6 const target: std.zig.CrossTarget = .{ .os_tag = .macos };8 b.default_step = test_step;
79
8 const test_step = b.step("test", "Test");10 add(b, test_step, .Debug);
9 test_step.dependOn(b.getInstallStep());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
11 const exe = b.addExecutable(.{19 const exe = b.addExecutable(.{
12 .name = "main",20 .name = "main",
...@@ -16,7 +24,7 @@ pub fn build(b: *std.Build) void {...@@ -16,7 +24,7 @@ pub fn build(b: *std.Build) void {
16 });24 });
17 exe.linkLibC();25 exe.linkLibC();
1826
19 const check_exe = exe.checkObject(.macho);27 const check_exe = exe.checkObject();
2028
21 check_exe.checkStart("cmd SEGMENT_64");29 check_exe.checkStart("cmd SEGMENT_64");
22 check_exe.checkNext("segname __LINKEDIT");30 check_exe.checkNext("segname __LINKEDIT");
test/link/macho/tls/build.zig+16-3
...@@ -1,7 +1,18 @@...@@ -1,7 +1,18 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub 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 {
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };16 const target: std.zig.CrossTarget = .{ .os_tag = .macos };
617
7 const lib = b.addSharedLibrary(.{18 const lib = b.addSharedLibrary(.{
...@@ -21,6 +32,8 @@ pub fn build(b: *std.Build) void {...@@ -21,6 +32,8 @@ pub fn build(b: *std.Build) void {
21 test_exe.linkLibrary(lib);32 test_exe.linkLibrary(lib);
22 test_exe.linkLibC();33 test_exe.linkLibC();
2334
24 const test_step = b.step("test", "Test it");35 const run = test_exe.run();
25 test_step.dependOn(&test_exe.step);36 run.skip_foreign_checks = true;
37
38 test_step.dependOn(&run.step);
26}39}
test/link/macho/unwind_info/build.zig+19-8
...@@ -1,14 +1,23 @@...@@ -1,14 +1,23 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
33
4pub const requires_symlinks = true;
5
4pub fn build(b: *std.Build) void {6pub fn build(b: *std.Build) void {
5 const optimize = b.standardOptimizeOption(.{});7 const test_step = b.step("test", "Test it");
6 const target: std.zig.CrossTarget = .{ .os_tag = .macos };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);19 testUnwindInfo(b, test_step, optimize, target, false, "no-dead-strip");
11 testUnwindInfo(b, test_step, optimize, target, true);20 testUnwindInfo(b, test_step, optimize, target, true, "yes-dead-strip");
12}21}
1322
14fn testUnwindInfo(23fn testUnwindInfo(
...@@ -17,11 +26,12 @@ fn testUnwindInfo(...@@ -17,11 +26,12 @@ fn testUnwindInfo(
17 optimize: std.builtin.OptimizeMode,26 optimize: std.builtin.OptimizeMode,
18 target: std.zig.CrossTarget,27 target: std.zig.CrossTarget,
19 dead_strip: bool,28 dead_strip: bool,
29 name: []const u8,
20) void {30) void {
21 const exe = createScenario(b, optimize, target);31 const exe = createScenario(b, optimize, target, name);
22 exe.link_gc_sections = dead_strip;32 exe.link_gc_sections = dead_strip;
2333
24 const check = exe.checkObject(.macho);34 const check = exe.checkObject();
25 check.checkStart("segname __TEXT");35 check.checkStart("segname __TEXT");
26 check.checkNext("sectname __gcc_except_tab");36 check.checkNext("sectname __gcc_except_tab");
27 check.checkNext("sectname __unwind_info");37 check.checkNext("sectname __unwind_info");
...@@ -54,9 +64,10 @@ fn createScenario(...@@ -54,9 +64,10 @@ fn createScenario(
54 b: *std.Build,64 b: *std.Build,
55 optimize: std.builtin.OptimizeMode,65 optimize: std.builtin.OptimizeMode,
56 target: std.zig.CrossTarget,66 target: std.zig.CrossTarget,
67 name: []const u8,
57) *std.Build.CompileStep {68) *std.Build.CompileStep {
58 const exe = b.addExecutable(.{69 const exe = b.addExecutable(.{
59 .name = "test",70 .name = name,
60 .optimize = optimize,71 .optimize = optimize,
61 .target = target,72 .target = target,
62 });73 });
test/link/macho/uuid/build.zig+23-62
...@@ -1,14 +1,16 @@...@@ -1,14 +1,16 @@
1const std = @import("std");1const std = @import("std");
2const Builder = std.Build.Builder;
3const CompileStep = std.Build.CompileStep;2const CompileStep = std.Build.CompileStep;
4const FileSource = std.Build.FileSource;3const FileSource = std.Build.FileSource;
5const Step = std.Build.Step;4const Step = std.Build.Step;
65
6pub const requires_symlinks = true;
7
7pub fn build(b: *std.Build) void {8pub fn build(b: *std.Build) void {
8 const test_step = b.step("test", "Test");9 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.
12 const aarch64_macos = std.zig.CrossTarget{14 const aarch64_macos = std.zig.CrossTarget{
13 .cpu_arch = .aarch64,15 .cpu_arch = .aarch64,
14 .os_tag = .macos,16 .os_tag = .macos,
...@@ -38,13 +40,15 @@ fn testUuid(...@@ -38,13 +40,15 @@ fn testUuid(
38 // stay the same across builds.40 // stay the same across builds.
39 {41 {
40 const dylib = simpleDylib(b, optimize, target);42 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";
42 install_step.step.dependOn(&dylib.step);45 install_step.step.dependOn(&dylib.step);
43 }46 }
44 {47 {
45 const dylib = simpleDylib(b, optimize, target);48 const dylib = simpleDylib(b, optimize, target);
46 dylib.strip = true;49 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";
48 install_step.step.dependOn(&dylib.step);52 install_step.step.dependOn(&dylib.step);
49 }53 }
5054
...@@ -68,86 +72,43 @@ fn simpleDylib(...@@ -68,86 +72,43 @@ fn simpleDylib(
68 return dylib;72 return dylib;
69}73}
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
114const CompareUuid = struct {75const CompareUuid = struct {
115 pub const base_id = .custom;76 pub const base_id = .custom;
11677
117 step: Step,78 step: Step,
118 builder: *Builder,
119 lhs: []const u8,79 lhs: []const u8,
120 rhs: []const u8,80 rhs: []const u8,
12181
122 pub fn create(builder: *Builder, lhs: []const u8, rhs: []const u8) *CompareUuid {82 pub fn create(owner: *std.Build, lhs: []const u8, rhs: []const u8) *CompareUuid {
123 const self = builder.allocator.create(CompareUuid) catch @panic("OOM");83 const self = owner.allocator.create(CompareUuid) catch @panic("OOM");
124 self.* = CompareUuid{84 self.* = CompareUuid{
125 .builder = builder,85 .step = Step.init(.{
126 .step = Step.init(86 .id = base_id,
127 .custom,87 .name = owner.fmt("compare uuid: {s} and {s}", .{
128 builder.fmt("compare uuid: {s} and {s}", .{
129 lhs,88 lhs,
130 rhs,89 rhs,
131 }),90 }),
132 builder.allocator,91 .owner = owner,
133 make,92 .makeFn = make,
134 ),93 }),
135 .lhs = lhs,94 .lhs = lhs,
136 .rhs = rhs,95 .rhs = rhs,
137 };96 };
138 return self;97 return self;
139 }98 }
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;
142 const self = @fieldParentPtr(CompareUuid, "step", step);103 const self = @fieldParentPtr(CompareUuid, "step", step);
143 const gpa = self.builder.allocator;104 const gpa = b.allocator;
144105
145 var lhs_uuid: [16]u8 = undefined;106 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);
147 try parseUuid(gpa, lhs_path, &lhs_uuid);108 try parseUuid(gpa, lhs_path, &lhs_uuid);
148109
149 var rhs_uuid: [16]u8 = undefined;110 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);
151 try parseUuid(gpa, rhs_path, &rhs_uuid);112 try parseUuid(gpa, rhs_path, &rhs_uuid);
152113
153 try std.testing.expectEqualStrings(&lhs_uuid, &rhs_uuid);114 try std.testing.expectEqualStrings(&lhs_uuid, &rhs_uuid);
test/link/macho/weak_framework/build.zig+12-4
...@@ -1,11 +1,19 @@...@@ -1,11 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4pub const requires_macos_sdk = true;
5
3pub fn build(b: *std.Build) void {6pub 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);
7 test_step.dependOn(b.getInstallStep());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 {
9 const exe = b.addExecutable(.{17 const exe = b.addExecutable(.{
10 .name = "test",18 .name = "test",
11 .optimize = optimize,19 .optimize = optimize,
...@@ -14,7 +22,7 @@ pub fn build(b: *std.Build) void {...@@ -14,7 +22,7 @@ pub fn build(b: *std.Build) void {
14 exe.linkLibC();22 exe.linkLibC();
15 exe.linkFrameworkWeak("Cocoa");23 exe.linkFrameworkWeak("Cocoa");
1624
17 const check = exe.checkObject(.macho);25 const check = exe.checkObject();
18 check.checkStart("cmd LOAD_WEAK_DYLIB");26 check.checkStart("cmd LOAD_WEAK_DYLIB");
19 check.checkNext("name {*}Cocoa");27 check.checkNext("name {*}Cocoa");
20 test_step.dependOn(&check.step);28 test_step.dependOn(&check.step);
test/link/macho/weak_library/build.zig+15-7
...@@ -1,11 +1,19 @@...@@ -1,11 +1,19 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_symlinks = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
5 const target: std.zig.CrossTarget = .{ .os_tag = .macos };7 b.default_step = test_step;
68
7 const test_step = b.step("test", "Test the program");9 add(b, test_step, .Debug);
8 test_step.dependOn(b.getInstallStep());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
10 const dylib = b.addSharedLibrary(.{18 const dylib = b.addSharedLibrary(.{
11 .name = "a",19 .name = "a",
...@@ -25,10 +33,10 @@ pub fn build(b: *std.Build) void {...@@ -25,10 +33,10 @@ pub fn build(b: *std.Build) void {
25 exe.addCSourceFile("main.c", &[0][]const u8{});33 exe.addCSourceFile("main.c", &[0][]const u8{});
26 exe.linkLibC();34 exe.linkLibC();
27 exe.linkSystemLibraryWeak("a");35 exe.linkSystemLibraryWeak("a");
28 exe.addLibraryPath(b.pathFromRoot("zig-out/lib"));36 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
29 exe.addRPath(b.pathFromRoot("zig-out/lib"));37 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
3038
31 const check = exe.checkObject(.macho);39 const check = exe.checkObject();
32 check.checkStart("cmd LOAD_WEAK_DYLIB");40 check.checkStart("cmd LOAD_WEAK_DYLIB");
33 check.checkNext("name @rpath/liba.dylib");41 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 @@...@@ -1,22 +1,31 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");6 const test_step = b.step("test", "Test it");
5 test_step.dependOn(b.getInstallStep());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 {
7 // The code in question will pull-in compiler-rt,16 // The code in question will pull-in compiler-rt,
8 // and therefore link with its archive file.17 // and therefore link with its archive file.
9 const lib = b.addSharedLibrary(.{18 const lib = b.addSharedLibrary(.{
10 .name = "main",19 .name = "main",
11 .root_source_file = .{ .path = "main.zig" },20 .root_source_file = .{ .path = "main.zig" },
12 .optimize = b.standardOptimizeOption(.{}),21 .optimize = optimize,
13 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },22 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
14 });23 });
15 lib.use_llvm = false;24 lib.use_llvm = false;
16 lib.use_lld = false;25 lib.use_lld = false;
17 lib.strip = false;26 lib.strip = false;
1827
19 const check = lib.checkObject(.wasm);28 const check = lib.checkObject();
20 check.checkStart("Section custom");29 check.checkStart("Section custom");
21 check.checkNext("name __truncsfhf2"); // Ensure it was imported and resolved30 check.checkNext("name __truncsfhf2"); // Ensure it was imported and resolved
2231
test/link/wasm/basic-features/build.zig+5-2
...@@ -1,11 +1,13 @@...@@ -1,11 +1,13 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 // Library with explicitly set cpu features6 // Library with explicitly set cpu features
5 const lib = b.addSharedLibrary(.{7 const lib = b.addSharedLibrary(.{
6 .name = "lib",8 .name = "lib",
7 .root_source_file = .{ .path = "main.zig" },9 .root_source_file = .{ .path = "main.zig" },
8 .optimize = b.standardOptimizeOption(.{}),10 .optimize = .Debug,
9 .target = .{11 .target = .{
10 .cpu_arch = .wasm32,12 .cpu_arch = .wasm32,
11 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },13 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
...@@ -17,11 +19,12 @@ pub fn build(b: *std.Build) void {...@@ -17,11 +19,12 @@ pub fn build(b: *std.Build) void {
17 lib.use_lld = false;19 lib.use_lld = false;
1820
19 // Verify the result contains the features explicitly set on the target for the library.21 // 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();
21 check.checkStart("name target_features");23 check.checkStart("name target_features");
22 check.checkNext("features 1");24 check.checkNext("features 1");
23 check.checkNext("+ atomics");25 check.checkNext("+ atomics");
2426
25 const test_step = b.step("test", "Run linker test");27 const test_step = b.step("test", "Run linker test");
26 test_step.dependOn(&check.step);28 test_step.dependOn(&check.step);
29 b.default_step = test_step;
27}30}
test/link/wasm/bss/build.zig+6-3
...@@ -1,14 +1,16 @@...@@ -1,14 +1,16 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");6 const test_step = b.step("test", "Test");
5 test_step.dependOn(b.getInstallStep());7 b.default_step = test_step;
68
7 const lib = b.addSharedLibrary(.{9 const lib = b.addSharedLibrary(.{
8 .name = "lib",10 .name = "lib",
9 .root_source_file = .{ .path = "lib.zig" },11 .root_source_file = .{ .path = "lib.zig" },
10 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },12 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),13 .optimize = .Debug,
12 });14 });
13 lib.use_llvm = false;15 lib.use_llvm = false;
14 lib.use_lld = false;16 lib.use_lld = false;
...@@ -17,7 +19,7 @@ pub fn build(b: *std.Build) void {...@@ -17,7 +19,7 @@ pub fn build(b: *std.Build) void {
17 lib.import_memory = true;19 lib.import_memory = true;
18 lib.install();20 lib.install();
1921
20 const check_lib = lib.checkObject(.wasm);22 const check_lib = lib.checkObject();
2123
22 // since we import memory, make sure it exists with the correct naming24 // since we import memory, make sure it exists with the correct naming
23 check_lib.checkStart("Section import");25 check_lib.checkStart("Section import");
...@@ -36,5 +38,6 @@ pub fn build(b: *std.Build) void {...@@ -36,5 +38,6 @@ pub fn build(b: *std.Build) void {
36 check_lib.checkNext("name .rodata");38 check_lib.checkNext("name .rodata");
37 check_lib.checkNext("index 1"); // bss section always last39 check_lib.checkNext("index 1"); // bss section always last
38 check_lib.checkNext("name .bss");40 check_lib.checkNext("name .bss");
41
39 test_step.dependOn(&check_lib.step);42 test_step.dependOn(&check_lib.step);
40}43}
test/link/wasm/export-data/build.zig+7-2
...@@ -2,7 +2,12 @@ const std = @import("std");...@@ -2,7 +2,12 @@ const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");4 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
7 const lib = b.addSharedLibrary(.{12 const lib = b.addSharedLibrary(.{
8 .name = "lib",13 .name = "lib",
...@@ -14,7 +19,7 @@ pub fn build(b: *std.Build) void {...@@ -14,7 +19,7 @@ pub fn build(b: *std.Build) void {
14 lib.export_symbol_names = &.{ "foo", "bar" };19 lib.export_symbol_names = &.{ "foo", "bar" };
15 lib.global_base = 0; // put data section at address 0 to make data symbols easier to parse20 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
19 check_lib.checkStart("Section global");24 check_lib.checkStart("Section global");
20 check_lib.checkNext("entries 3");25 check_lib.checkNext("entries 3");
test/link/wasm/export/build.zig+14-5
...@@ -1,8 +1,18 @@...@@ -1,8 +1,18 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub 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 {
6 const no_export = b.addSharedLibrary(.{16 const no_export = b.addSharedLibrary(.{
7 .name = "no-export",17 .name = "no-export",
8 .root_source_file = .{ .path = "main.zig" },18 .root_source_file = .{ .path = "main.zig" },
...@@ -32,25 +42,24 @@ pub fn build(b: *std.Build) void {...@@ -32,25 +42,24 @@ pub fn build(b: *std.Build) void {
32 force_export.use_llvm = false;42 force_export.use_llvm = false;
33 force_export.use_lld = false;43 force_export.use_lld = false;
3444
35 const check_no_export = no_export.checkObject(.wasm);45 const check_no_export = no_export.checkObject();
36 check_no_export.checkStart("Section export");46 check_no_export.checkStart("Section export");
37 check_no_export.checkNext("entries 1");47 check_no_export.checkNext("entries 1");
38 check_no_export.checkNext("name memory");48 check_no_export.checkNext("name memory");
39 check_no_export.checkNext("kind memory");49 check_no_export.checkNext("kind memory");
4050
41 const check_dynamic_export = dynamic_export.checkObject(.wasm);51 const check_dynamic_export = dynamic_export.checkObject();
42 check_dynamic_export.checkStart("Section export");52 check_dynamic_export.checkStart("Section export");
43 check_dynamic_export.checkNext("entries 2");53 check_dynamic_export.checkNext("entries 2");
44 check_dynamic_export.checkNext("name foo");54 check_dynamic_export.checkNext("name foo");
45 check_dynamic_export.checkNext("kind function");55 check_dynamic_export.checkNext("kind function");
4656
47 const check_force_export = force_export.checkObject(.wasm);57 const check_force_export = force_export.checkObject();
48 check_force_export.checkStart("Section export");58 check_force_export.checkStart("Section export");
49 check_force_export.checkNext("entries 2");59 check_force_export.checkNext("entries 2");
50 check_force_export.checkNext("name foo");60 check_force_export.checkNext("name foo");
51 check_force_export.checkNext("kind function");61 check_force_export.checkNext("kind function");
5262
53 const test_step = b.step("test", "Run linker test");
54 test_step.dependOn(&check_no_export.step);63 test_step.dependOn(&check_no_export.step);
55 test_step.dependOn(&check_dynamic_export.step);64 test_step.dependOn(&check_dynamic_export.step);
56 test_step.dependOn(&check_force_export.step);65 test_step.dependOn(&check_force_export.step);
test/link/wasm/extern-mangle/build.zig+11-5
...@@ -1,20 +1,26 @@...@@ -1,20 +1,26 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");4 const test_step = b.step("test", "Test it");
5 test_step.dependOn(b.getInstallStep());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 {
7 const lib = b.addSharedLibrary(.{14 const lib = b.addSharedLibrary(.{
8 .name = "lib",15 .name = "lib",
9 .root_source_file = .{ .path = "lib.zig" },16 .root_source_file = .{ .path = "lib.zig" },
10 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },17 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),18 .optimize = optimize,
12 });19 });
13 lib.import_symbols = true; // import `a` and `b`20 lib.import_symbols = true; // import `a` and `b`
14 lib.rdynamic = true; // export `foo`21 lib.rdynamic = true; // export `foo`
15 lib.install();
1622
17 const check_lib = lib.checkObject(.wasm);23 const check_lib = lib.checkObject();
18 check_lib.checkStart("Section import");24 check_lib.checkStart("Section import");
19 check_lib.checkNext("entries 2"); // a.hello & b.hello25 check_lib.checkNext("entries 2"); // a.hello & b.hello
20 check_lib.checkNext("module a");26 check_lib.checkNext("module a");
test/link/wasm/extern/build.zig+15-3
...@@ -1,19 +1,31 @@...@@ -1,19 +1,31 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub 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 {
4 const exe = b.addExecutable(.{16 const exe = b.addExecutable(.{
5 .name = "extern",17 .name = "extern",
6 .root_source_file = .{ .path = "main.zig" },18 .root_source_file = .{ .path = "main.zig" },
7 .optimize = b.standardOptimizeOption(.{}),19 .optimize = optimize,
8 .target = .{ .cpu_arch = .wasm32, .os_tag = .wasi },20 .target = .{ .cpu_arch = .wasm32, .os_tag = .wasi },
9 });21 });
10 exe.addCSourceFile("foo.c", &.{});22 exe.addCSourceFile("foo.c", &.{});
11 exe.use_llvm = false;23 exe.use_llvm = false;
12 exe.use_lld = false;24 exe.use_lld = false;
1325
14 const run = exe.runEmulatable();26 const run = b.addRunArtifact(exe);
27 run.skip_foreign_checks = true;
15 run.expectStdOutEqual("Result: 30");28 run.expectStdOutEqual("Result: 30");
1629
17 const test_step = b.step("test", "Run linker test");
18 test_step.dependOn(&run.step);30 test_step.dependOn(&run.step);
19}31}
test/link/wasm/function-table/build.zig+16-9
...@@ -1,13 +1,20 @@...@@ -1,13 +1,20 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub 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");9 add(b, test_step, .Debug);
7 test_step.dependOn(b.getInstallStep());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 {
9 const import_table = b.addSharedLibrary(.{16 const import_table = b.addSharedLibrary(.{
10 .name = "lib",17 .name = "import_table",
11 .root_source_file = .{ .path = "lib.zig" },18 .root_source_file = .{ .path = "lib.zig" },
12 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },19 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
13 .optimize = optimize,20 .optimize = optimize,
...@@ -17,7 +24,7 @@ pub fn build(b: *std.Build) void {...@@ -17,7 +24,7 @@ pub fn build(b: *std.Build) void {
17 import_table.import_table = true;24 import_table.import_table = true;
1825
19 const export_table = b.addSharedLibrary(.{26 const export_table = b.addSharedLibrary(.{
20 .name = "lib",27 .name = "export_table",
21 .root_source_file = .{ .path = "lib.zig" },28 .root_source_file = .{ .path = "lib.zig" },
22 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },29 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
23 .optimize = optimize,30 .optimize = optimize,
...@@ -27,7 +34,7 @@ pub fn build(b: *std.Build) void {...@@ -27,7 +34,7 @@ pub fn build(b: *std.Build) void {
27 export_table.export_table = true;34 export_table.export_table = true;
2835
29 const regular_table = b.addSharedLibrary(.{36 const regular_table = b.addSharedLibrary(.{
30 .name = "lib",37 .name = "regular_table",
31 .root_source_file = .{ .path = "lib.zig" },38 .root_source_file = .{ .path = "lib.zig" },
32 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },39 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
33 .optimize = optimize,40 .optimize = optimize,
...@@ -35,9 +42,9 @@ pub fn build(b: *std.Build) void {...@@ -35,9 +42,9 @@ pub fn build(b: *std.Build) void {
35 regular_table.use_llvm = false;42 regular_table.use_llvm = false;
36 regular_table.use_lld = false;43 regular_table.use_lld = false;
3744
38 const check_import = import_table.checkObject(.wasm);45 const check_import = import_table.checkObject();
39 const check_export = export_table.checkObject(.wasm);46 const check_export = export_table.checkObject();
40 const check_regular = regular_table.checkObject(.wasm);47 const check_regular = regular_table.checkObject();
4148
42 check_import.checkStart("Section import");49 check_import.checkStart("Section import");
43 check_import.checkNext("entries 1");50 check_import.checkNext("entries 1");
test/link/wasm/infer-features/build.zig+6-5
...@@ -1,12 +1,12 @@...@@ -1,12 +1,12 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub const requires_stage2 = true;
4 const optimize = b.standardOptimizeOption(.{});
54
5pub fn build(b: *std.Build) void {
6 // Wasm Object file which we will use to infer the features from6 // Wasm Object file which we will use to infer the features from
7 const c_obj = b.addObject(.{7 const c_obj = b.addObject(.{
8 .name = "c_obj",8 .name = "c_obj",
9 .optimize = optimize,9 .optimize = .Debug,
10 .target = .{10 .target = .{
11 .cpu_arch = .wasm32,11 .cpu_arch = .wasm32,
12 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.bleeding_edge },12 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.bleeding_edge },
...@@ -20,7 +20,7 @@ pub fn build(b: *std.Build) void {...@@ -20,7 +20,7 @@ pub fn build(b: *std.Build) void {
20 const lib = b.addSharedLibrary(.{20 const lib = b.addSharedLibrary(.{
21 .name = "lib",21 .name = "lib",
22 .root_source_file = .{ .path = "main.zig" },22 .root_source_file = .{ .path = "main.zig" },
23 .optimize = optimize,23 .optimize = .Debug,
24 .target = .{24 .target = .{
25 .cpu_arch = .wasm32,25 .cpu_arch = .wasm32,
26 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },26 .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
...@@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void {...@@ -32,7 +32,7 @@ pub fn build(b: *std.Build) void {
32 lib.addObject(c_obj);32 lib.addObject(c_obj);
3333
34 // Verify the result contains the features from the C Object file.34 // Verify the result contains the features from the C Object file.
35 const check = lib.checkObject(.wasm);35 const check = lib.checkObject();
36 check.checkStart("name target_features");36 check.checkStart("name target_features");
37 check.checkNext("features 7");37 check.checkNext("features 7");
38 check.checkNext("+ atomics");38 check.checkNext("+ atomics");
...@@ -45,4 +45,5 @@ pub fn build(b: *std.Build) void {...@@ -45,4 +45,5 @@ pub fn build(b: *std.Build) void {
4545
46 const test_step = b.step("test", "Run linker test");46 const test_step = b.step("test", "Run linker test");
47 test_step.dependOn(&check.step);47 test_step.dependOn(&check.step);
48 b.default_step = test_step;
48}49}
test/link/wasm/producers/build.zig+14-7
...@@ -1,26 +1,33 @@...@@ -1,26 +1,33 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
33
4pub const requires_stage2 = true;
5
4pub fn build(b: *std.Build) void {6pub fn build(b: *std.Build) void {
5 const test_step = b.step("test", "Test");7 const test_step = b.step("test", "Test it");
6 test_step.dependOn(b.getInstallStep());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 {
8 const lib = b.addSharedLibrary(.{17 const lib = b.addSharedLibrary(.{
9 .name = "lib",18 .name = "lib",
10 .root_source_file = .{ .path = "lib.zig" },19 .root_source_file = .{ .path = "lib.zig" },
11 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },20 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
12 .optimize = b.standardOptimizeOption(.{}),21 .optimize = optimize,
13 });22 });
14 lib.use_llvm = false;23 lib.use_llvm = false;
15 lib.use_lld = false;24 lib.use_lld = false;
16 lib.strip = false;25 lib.strip = false;
17 lib.install();26 lib.install();
1827
19 const zig_version = builtin.zig_version;28 const version_fmt = "version " ++ builtin.zig_version_string;
20 var version_buf: [100]u8 = undefined;
21 const version_fmt = std.fmt.bufPrint(&version_buf, "version {}", .{zig_version}) catch unreachable;
2229
23 const check_lib = lib.checkObject(.wasm);30 const check_lib = lib.checkObject();
24 check_lib.checkStart("name producers");31 check_lib.checkStart("name producers");
25 check_lib.checkNext("fields 2");32 check_lib.checkNext("fields 2");
26 check_lib.checkNext("field_name language");33 check_lib.checkNext("field_name language");
test/link/wasm/segments/build.zig+13-4
...@@ -1,21 +1,30 @@...@@ -1,21 +1,30 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");6 const test_step = b.step("test", "Test it");
5 test_step.dependOn(b.getInstallStep());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 {
7 const lib = b.addSharedLibrary(.{16 const lib = b.addSharedLibrary(.{
8 .name = "lib",17 .name = "lib",
9 .root_source_file = .{ .path = "lib.zig" },18 .root_source_file = .{ .path = "lib.zig" },
10 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },19 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),20 .optimize = optimize,
12 });21 });
13 lib.use_llvm = false;22 lib.use_llvm = false;
14 lib.use_lld = false;23 lib.use_lld = false;
15 lib.strip = false;24 lib.strip = false;
16 lib.install();25 lib.install();
1726
18 const check_lib = lib.checkObject(.wasm);27 const check_lib = lib.checkObject();
19 check_lib.checkStart("Section data");28 check_lib.checkStart("Section data");
20 check_lib.checkNext("entries 2"); // rodata & data, no bss because we're exporting memory29 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 @@...@@ -1,14 +1,23 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");6 const test_step = b.step("test", "Test it");
5 test_step.dependOn(b.getInstallStep());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 {
7 const lib = b.addSharedLibrary(.{16 const lib = b.addSharedLibrary(.{
8 .name = "lib",17 .name = "lib",
9 .root_source_file = .{ .path = "lib.zig" },18 .root_source_file = .{ .path = "lib.zig" },
10 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },19 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),20 .optimize = optimize,
12 });21 });
13 lib.use_llvm = false;22 lib.use_llvm = false;
14 lib.use_lld = false;23 lib.use_lld = false;
...@@ -16,7 +25,7 @@ pub fn build(b: *std.Build) void {...@@ -16,7 +25,7 @@ pub fn build(b: *std.Build) void {
16 lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size25 lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size
17 lib.install();26 lib.install();
1827
19 const check_lib = lib.checkObject(.wasm);28 const check_lib = lib.checkObject();
2029
21 // ensure global exists and its initial value is equal to explitic stack size30 // ensure global exists and its initial value is equal to explitic stack size
22 check_lib.checkStart("Section global");31 check_lib.checkStart("Section global");
test/link/wasm/type/build.zig+13-4
...@@ -1,21 +1,30 @@...@@ -1,21 +1,30 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");6 const test_step = b.step("test", "Test it");
5 test_step.dependOn(b.getInstallStep());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 {
7 const lib = b.addSharedLibrary(.{16 const lib = b.addSharedLibrary(.{
8 .name = "lib",17 .name = "lib",
9 .root_source_file = .{ .path = "lib.zig" },18 .root_source_file = .{ .path = "lib.zig" },
10 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },19 .target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
11 .optimize = b.standardOptimizeOption(.{}),20 .optimize = optimize,
12 });21 });
13 lib.use_llvm = false;22 lib.use_llvm = false;
14 lib.use_lld = false;23 lib.use_lld = false;
15 lib.strip = false;24 lib.strip = false;
16 lib.install();25 lib.install();
1726
18 const check_lib = lib.checkObject(.wasm);27 const check_lib = lib.checkObject();
19 check_lib.checkStart("Section type");28 check_lib.checkStart("Section type");
20 // only 2 entries, although we have 3 functions.29 // only 2 entries, although we have 3 functions.
21 // This is to test functions with the same function signature30 // 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 @@...@@ -1,117 +1,218 @@
1const std = @import("std");1pub const SimpleCase = struct {
2const builtin = @import("builtin");2 src_path: []const u8,
3const tests = @import("tests.zig");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 {12pub const BuildCase = struct {
6 cases.add("test/standalone/hello_world/hello.zig");13 build_root: []const u8,
7 cases.addC("test/standalone/hello_world/hello_libc.zig");14 import: type,
15};
816
9 cases.addBuildFile("test/standalone/options/build.zig", .{17pub const simple_cases = [_]SimpleCase{
10 .extra_argv = &.{18 .{
11 "-Dbool_true",19 .src_path = "test/standalone/hello_world/hello.zig",
12 "-Dbool_false=false",20 .all_modes = true,
13 "-Dint=1234",21 },
14 "-De=two",22 .{
15 "-Dstring=hello",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,
16 },43 },
17 });44 },
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 }
6945
70 if (builtin.os.tag == .windows) {46 .{ .src_path = "test/standalone/issue_12471/main.zig" },
71 cases.addBuildFile("test/standalone/windows_spawn/build.zig", .{});47 .{ .src_path = "test/standalone/guess_number/main.zig" },
72 }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", .{53 .{
75 .build_modes = true,54 .src_path = "test/standalone/issue_9402/main.zig",
76 .cross_targets = true,55 .os_filter = .windows,
77 });56 .link_libc = true,
7857 },
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 }
9058
91 // Ensure the development tools are buildable. Alphabetically sorted.59 // Ensure the development tools are buildable. Alphabetically sorted.
92 // No need to build `tools/spirv/grammar.zig`.60 // No need to build `tools/spirv/grammar.zig`.
93 cases.add("tools/extract-grammar.zig");61 .{ .src_path = "tools/extract-grammar.zig" },
94 cases.add("tools/gen_outline_atomics.zig");62 .{ .src_path = "tools/gen_outline_atomics.zig" },
95 cases.add("tools/gen_spirv_spec.zig");63 .{ .src_path = "tools/gen_spirv_spec.zig" },
96 cases.add("tools/gen_stubs.zig");64 .{ .src_path = "tools/gen_stubs.zig" },
97 cases.add("tools/generate_linux_syscalls.zig");65 .{ .src_path = "tools/generate_linux_syscalls.zig" },
98 cases.add("tools/process_headers.zig");66 .{ .src_path = "tools/process_headers.zig" },
99 cases.add("tools/update-license-headers.zig");67 .{ .src_path = "tools/update-license-headers.zig" },
100 cases.add("tools/update-linux-headers.zig");68 .{ .src_path = "tools/update-linux-headers.zig" },
101 cases.add("tools/update_clang_options.zig");69 .{ .src_path = "tools/update_clang_options.zig" },
102 cases.add("tools/update_cpu_features.zig");70 .{ .src_path = "tools/update_cpu_features.zig" },
103 cases.add("tools/update_glibc.zig");71 .{ .src_path = "tools/update_glibc.zig" },
104 cases.add("tools/update_spirv_features.zig");72 .{ .src_path = "tools/update_spirv_features.zig" },
73};
10574
106 cases.addBuildFile("test/standalone/issue_13030/build.zig", .{ .build_modes = true });75pub const build_cases = [_]BuildCase{
107 cases.addBuildFile("test/standalone/emit_asm_and_bin/build.zig", .{});76 .{
108 cases.addBuildFile("test/standalone/issue_12588/build.zig", .{});77 .build_root = "test/standalone/test_runner_path",
109 cases.addBuildFile("test/standalone/embed_generated_file/build.zig", .{});78 .import = @import("standalone/test_runner_path/build.zig"),
110 cases.addBuildFile("test/standalone/extern/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", .{});218const std = @import("std");
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}
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 @@...@@ -1,27 +1,24 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const CrossTarget = std.zig.CrossTarget;
43
5// TODO integrate this with the std.Build executor API4pub fn build(b: *std.Build) void {
6fn isRunnableTarget(t: CrossTarget) bool {5 const test_step = b.step("test", "Test it");
7 if (t.isNative()) return true;6 b.default_step = test_step;
87
9 return (t.getOsTag() == builtin.os.tag and8 add(b, test_step, .Debug);
10 t.getCpuArch() == builtin.cpu.arch);9 add(b, test_step, .ReleaseFast);
10 add(b, test_step, .ReleaseSmall);
11 add(b, test_step, .ReleaseSafe);
11}12}
1213
13pub fn build(b: *std.Build) void {14fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
14 const optimize = b.standardOptimizeOption(.{});15 const target: std.zig.CrossTarget = .{};
15 const target = b.standardTargetOptions(.{});
16
17 const test_step = b.step("test", "Test the program");
1816
19 const exe_c = b.addExecutable(.{17 const exe_c = b.addExecutable(.{
20 .name = "test_c",18 .name = "test_c",
21 .optimize = optimize,19 .optimize = optimize,
22 .target = target,20 .target = target,
23 });21 });
24 b.default_step.dependOn(&exe_c.step);
25 exe_c.addCSourceFile("test.c", &[0][]const u8{});22 exe_c.addCSourceFile("test.c", &[0][]const u8{});
26 exe_c.linkLibC();23 exe_c.linkLibC();
2724
...@@ -47,13 +44,13 @@ pub fn build(b: *std.Build) void {...@@ -47,13 +44,13 @@ pub fn build(b: *std.Build) void {
47 else => {},44 else => {},
48 }45 }
4946
50 if (isRunnableTarget(target)) {47 const run_c_cmd = b.addRunArtifact(exe_c);
51 const run_c_cmd = exe_c.run();48 run_c_cmd.expectExitCode(0);
52 test_step.dependOn(&run_c_cmd.step);49 run_c_cmd.skip_foreign_checks = true;
53 const run_cpp_cmd = exe_cpp.run();50 test_step.dependOn(&run_c_cmd.step);
54 test_step.dependOn(&run_cpp_cmd.step);51
55 } else {52 const run_cpp_cmd = b.addRunArtifact(exe_cpp);
56 test_step.dependOn(&exe_c.step);53 run_cpp_cmd.expectExitCode(0);
57 test_step.dependOn(&exe_cpp.step);54 run_cpp_cmd.skip_foreign_checks = true;
58 }55 test_step.dependOn(&run_cpp_cmd.step);
59}56}
test/standalone/dep_diamond/build.zig+4-2
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
6 const shared = b.createModule(.{9 const shared = b.createModule(.{
7 .source_file = .{ .path = "shared.zig" },10 .source_file = .{ .path = "shared.zig" },
...@@ -23,6 +26,5 @@ pub fn build(b: *std.Build) void {...@@ -23,6 +26,5 @@ pub fn build(b: *std.Build) void {
2326
24 const run = exe.run();27 const run = exe.run();
2528
26 const test_step = b.step("test", "Test it");
27 test_step.dependOn(&run.step);29 test_step.dependOn(&run.step);
28}30}
test/standalone/dep_mutually_recursive/build.zig+4-2
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
6 const foo = b.createModule(.{9 const foo = b.createModule(.{
7 .source_file = .{ .path = "foo.zig" },10 .source_file = .{ .path = "foo.zig" },
...@@ -21,6 +24,5 @@ pub fn build(b: *std.Build) void {...@@ -21,6 +24,5 @@ pub fn build(b: *std.Build) void {
2124
22 const run = exe.run();25 const run = exe.run();
2326
24 const test_step = b.step("test", "Test it");
25 test_step.dependOn(&run.step);27 test_step.dependOn(&run.step);
26}28}
test/standalone/dep_recursive/build.zig+4-2
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
6 const foo = b.createModule(.{9 const foo = b.createModule(.{
7 .source_file = .{ .path = "foo.zig" },10 .source_file = .{ .path = "foo.zig" },
...@@ -17,6 +20,5 @@ pub fn build(b: *std.Build) void {...@@ -17,6 +20,5 @@ pub fn build(b: *std.Build) void {
1720
18 const run = exe.run();21 const run = exe.run();
1922
20 const test_step = b.step("test", "Test it");
21 test_step.dependOn(&run.step);23 test_step.dependOn(&run.step);
22}24}
test/standalone/dep_shared_builtin/build.zig+4-2
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
6 const exe = b.addExecutable(.{9 const exe = b.addExecutable(.{
7 .name = "test",10 .name = "test",
...@@ -14,6 +17,5 @@ pub fn build(b: *std.Build) void {...@@ -14,6 +17,5 @@ pub fn build(b: *std.Build) void {
1417
15 const run = exe.run();18 const run = exe.run();
1619
17 const test_step = b.step("test", "Test it");
18 test_step.dependOn(&run.step);20 test_step.dependOn(&run.step);
19}21}
test/standalone/dep_triangle/build.zig+4-2
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
6 const shared = b.createModule(.{9 const shared = b.createModule(.{
7 .source_file = .{ .path = "shared.zig" },10 .source_file = .{ .path = "shared.zig" },
...@@ -20,6 +23,5 @@ pub fn build(b: *std.Build) void {...@@ -20,6 +23,5 @@ pub fn build(b: *std.Build) void {
2023
21 const run = exe.run();24 const run = exe.run();
2225
23 const test_step = b.step("test", "Test it");
24 test_step.dependOn(&run.step);26 test_step.dependOn(&run.step);
25}27}
test/standalone/embed_generated_file/build.zig+3-5
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const target = b.standardTargetOptions(.{});4 const test_step = b.step("test", "Test it");
5 const optimize = b.standardOptimizeOption(.{});5 b.default_step = test_step;
66
7 const bootloader = b.addExecutable(.{7 const bootloader = b.addExecutable(.{
8 .name = "bootloader",8 .name = "bootloader",
...@@ -16,13 +16,11 @@ pub fn build(b: *std.Build) void {...@@ -16,13 +16,11 @@ pub fn build(b: *std.Build) void {
1616
17 const exe = b.addTest(.{17 const exe = b.addTest(.{
18 .root_source_file = .{ .path = "main.zig" },18 .root_source_file = .{ .path = "main.zig" },
19 .target = target,19 .optimize = .Debug,
20 .optimize = optimize,
21 });20 });
22 exe.addAnonymousModule("bootloader.elf", .{21 exe.addAnonymousModule("bootloader.elf", .{
23 .source_file = bootloader.getOutputSource(),22 .source_file = bootloader.getOutputSource(),
24 });23 });
2524
26 const test_step = b.step("test", "Test the program");
27 test_step.dependOn(&exe.step);25 test_step.dependOn(&exe.step);
28}26}
test/standalone/emit_asm_and_bin/build.zig+4-2
...@@ -1,6 +1,9 @@...@@ -1,6 +1,9 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
4 const main = b.addTest(.{7 const main = b.addTest(.{
5 .root_source_file = .{ .path = "main.zig" },8 .root_source_file = .{ .path = "main.zig" },
6 .optimize = b.standardOptimizeOption(.{}),9 .optimize = b.standardOptimizeOption(.{}),
...@@ -8,6 +11,5 @@ pub fn build(b: *std.Build) void {...@@ -8,6 +11,5 @@ pub fn build(b: *std.Build) void {
8 main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") };11 main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") };
9 main.emit_bin = .{ .emit_to = b.pathFromRoot("main") };12 main.emit_bin = .{ .emit_to = b.pathFromRoot("main") };
1013
11 const test_step = b.step("test", "Run test");14 test_step.dependOn(&main.run().step);
12 test_step.dependOn(&main.step);
13}15}
test/standalone/empty_env/build.zig+13-3
...@@ -1,15 +1,25 @@...@@ -1,15 +1,25 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");
23
3pub fn build(b: *std.Build) void {4pub 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
4 const main = b.addExecutable(.{15 const main = b.addExecutable(.{
5 .name = "main",16 .name = "main",
6 .root_source_file = .{ .path = "main.zig" },17 .root_source_file = .{ .path = "main.zig" },
7 .optimize = b.standardOptimizeOption(.{}),18 .optimize = optimize,
8 });19 });
920
10 const run = main.run();21 const run = b.addRunArtifact(main);
11 run.clearEnvironment();22 run.clearEnvironment();
1223
13 const test_step = b.step("test", "Test it");
14 test_step.dependOn(&run.step);24 test_step.dependOn(&run.step);
15}25}
test/standalone/extern/build.zig+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});4 const optimize: std.builtin.OptimizeMode = .Debug;
55
6 const obj = b.addObject(.{6 const obj = b.addObject(.{
7 .name = "exports",7 .name = "exports",
test/standalone/global_linkage/build.zig+8-5
...@@ -1,20 +1,24 @@...@@ -1,20 +1,24 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
6 const obj1 = b.addStaticLibrary(.{10 const obj1 = b.addStaticLibrary(.{
7 .name = "obj1",11 .name = "obj1",
8 .root_source_file = .{ .path = "obj1.zig" },12 .root_source_file = .{ .path = "obj1.zig" },
9 .optimize = optimize,13 .optimize = optimize,
10 .target = .{},14 .target = target,
11 });15 });
1216
13 const obj2 = b.addStaticLibrary(.{17 const obj2 = b.addStaticLibrary(.{
14 .name = "obj2",18 .name = "obj2",
15 .root_source_file = .{ .path = "obj2.zig" },19 .root_source_file = .{ .path = "obj2.zig" },
16 .optimize = optimize,20 .optimize = optimize,
17 .target = .{},21 .target = target,
18 });22 });
1923
20 const main = b.addTest(.{24 const main = b.addTest(.{
...@@ -24,6 +28,5 @@ pub fn build(b: *std.Build) void {...@@ -24,6 +28,5 @@ pub fn build(b: *std.Build) void {
24 main.linkLibrary(obj1);28 main.linkLibrary(obj1);
25 main.linkLibrary(obj2);29 main.linkLibrary(obj2);
2630
27 const test_step = b.step("test", "Test it");31 test_step.dependOn(&main.run().step);
28 test_step.dependOn(&main.step);
29}32}
test/standalone/install_raw_hex/build.zig+3-3
...@@ -3,8 +3,8 @@ const std = @import("std");...@@ -3,8 +3,8 @@ const std = @import("std");
3const CheckFileStep = std.Build.CheckFileStep;3const CheckFileStep = std.Build.CheckFileStep;
44
5pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test the program");6 const test_step = b.step("test", "Test it");
7 b.default_step.dependOn(test_step);7 b.default_step = test_step;
88
9 const target = .{9 const target = .{
10 .cpu_arch = .thumb,10 .cpu_arch = .thumb,
...@@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void {...@@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void {
13 .abi = .gnueabihf,13 .abi = .gnueabihf,
14 };14 };
1515
16 const optimize = b.standardOptimizeOption(.{});16 const optimize: std.builtin.OptimizeMode = .Debug;
1717
18 const elf = b.addExecutable(.{18 const elf = b.addExecutable(.{
19 .name = "zig-nrf52-blink.elf",19 .name = "zig-nrf52-blink.elf",
test/standalone/issue_11595/build.zig+14-17
...@@ -1,18 +1,17 @@...@@ -1,18 +1,17 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const CrossTarget = std.zig.CrossTarget;
43
5// TODO integrate this with the std.Build executor API4pub fn build(b: *std.Build) void {
6fn isRunnableTarget(t: CrossTarget) bool {5 const test_step = b.step("test", "Test it");
7 if (t.isNative()) return true;6 b.default_step = test_step;
87
9 return (t.getOsTag() == builtin.os.tag and8 const optimize: std.builtin.OptimizeMode = .Debug;
10 t.getCpuArch() == builtin.cpu.arch);9 const target: std.zig.CrossTarget = .{};
11}
1210
13pub fn build(b: *std.Build) void {11 if (builtin.os.tag == .windows) {
14 const optimize = b.standardOptimizeOption(.{});12 // https://github.com/ziglang/zig/issues/12419
15 const target = b.standardTargetOptions(.{});13 return;
14 }
1615
17 const exe = b.addExecutable(.{16 const exe = b.addExecutable(.{
18 .name = "zigtest",17 .name = "zigtest",
...@@ -44,11 +43,9 @@ pub fn build(b: *std.Build) void {...@@ -44,11 +43,9 @@ pub fn build(b: *std.Build) void {
4443
45 b.default_step.dependOn(&exe.step);44 b.default_step.dependOn(&exe.step);
4645
47 const test_step = b.step("test", "Test the program");46 const run_cmd = b.addRunArtifact(exe);
48 if (isRunnableTarget(target)) {47 run_cmd.skip_foreign_checks = true;
49 const run_cmd = exe.run();48 run_cmd.expectExitCode(0);
50 test_step.dependOn(&run_cmd.step);49
51 } else {50 test_step.dependOn(&run_cmd.step);
52 test_step.dependOn(&exe.step);
53 }
54}51}
test/standalone/issue_12588/build.zig+5-3
...@@ -1,8 +1,11 @@...@@ -1,8 +1,11 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});4 const test_step = b.step("test", "Test it");
5 const target = b.standardTargetOptions(.{});5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8 const target: std.zig.CrossTarget = .{};
69
7 const obj = b.addObject(.{10 const obj = b.addObject(.{
8 .name = "main",11 .name = "main",
...@@ -15,6 +18,5 @@ pub fn build(b: *std.Build) void {...@@ -15,6 +18,5 @@ pub fn build(b: *std.Build) void {
15 obj.emit_bin = .no_emit;18 obj.emit_bin = .no_emit;
16 b.default_step.dependOn(&obj.step);19 b.default_step.dependOn(&obj.step);
1720
18 const test_step = b.step("test", "Test the program");
19 test_step.dependOn(&obj.step);21 test_step.dependOn(&obj.step);
20}22}
test/standalone/issue_12706/build.zig+9-21
...@@ -2,17 +2,12 @@ const std = @import("std");...@@ -2,17 +2,12 @@ const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const CrossTarget = std.zig.CrossTarget;3const 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
13pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
14 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
15 const target = b.standardTargetOptions(.{});7 b.default_step = test_step;
8
9 const optimize: std.builtin.OptimizeMode = .Debug;
10 const target: std.zig.CrossTarget = .{};
1611
17 const exe = b.addExecutable(.{12 const exe = b.addExecutable(.{
18 .name = "main",13 .name = "main",
...@@ -20,22 +15,15 @@ pub fn build(b: *std.Build) void {...@@ -20,22 +15,15 @@ pub fn build(b: *std.Build) void {
20 .optimize = optimize,15 .optimize = optimize,
21 .target = target,16 .target = target,
22 });17 });
23 exe.install();
2418
25 const c_sources = [_][]const u8{19 const c_sources = [_][]const u8{
26 "test.c",20 "test.c",
27 };21 };
28
29 exe.addCSourceFiles(&c_sources, &.{});22 exe.addCSourceFiles(&c_sources, &.{});
30 exe.linkLibC();23 exe.linkLibC();
3124
32 b.default_step.dependOn(&exe.step);25 const run_cmd = b.addRunArtifact(exe);
3326 run_cmd.expectExitCode(0);
34 const test_step = b.step("test", "Test the program");27 run_cmd.skip_foreign_checks = true;
35 if (isRunnableTarget(target)) {28 test_step.dependOn(&run_cmd.step);
36 const run_cmd = exe.run();
37 test_step.dependOn(&run_cmd.step);
38 } else {
39 test_step.dependOn(&exe.step);
40 }
41}29}
test/standalone/issue_13030/build.zig+10-5
...@@ -3,17 +3,22 @@ const builtin = @import("builtin");...@@ -3,17 +3,22 @@ const builtin = @import("builtin");
3const CrossTarget = std.zig.CrossTarget;3const CrossTarget = std.zig.CrossTarget;
44
5pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
6 const optimize = b.standardOptimizeOption(.{});6 const test_step = b.step("test", "Test it");
7 const target = b.standardTargetOptions(.{});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 {
9 const obj = b.addObject(.{16 const obj = b.addObject(.{
10 .name = "main",17 .name = "main",
11 .root_source_file = .{ .path = "main.zig" },18 .root_source_file = .{ .path = "main.zig" },
12 .optimize = optimize,19 .optimize = optimize,
13 .target = target,20 .target = .{},
14 });21 });
15 b.default_step.dependOn(&obj.step);
1622
17 const test_step = b.step("test", "Test the program");
18 test_step.dependOn(&obj.step);23 test_step.dependOn(&obj.step);
19}24}
test/standalone/issue_13970/build.zig+6-4
...@@ -1,6 +1,9 @@...@@ -1,6 +1,9 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
4 const test1 = b.addTest(.{7 const test1 = b.addTest(.{
5 .root_source_file = .{ .path = "test_root/empty.zig" },8 .root_source_file = .{ .path = "test_root/empty.zig" },
6 });9 });
...@@ -14,8 +17,7 @@ pub fn build(b: *std.Build) void {...@@ -14,8 +17,7 @@ pub fn build(b: *std.Build) void {
14 test2.setTestRunner("src/main.zig");17 test2.setTestRunner("src/main.zig");
15 test3.setTestRunner("src/main.zig");18 test3.setTestRunner("src/main.zig");
1619
17 const test_step = b.step("test", "Test package path resolution of custom test runner");20 test_step.dependOn(&test1.run().step);
18 test_step.dependOn(&test1.step);21 test_step.dependOn(&test2.run().step);
19 test_step.dependOn(&test2.step);22 test_step.dependOn(&test3.run().step);
20 test_step.dependOn(&test3.step);
21}23}
test/standalone/issue_339/build.zig+8-3
...@@ -1,13 +1,18 @@...@@ -1,13 +1,18 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
4 const obj = b.addObject(.{10 const obj = b.addObject(.{
5 .name = "test",11 .name = "test",
6 .root_source_file = .{ .path = "test.zig" },12 .root_source_file = .{ .path = "test.zig" },
7 .target = b.standardTargetOptions(.{}),13 .target = target,
8 .optimize = b.standardOptimizeOption(.{}),14 .optimize = optimize,
9 });15 });
1016
11 const test_step = b.step("test", "Test the program");
12 test_step.dependOn(&obj.step);17 test_step.dependOn(&obj.step);
13}18}
test/standalone/issue_5825/build.zig+4-2
...@@ -1,12 +1,15 @@...@@ -1,12 +1,15 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
4 const target = .{7 const target = .{
5 .cpu_arch = .x86_64,8 .cpu_arch = .x86_64,
6 .os_tag = .windows,9 .os_tag = .windows,
7 .abi = .msvc,10 .abi = .msvc,
8 };11 };
9 const optimize = b.standardOptimizeOption(.{});12 const optimize: std.builtin.OptimizeMode = .Debug;
10 const obj = b.addObject(.{13 const obj = b.addObject(.{
11 .name = "issue_5825",14 .name = "issue_5825",
12 .root_source_file = .{ .path = "main.zig" },15 .root_source_file = .{ .path = "main.zig" },
...@@ -24,6 +27,5 @@ pub fn build(b: *std.Build) void {...@@ -24,6 +27,5 @@ pub fn build(b: *std.Build) void {
24 exe.linkSystemLibrary("ntdll");27 exe.linkSystemLibrary("ntdll");
25 exe.addObject(obj);28 exe.addObject(obj);
2629
27 const test_step = b.step("test", "Test the program");
28 test_step.dependOn(&exe.step);30 test_step.dependOn(&exe.step);
29}31}
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 @@...@@ -1,13 +1,13 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
4 const test_artifact = b.addTest(.{7 const test_artifact = b.addTest(.{
5 .root_source_file = .{ .path = "main.zig" },8 .root_source_file = .{ .path = "main.zig" },
6 });9 });
7 test_artifact.addIncludePath("a_directory");10 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");
12 test_step.dependOn(&test_artifact.step);12 test_step.dependOn(&test_artifact.step);
13}13}
test/standalone/issue_8550/build.zig+5-2
...@@ -1,6 +1,10 @@...@@ -1,6 +1,10 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) !void {3pub 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;
4 const target = std.zig.CrossTarget{8 const target = std.zig.CrossTarget{
5 .os_tag = .freestanding,9 .os_tag = .freestanding,
6 .cpu_arch = .arm,10 .cpu_arch = .arm,
...@@ -8,7 +12,7 @@ pub fn build(b: *std.Build) !void {...@@ -8,7 +12,7 @@ pub fn build(b: *std.Build) !void {
8 .explicit = &std.Target.arm.cpu.arm1176jz_s,12 .explicit = &std.Target.arm.cpu.arm1176jz_s,
9 },13 },
10 };14 };
11 const optimize = b.standardOptimizeOption(.{});15
12 const kernel = b.addExecutable(.{16 const kernel = b.addExecutable(.{
13 .name = "kernel",17 .name = "kernel",
14 .root_source_file = .{ .path = "./main.zig" },18 .root_source_file = .{ .path = "./main.zig" },
...@@ -19,6 +23,5 @@ pub fn build(b: *std.Build) !void {...@@ -19,6 +23,5 @@ pub fn build(b: *std.Build) !void {
19 kernel.setLinkerScriptPath(.{ .path = "./linker.ld" });23 kernel.setLinkerScriptPath(.{ .path = "./linker.ld" });
20 kernel.install();24 kernel.install();
2125
22 const test_step = b.step("test", "Test it");
23 test_step.dependOn(&kernel.step);26 test_step.dependOn(&kernel.step);
24}27}
test/standalone/issue_9812/build.zig+5-2
...@@ -1,7 +1,11 @@...@@ -1,7 +1,11 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) !void {3pub 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
5 const zip_add = b.addTest(.{9 const zip_add = b.addTest(.{
6 .root_source_file = .{ .path = "main.zig" },10 .root_source_file = .{ .path = "main.zig" },
7 .optimize = optimize,11 .optimize = optimize,
...@@ -13,6 +17,5 @@ pub fn build(b: *std.Build) !void {...@@ -13,6 +17,5 @@ pub fn build(b: *std.Build) !void {
13 zip_add.addIncludePath("vendor/kuba-zip");17 zip_add.addIncludePath("vendor/kuba-zip");
14 zip_add.linkLibC();18 zip_add.linkLibC();
1519
16 const test_step = b.step("test", "Test it");
17 test_step.dependOn(&zip_add.step);20 test_step.dependOn(&zip_add.step);
18}21}
test/standalone/load_dynamic_library/build.zig+16-4
...@@ -1,8 +1,19 @@...@@ -1,8 +1,19 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");
23
3pub fn build(b: *std.Build) void {4pub fn build(b: *std.Build) void {
4 const target = b.standardTargetOptions(.{});5 const test_step = b.step("test", "Test it");
5 const optimize = b.standardOptimizeOption(.{});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
7 const lib = b.addSharedLibrary(.{18 const lib = b.addSharedLibrary(.{
8 .name = "add",19 .name = "add",
...@@ -19,9 +30,10 @@ pub fn build(b: *std.Build) void {...@@ -19,9 +30,10 @@ pub fn build(b: *std.Build) void {
19 .target = target,30 .target = target,
20 });31 });
2132
22 const run = main.run();33 const run = b.addRunArtifact(main);
23 run.addArtifactArg(lib);34 run.addArtifactArg(lib);
35 run.skip_foreign_checks = true;
36 run.expectExitCode(0);
2437
25 const test_step = b.step("test", "Test the program");
26 test_step.dependOn(&run.step);38 test_step.dependOn(&run.step);
27}39}
test/standalone/load_dynamic_library/main.zig+1-5
...@@ -11,11 +11,7 @@ pub fn main() !void {...@@ -11,11 +11,7 @@ pub fn main() !void {
11 var lib = try std.DynLib.open(dynlib_name);11 var lib = try std.DynLib.open(dynlib_name);
12 defer lib.close();12 defer lib.close();
1313
14 const Add = switch (@import("builtin").zig_backend) {14 const Add = *const fn (i32, i32) callconv(.C) i32;
15 .stage1 => fn (i32, i32) callconv(.C) i32,
16 else => *const fn (i32, i32) callconv(.C) i32,
17 };
18
19 const addFn = lib.lookup(Add, "add") orelse return error.SymbolNotFound;15 const addFn = lib.lookup(Add, "add") orelse return error.SymbolNotFound;
2016
21 const result = addFn(12, 34);17 const result = addFn(12, 34);
test/standalone/main_pkg_path/build.zig+4-2
...@@ -1,11 +1,13 @@...@@ -1,11 +1,13 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
4 const test_exe = b.addTest(.{7 const test_exe = b.addTest(.{
5 .root_source_file = .{ .path = "a/test.zig" },8 .root_source_file = .{ .path = "a/test.zig" },
6 });9 });
7 test_exe.setMainPkgPath(".");10 test_exe.setMainPkgPath(".");
811
9 const test_step = b.step("test", "Test the program");12 test_step.dependOn(&test_exe.run().step);
10 test_step.dependOn(&test_exe.step);
11}13}
test/standalone/mix_c_files/build.zig+13-19
...@@ -1,34 +1,28 @@...@@ -1,34 +1,28 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");
3const CrossTarget = std.zig.CrossTarget;
42
5// TODO integrate this with the std.Build executor API3pub fn build(b: *std.Build) void {
6fn isRunnableTarget(t: CrossTarget) bool {4 const test_step = b.step("test", "Test it");
7 if (t.isNative()) return true;5 b.default_step = test_step;
86
9 return (t.getOsTag() == builtin.os.tag and7 add(b, test_step, .Debug);
10 t.getCpuArch() == builtin.cpu.arch);8 add(b, test_step, .ReleaseFast);
9 add(b, test_step, .ReleaseSmall);
10 add(b, test_step, .ReleaseSafe);
11}11}
1212
13pub fn build(b: *std.Build) void {13fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
14 const optimize = b.standardOptimizeOption(.{});
15 const target = b.standardTargetOptions(.{});
16
17 const exe = b.addExecutable(.{14 const exe = b.addExecutable(.{
18 .name = "test",15 .name = "test",
19 .root_source_file = .{ .path = "main.zig" },16 .root_source_file = .{ .path = "main.zig" },
20 .optimize = optimize,17 .optimize = optimize,
21 .target = target,
22 });18 });
23 exe.addCSourceFile("test.c", &[_][]const u8{"-std=c11"});19 exe.addCSourceFile("test.c", &[_][]const u8{"-std=c11"});
24 exe.linkLibC();20 exe.linkLibC();
25 b.default_step.dependOn(&exe.step);21 b.default_step.dependOn(&exe.step);
2622
27 const test_step = b.step("test", "Test the program");23 const run_cmd = b.addRunArtifact(exe);
28 if (isRunnableTarget(target)) {24 run_cmd.skip_foreign_checks = true;
29 const run_cmd = exe.run();25 run_cmd.expectExitCode(0);
30 test_step.dependOn(&run_cmd.step);26
31 } else {27 test_step.dependOn(&run_cmd.step);
32 test_step.dependOn(&exe.step);
33 }
34}28}
test/standalone/mix_o_files/build.zig+7-3
...@@ -1,18 +1,23 @@...@@ -1,18 +1,23 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
6 const obj = b.addObject(.{10 const obj = b.addObject(.{
7 .name = "base64",11 .name = "base64",
8 .root_source_file = .{ .path = "base64.zig" },12 .root_source_file = .{ .path = "base64.zig" },
9 .optimize = optimize,13 .optimize = optimize,
10 .target = .{},14 .target = target,
11 });15 });
1216
13 const exe = b.addExecutable(.{17 const exe = b.addExecutable(.{
14 .name = "test",18 .name = "test",
15 .optimize = optimize,19 .optimize = optimize,
20 .target = target,
16 });21 });
17 exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});22 exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
18 exe.addObject(obj);23 exe.addObject(obj);
...@@ -22,6 +27,5 @@ pub fn build(b: *std.Build) void {...@@ -22,6 +27,5 @@ pub fn build(b: *std.Build) void {
2227
23 const run_cmd = exe.run();28 const run_cmd = exe.run();
2429
25 const test_step = b.step("test", "Test the program");
26 test_step.dependOn(&run_cmd.step);30 test_step.dependOn(&run_cmd.step);
27}31}
test/standalone/options/build.zig+1-1
...@@ -20,5 +20,5 @@ pub fn build(b: *std.Build) void {...@@ -20,5 +20,5 @@ pub fn build(b: *std.Build) void {
20 options.addOption([]const u8, "string", b.option([]const u8, "string", "s").?);20 options.addOption([]const u8, "string", b.option([]const u8, "string", "s").?);
2121
22 const test_step = b.step("test", "Run unit tests");22 const test_step = b.step("test", "Run unit tests");
23 test_step.dependOn(&main.step);23 test_step.dependOn(&main.run().step);
24}24}
test/standalone/pie/build.zig+14-4
...@@ -1,14 +1,24 @@...@@ -1,14 +1,24 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
4 const main = b.addTest(.{13 const main = b.addTest(.{
5 .root_source_file = .{ .path = "main.zig" },14 .root_source_file = .{ .path = "main.zig" },
6 .optimize = b.standardOptimizeOption(.{}),15 .optimize = optimize,
16 .target = target,
7 });17 });
8 main.pie = true;18 main.pie = true;
919
10 const test_step = b.step("test", "Test the program");20 const run = main.run();
11 test_step.dependOn(&main.step);21 run.skip_foreign_checks = true;
1222
13 b.default_step.dependOn(test_step);23 test_step.dependOn(&run.step);
14}24}
test/standalone/pkg_import/build.zig+4-2
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
6 const exe = b.addExecutable(.{9 const exe = b.addExecutable(.{
7 .name = "test",10 .name = "test",
...@@ -12,6 +15,5 @@ pub fn build(b: *std.Build) void {...@@ -12,6 +15,5 @@ pub fn build(b: *std.Build) void {
1215
13 const run = exe.run();16 const run = exe.run();
1417
15 const test_step = b.step("test", "Test it");
16 test_step.dependOn(&run.step);18 test_step.dependOn(&run.step);
17}19}
test/standalone/shared_library/build.zig+5-5
...@@ -1,8 +1,11 @@...@@ -1,8 +1,11 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub fn build(b: *std.Build) void {
4 const optimize = b.standardOptimizeOption(.{});4 const test_step = b.step("test", "Test it");
5 const target = b.standardTargetOptions(.{});5 b.default_step = test_step;
6
7 const optimize: std.builtin.OptimizeMode = .Debug;
8 const target: std.zig.CrossTarget = .{};
6 const lib = b.addSharedLibrary(.{9 const lib = b.addSharedLibrary(.{
7 .name = "mathtest",10 .name = "mathtest",
8 .root_source_file = .{ .path = "mathtest.zig" },11 .root_source_file = .{ .path = "mathtest.zig" },
...@@ -20,10 +23,7 @@ pub fn build(b: *std.Build) void {...@@ -20,10 +23,7 @@ pub fn build(b: *std.Build) void {
20 exe.linkLibrary(lib);23 exe.linkLibrary(lib);
21 exe.linkSystemLibrary("c");24 exe.linkSystemLibrary("c");
2225
23 b.default_step.dependOn(&exe.step);
24
25 const run_cmd = exe.run();26 const run_cmd = exe.run();
2627
27 const test_step = b.step("test", "Test the program");
28 test_step.dependOn(&run_cmd.step);28 test_step.dependOn(&run_cmd.step);
29}29}
test/standalone/sigpipe/build.zig+14-5
...@@ -2,7 +2,16 @@ const std = @import("std");...@@ -2,7 +2,16 @@ const std = @import("std");
2const os = std.os;2const os = std.os;
33
4pub fn build(b: *std.build.Builder) !void {4pub 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
7 // This test runs "breakpipe" as a child process and that process16 // This test runs "breakpipe" as a child process and that process
8 // depends on inheriting a SIGPIPE disposition of "default".17 // depends on inheriting a SIGPIPE disposition of "default".
...@@ -23,12 +32,12 @@ pub fn build(b: *std.build.Builder) !void {...@@ -23,12 +32,12 @@ pub fn build(b: *std.build.Builder) !void {
23 .root_source_file = .{ .path = "breakpipe.zig" },32 .root_source_file = .{ .path = "breakpipe.zig" },
24 });33 });
25 exe.addOptions("build_options", options);34 exe.addOptions("build_options", options);
26 const run = exe.run();35 const run = b.addRunArtifact(exe);
27 if (keep_sigpipe) {36 if (keep_sigpipe) {
28 run.expected_term = .{ .Signal = std.os.SIG.PIPE };37 run.addCheck(.{ .expect_term = .{ .Signal = std.os.SIG.PIPE } });
29 } else {38 } else {
30 run.stdout_action = .{ .expect_exact = "BrokenPipe\n" };39 run.addCheck(.{ .expect_stdout_exact = "BrokenPipe\n" });
31 run.expected_term = .{ .Exited = 123 };40 run.addCheck(.{ .expect_term = .{ .Exited = 123 } });
32 }41 }
33 test_step.dependOn(&run.step);42 test_step.dependOn(&run.step);
34 }43 }
test/standalone/static_c_lib/build.zig+5-3
...@@ -1,7 +1,10 @@...@@ -1,7 +1,10 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
6 const foo = b.addStaticLibrary(.{9 const foo = b.addStaticLibrary(.{
7 .name = "foo",10 .name = "foo",
...@@ -18,6 +21,5 @@ pub fn build(b: *std.Build) void {...@@ -18,6 +21,5 @@ pub fn build(b: *std.Build) void {
18 test_exe.linkLibrary(foo);21 test_exe.linkLibrary(foo);
19 test_exe.addIncludePath(".");22 test_exe.addIncludePath(".");
2023
21 const test_step = b.step("test", "Test it");24 test_step.dependOn(&test_exe.run().step);
22 test_step.dependOn(&test_exe.step);
23}25}
test/standalone/test_runner_module_imports/build.zig+1-1
...@@ -15,5 +15,5 @@ pub fn build(b: *std.Build) void {...@@ -15,5 +15,5 @@ pub fn build(b: *std.Build) void {
15 t.addModule("module2", module2);15 t.addModule("module2", module2);
1616
17 const test_step = b.step("test", "Run unit tests");17 const test_step = b.step("test", "Run unit tests");
18 test_step.dependOn(&t.step);18 test_step.dependOn(&t.run().step);
19}19}
test/standalone/test_runner_path/build.zig+5-2
...@@ -1,14 +1,17 @@...@@ -1,14 +1,17 @@
1const std = @import("std");1const std = @import("std");
22
3pub const requires_stage2 = true;
4
3pub fn build(b: *std.Build) void {5pub fn build(b: *std.Build) void {
6 const test_step = b.step("test", "Test the program");
7 b.default_step = test_step;
8
4 const test_exe = b.addTest(.{9 const test_exe = b.addTest(.{
5 .root_source_file = .{ .path = "test.zig" },10 .root_source_file = .{ .path = "test.zig" },
6 .kind = .test_exe,
7 });11 });
8 test_exe.test_runner = "test_runner.zig";12 test_exe.test_runner = "test_runner.zig";
913
10 const test_run = test_exe.run();14 const test_run = test_exe.run();
1115
12 const test_step = b.step("test", "Test the program");
13 test_step.dependOn(&test_run.step);16 test_step.dependOn(&test_run.step);
14}17}
test/standalone/test_runner_path/test_runner.zig+4-36
...@@ -1,51 +1,19 @@...@@ -1,51 +1,19 @@
1const std = @import("std");1const std = @import("std");
2const io = std.io;
3const builtin = @import("builtin");2const builtin = @import("builtin");
43
5pub const io_mode: io.Mode = builtin.test_io_mode;
6
7pub fn main() void {4pub fn main() void {
8 const test_fn_list = builtin.test_functions;
9 var ok_count: usize = 0;5 var ok_count: usize = 0;
10 var skip_count: usize = 0;6 var skip_count: usize = 0;
11 var fail_count: usize = 0;7 var fail_count: usize = 0;
128
13 var async_frame_buffer: []align(std.Target.stack_align) u8 = undefined;9 for (builtin.test_functions) |test_fn| {
14 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly10 if (test_fn.func()) |_| {
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) |_| {
34 ok_count += 1;11 ok_count += 1;
35 } else |err| switch (err) {12 } else |err| switch (err) {
36 error.SkipZigTest => {13 error.SkipZigTest => skip_count += 1,
37 skip_count += 1;14 else => fail_count += 1,
38 },
39 else => {
40 fail_count += 1;
41 },
42 }15 }
43 }16 }
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 }
49 if (ok_count != 1 or skip_count != 1 or fail_count != 1) {17 if (ok_count != 1 or skip_count != 1 or fail_count != 1) {
50 std.process.exit(1);18 std.process.exit(1);
51 }19 }
test/standalone/use_alias/build.zig+7-3
...@@ -1,12 +1,16 @@...@@ -1,12 +1,16 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn build(b: *std.Build) void {3pub 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
4 const main = b.addTest(.{9 const main = b.addTest(.{
5 .root_source_file = .{ .path = "main.zig" },10 .root_source_file = .{ .path = "main.zig" },
6 .optimize = b.standardOptimizeOption(.{}),11 .optimize = optimize,
7 });12 });
8 main.addIncludePath(".");13 main.addIncludePath(".");
914
10 const test_step = b.step("test", "Test it");15 test_step.dependOn(&main.run().step);
11 test_step.dependOn(&main.step);
12}16}
test/standalone/windows_spawn/build.zig+13-3
...@@ -1,23 +1,33 @@...@@ -1,23 +1,33 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");
23
3pub fn build(b: *std.Build) void {4pub 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
6 const hello = b.addExecutable(.{13 const hello = b.addExecutable(.{
7 .name = "hello",14 .name = "hello",
8 .root_source_file = .{ .path = "hello.zig" },15 .root_source_file = .{ .path = "hello.zig" },
9 .optimize = optimize,16 .optimize = optimize,
17 .target = target,
10 });18 });
1119
12 const main = b.addExecutable(.{20 const main = b.addExecutable(.{
13 .name = "main",21 .name = "main",
14 .root_source_file = .{ .path = "main.zig" },22 .root_source_file = .{ .path = "main.zig" },
15 .optimize = optimize,23 .optimize = optimize,
24 .target = target,
16 });25 });
1726
18 const run = main.run();27 const run = b.addRunArtifact(main);
19 run.addArtifactArg(hello);28 run.addArtifactArg(hello);
29 run.expectExitCode(0);
30 run.skip_foreign_checks = true;
2031
21 const test_step = b.step("test", "Test it");
22 test_step.dependOn(&run.step);32 test_step.dependOn(&run.step);
23}33}
test/tests.zig+449-745
...@@ -1,16 +1,9 @@...@@ -1,16 +1,9 @@
1const std = @import("std");1const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const debug = std.debug;3const assert = std.debug.assert;
4const CrossTarget = std.zig.CrossTarget;4const CrossTarget = std.zig.CrossTarget;
5const io = std.io;
6const fs = std.fs;
7const mem = std.mem;5const mem = std.mem;
8const fmt = std.fmt;
9const ArrayList = std.ArrayList;
10const OptimizeMode = std.builtin.OptimizeMode;6const OptimizeMode = std.builtin.OptimizeMode;
11const CompileStep = std.Build.CompileStep;
12const Allocator = mem.Allocator;
13const ExecError = std.Build.ExecError;
14const Step = std.Build.Step;7const Step = std.Build.Step;
158
16// Cases9// Cases
...@@ -20,13 +13,13 @@ const stack_traces = @import("stack_traces.zig");...@@ -20,13 +13,13 @@ const stack_traces = @import("stack_traces.zig");
20const assemble_and_link = @import("assemble_and_link.zig");13const assemble_and_link = @import("assemble_and_link.zig");
21const translate_c = @import("translate_c.zig");14const translate_c = @import("translate_c.zig");
22const run_translated_c = @import("run_translated_c.zig");15const run_translated_c = @import("run_translated_c.zig");
23const gen_h = @import("gen_h.zig");
24const link = @import("link.zig");16const link = @import("link.zig");
2517
26// Implementations18// Implementations
27pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext;19pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext;
28pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext;20pub 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
31const TestTarget = struct {24const TestTarget = struct {
32 target: CrossTarget = @as(CrossTarget, .{}),25 target: CrossTarget = @as(CrossTarget, .{}),
...@@ -460,10 +453,71 @@ const test_targets = blk: {...@@ -460,10 +453,71 @@ const test_targets = blk: {
460 };453 };
461};454};
462455
463const max_stdout_size = 1 * 1024 * 1024; // 1 MB456const 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 {515pub fn addCompareOutputTests(
466 const cases = b.allocator.create(CompareOutputContext) catch unreachable;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");
467 cases.* = CompareOutputContext{521 cases.* = CompareOutputContext{
468 .b = b,522 .b = b,
469 .step = b.step("test-compare-output", "Run the compare output tests"),523 .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...@@ -477,14 +531,26 @@ pub fn addCompareOutputTests(b: *std.Build, test_filter: ?[]const u8, optimize_m
477 return cases.step;531 return cases.step;
478}532}
479533
480pub fn addStackTraceTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step {534pub fn addStackTraceTests(
481 const cases = b.allocator.create(StackTracesContext) catch unreachable;535 b: *std.Build,
482 cases.* = StackTracesContext{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.* = .{
483 .b = b,548 .b = b,
484 .step = b.step("test-stack-traces", "Run the stack trace tests"),549 .step = b.step("test-stack-traces", "Run the stack trace tests"),
485 .test_index = 0,550 .test_index = 0,
486 .test_filter = test_filter,551 .test_filter = test_filter,
487 .optimize_modes = optimize_modes,552 .optimize_modes = optimize_modes,
553 .check_exe = check_exe,
488 };554 };
489555
490 stack_traces.addCases(cases);556 stack_traces.addCases(cases);
...@@ -494,91 +560,302 @@ pub fn addStackTraceTests(b: *std.Build, test_filter: ?[]const u8, optimize_mode...@@ -494,91 +560,302 @@ pub fn addStackTraceTests(b: *std.Build, test_filter: ?[]const u8, optimize_mode
494560
495pub fn addStandaloneTests(561pub fn addStandaloneTests(
496 b: *std.Build,562 b: *std.Build,
497 test_filter: ?[]const u8,
498 optimize_modes: []const OptimizeMode,563 optimize_modes: []const OptimizeMode,
499 skip_non_native: bool,
500 enable_macos_sdk: bool,564 enable_macos_sdk: bool,
501 target: std.zig.CrossTarget,
502 omit_stage2: bool,565 omit_stage2: bool,
503 enable_darling: bool,
504 enable_qemu: bool,
505 enable_rosetta: bool,
506 enable_wasmtime: bool,
507 enable_wine: bool,
508 enable_symlinks_windows: bool,566 enable_symlinks_windows: bool,
509) *Step {567) *Step {
510 const cases = b.allocator.create(StandaloneContext) catch unreachable;568 const step = b.step("test-standalone", "Run the standalone tests");
511 cases.* = StandaloneContext{569 const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows;
512 .b = b,570
513 .step = b.step("test-standalone", "Run the standalone tests"),571 for (standalone.simple_cases) |case| {
514 .test_index = 0,572 for (optimize_modes) |optimize| {
515 .test_filter = test_filter,573 if (!case.all_modes and optimize != .Debug) continue;
516 .optimize_modes = optimize_modes,574 if (case.os_filter) |os_tag| {
517 .skip_non_native = skip_non_native,575 if (os_tag != builtin.os.tag) continue;
518 .enable_macos_sdk = enable_macos_sdk,576 }
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 };
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;
532}626}
533627
534pub fn addLinkTests(628pub fn addLinkTests(
535 b: *std.Build,629 b: *std.Build,
536 test_filter: ?[]const u8,
537 optimize_modes: []const OptimizeMode,
538 enable_macos_sdk: bool,630 enable_macos_sdk: bool,
539 omit_stage2: bool,631 omit_stage2: bool,
540 enable_symlinks_windows: bool,632 enable_symlinks_windows: bool,
541) *Step {633) *Step {
542 const cases = b.allocator.create(StandaloneContext) catch unreachable;634 const step = b.step("test-link", "Run the linker tests");
543 cases.* = StandaloneContext{635 const omit_symlinks = builtin.os.tag == .windows and !enable_symlinks_windows;
544 .b = b,636
545 .step = b.step("test-link", "Run the linker tests"),637 inline for (link.cases) |case| {
546 .test_index = 0,638 const requires_stage2 = @hasDecl(case.import, "requires_stage2") and
547 .test_filter = test_filter,639 case.import.requires_stage2;
548 .optimize_modes = optimize_modes,640 const requires_symlinks = @hasDecl(case.import, "requires_symlinks") and
549 .skip_non_native = true,641 case.import.requires_symlinks;
550 .enable_macos_sdk = enable_macos_sdk,642 const requires_macos_sdk = @hasDecl(case.import, "requires_macos_sdk") and
551 .target = .{},643 case.import.requires_macos_sdk;
552 .omit_stage2 = omit_stage2,644 const bad =
553 .enable_symlinks_windows = enable_symlinks_windows,645 (requires_stage2 and omit_stage2) or
554 };646 (requires_symlinks and omit_symlinks) or
555 link.addCases(cases);647 (requires_macos_sdk and !enable_macos_sdk);
556 return cases.step;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;
557}659}
558660
559pub fn addCliTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step {661pub fn addCliTests(b: *std.Build) *Step {
560 _ = test_filter;
561 _ = optimize_modes;
562 const step = b.step("test-cli", "Test the command line interface");662 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(.{689 {
565 .name = "test-cli",690 // Test `zig init-exe`.
566 .root_source_file = .{ .path = "test/cli.zig" },691 const tmp_path = b.makeTempPath();
567 .target = .{},692 const init_exe = b.addSystemCommand(&.{ b.zig_exe, "init-exe" });
568 .optimize = .Debug,693 init_exe.cwd = tmp_path;
569 });694 init_exe.setName("zig init-exe");
570 const run_cmd = exe.run();695 init_exe.expectStdOutEqual("");
571 run_cmd.addArgs(&[_][]const u8{696 init_exe.expectStdErrEqual("info: Created build.zig\n" ++
572 fs.realpathAlloc(b.allocator, b.zig_exe) catch unreachable,697 "info: Created src" ++ s ++ "main.zig\n" ++
573 b.pathFromRoot(b.cache_root.path orelse "."),698 "info: Next, try `zig build --help` or `zig build run`\n");
574 });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);
577 return step;854 return step;
578}855}
579856
580pub fn addAssembleAndLinkTests(b: *std.Build, test_filter: ?[]const u8, optimize_modes: []const OptimizeMode) *Step {857pub 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");
582 cases.* = CompareOutputContext{859 cases.* = CompareOutputContext{
583 .b = b,860 .b = b,
584 .step = b.step("test-asm-link", "Run the assemble and link tests"),861 .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...@@ -593,7 +870,7 @@ pub fn addAssembleAndLinkTests(b: *std.Build, test_filter: ?[]const u8, optimize
593}870}
594871
595pub fn addTranslateCTests(b: *std.Build, test_filter: ?[]const u8) *Step {872pub 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");
597 cases.* = TranslateCContext{874 cases.* = TranslateCContext{
598 .b = b,875 .b = b,
599 .step = b.step("test-translate-c", "Run the C translation tests"),876 .step = b.step("test-translate-c", "Run the C translation tests"),
...@@ -611,7 +888,7 @@ pub fn addRunTranslatedCTests(...@@ -611,7 +888,7 @@ pub fn addRunTranslatedCTests(
611 test_filter: ?[]const u8,888 test_filter: ?[]const u8,
612 target: std.zig.CrossTarget,889 target: std.zig.CrossTarget,
613) *Step {890) *Step {
614 const cases = b.allocator.create(RunTranslatedCContext) catch unreachable;891 const cases = b.allocator.create(RunTranslatedCContext) catch @panic("OOM");
615 cases.* = .{892 cases.* = .{
616 .b = b,893 .b = b,
617 .step = b.step("test-run-translated-c", "Run the Run-Translated-C tests"),894 .step = b.step("test-run-translated-c", "Run the Run-Translated-C tests"),
...@@ -625,22 +902,7 @@ pub fn addRunTranslatedCTests(...@@ -625,22 +902,7 @@ pub fn addRunTranslatedCTests(
625 return cases.step;902 return cases.step;
626}903}
627904
628pub fn addGenHTests(b: *std.Build, test_filter: ?[]const u8) *Step {905const ModuleTestOptions = struct {
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,
644 test_filter: ?[]const u8,906 test_filter: ?[]const u8,
645 root_src: []const u8,907 root_src: []const u8,
646 name: []const u8,908 name: []const u8,
...@@ -651,14 +913,17 @@ pub fn addPkgTests(...@@ -651,14 +913,17 @@ pub fn addPkgTests(
651 skip_libc: bool,913 skip_libc: bool,
652 skip_stage1: bool,914 skip_stage1: bool,
653 skip_stage2: bool,915 skip_stage2: bool,
654) *Step {916 max_rss: usize = 0,
655 const step = b.step(b.fmt("test-{s}", .{name}), desc);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
657 for (test_targets) |test_target| {922 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())
659 continue;924 continue;
660925
661 if (skip_libc and test_target.link_libc)926 if (options.skip_libc and test_target.link_libc)
662 continue;927 continue;
663928
664 if (test_target.link_libc and test_target.target.getOs().requiresLibC()) {929 if (test_target.link_libc and test_target.target.getOs().requiresLibC()) {
...@@ -666,7 +931,7 @@ pub fn addPkgTests(...@@ -666,7 +931,7 @@ pub fn addPkgTests(
666 continue;931 continue;
667 }932 }
668933
669 if (skip_single_threaded and test_target.single_threaded)934 if (options.skip_single_threaded and test_target.single_threaded)
670 continue;935 continue;
671936
672 if (test_target.disable_native and937 if (test_target.disable_native and
...@@ -677,12 +942,12 @@ pub fn addPkgTests(...@@ -677,12 +942,12 @@ pub fn addPkgTests(
677 }942 }
678943
679 if (test_target.backend) |backend| switch (backend) {944 if (test_target.backend) |backend| switch (backend) {
680 .stage1 => if (skip_stage1) continue,945 .stage1 => if (options.skip_stage1) continue,
681 .stage2_llvm => {},946 .stage2_llvm => {},
682 else => if (skip_stage2) continue,947 else => if (options.skip_stage2) continue,
683 };948 };
684949
685 const want_this_mode = for (optimize_modes) |m| {950 const want_this_mode = for (options.optimize_modes) |m| {
686 if (m == test_target.optimize_mode) break true;951 if (m == test_target.optimize_mode) break true;
687 } else false;952 } else false;
688 if (!want_this_mode) continue;953 if (!want_this_mode) continue;
...@@ -694,25 +959,24 @@ pub fn addPkgTests(...@@ -694,25 +959,24 @@ pub fn addPkgTests(
694 else959 else
695 "bare";960 "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
699 const these_tests = b.addTest(.{970 const these_tests = b.addTest(.{
700 .root_source_file = .{ .path = root_src },971 .root_source_file = .{ .path = options.root_src },
701 .optimize = test_target.optimize_mode,972 .optimize = test_target.optimize_mode,
702 .target = test_target.target,973 .target = test_target.target,
974 .max_rss = max_rss,
703 });975 });
704 const single_threaded_txt = if (test_target.single_threaded) "single" else "multi";976 const single_threaded_txt = if (test_target.single_threaded) "single" else "multi";
705 const backend_txt = if (test_target.backend) |backend| @tagName(backend) else "default";977 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 }));
714 these_tests.single_threaded = test_target.single_threaded;978 these_tests.single_threaded = test_target.single_threaded;
715 these_tests.setFilter(test_filter);979 these_tests.setFilter(options.test_filter);
716 if (test_target.link_libc) {980 if (test_target.link_libc) {
717 these_tests.linkSystemLibrary("c");981 these_tests.linkSystemLibrary("c");
718 }982 }
...@@ -736,654 +1000,94 @@ pub fn addPkgTests(...@@ -736,654 +1000,94 @@ pub fn addPkgTests(
736 },1000 },
737 };1001 };
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);
740 }1015 }
741 return step;1016 return step;
742}1017}
7431018
744pub const StackTracesContext = struct {1019pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *Step {
745 b: *std.Build,1020 const step = b.step("test-c-abi", "Run the C ABI tests");
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 }
10821021
1083 if (features.use_emulation) {1022 const optimize_modes: [2]OptimizeMode = .{ .Debug, .ReleaseFast };
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 }
11001023
1101 const optimize_modes = if (features.build_modes) self.optimize_modes else &[1]OptimizeMode{.Debug};1024 for (optimize_modes) |optimize_mode| {
1102 for (optimize_modes) |optimize_mode| {1025 if (optimize_mode != .Debug and skip_release) continue;
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 }
11211026
1122 pub fn addAllArgs(self: *StandaloneContext, root_src: []const u8, link_libc: bool) void {1027 for (c_abi_targets) |c_abi_target| {
1123 const b = self.b;1028 if (skip_non_native and !c_abi_target.isNative()) continue;
11241029
1125 for (self.optimize_modes) |optimize| {1030 if (c_abi_target.isWindows() and c_abi_target.getCpuArch() == .aarch64) {
1126 const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {s} ({s})", .{1031 // https://github.com/ziglang/zig/issues/14908
1127 root_src,1032 continue;
1128 @tagName(optimize),
1129 }) catch unreachable;
1130 if (self.test_filter) |filter| {
1131 if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
1132 }1033 }
11331034
1134 const exe = b.addExecutable(.{1035 const test_step = b.addTest(.{
1135 .name = "test",1036 .root_source_file = .{ .path = "test/c_abi/main.zig" },
1136 .root_source_file = .{ .path = root_src },1037 .optimize = optimize_mode,
1137 .optimize = optimize,1038 .target = c_abi_target,
1138 .target = .{},
1139 });1039 });
1140 if (link_libc) {1040 if (c_abi_target.abi != null and c_abi_target.abi.?.isMusl()) {
1141 exe.linkSystemLibrary("c");1041 // TODO NativeTargetInfo insists on dynamically linking musl
1042 // for some reason?
1043 test_step.target_info.dynamic_linker.max_byte = null;
1142 }1044 }
11431045 test_step.linkLibC();
1144 const log_step = b.addLog("PASS {s}", .{annotated_case_name});1046 test_step.addCSourceFile("test/c_abi/cfuncs.c", &.{"-std=c99"});
1145 log_step.step.dependOn(&exe.step);1047
11461048 // test-c-abi should test both with LTO on and with LTO off. Only
1147 self.step.dependOn(&log_step.step);1049 // some combinations are passing currently:
1148 }1050 // https://github.com/ziglang/zig/issues/14908
1149 }1051 if (c_abi_target.isWindows()) {
1150};1052 test_step.want_lto = false;
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 }
1230 }1053 }
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 {1055 const triple_prefix = c_abi_target.zigTriple(b.allocator) catch @panic("OOM");
1263 const b = self.b;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;1060 const run = test_step.run();
1266 const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {s} ({s})", .{ case.name, @tagName(optimize_mode) }) catch unreachable;1061 run.skip_foreign_checks = true;
1267 if (self.test_filter) |filter| {1062 step.dependOn(&run.step);
1268 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
1269 }1063 }
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);
1282 }1064 }
1283};1065 return step;
1284
1285fn printInvocation(args: []const []const u8) void {
1286 for (args) |arg| {
1287 std.debug.print("{s} ", .{arg});
1288 }
1289 std.debug.print("\n", .{});
1290}1066}
12911067
1292const c_abi_targets = [_]CrossTarget{1068pub fn addCases(
1293 .{},1069 b: *std.Build,
1294 .{1070 parent_step: *Step,
1295 .cpu_arch = .x86_64,1071 opt_test_filter: ?[]const u8,
1296 .os_tag = .linux,1072 check_case_exe: *std.Build.CompileStep,
1297 .abi = .musl,1073) !void {
1298 },1074 const arena = b.allocator;
1299 .{1075 const gpa = b.allocator;
1300 .cpu_arch = .x86,1076
1301 .os_tag = .linux,1077 var cases = @import("src/Cases.zig").init(gpa, arena);
1302 .abi = .musl,1078
1303 },1079 var dir = try b.build_root.handle.openIterableDir("test/cases", .{});
1304 .{1080 defer dir.close();
1305 .cpu_arch = .aarch64,1081
1306 .os_tag = .linux,1082 cases.addFromDir(dir);
1307 .abi = .musl,1083 try @import("cases.zig").addCases(&cases);
1308 },1084
1309 .{1085 const cases_dir_path = try b.build_root.join(b.allocator, &.{ "test", "cases" });
1310 .cpu_arch = .arm,1086 cases.lowerToBuildSteps(
1311 .os_tag = .linux,1087 b,
1312 .abi = .musleabihf,1088 parent_step,
1313 },1089 opt_test_filter,
1314 .{1090 cases_dir_path,
1315 .cpu_arch = .mips,1091 check_case_exe,
1316 .os_tag = .linux,1092 );
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;
1389}1093}